From 776f7d953aa2199bade8e84fd35804fd89fba08c Mon Sep 17 00:00:00 2001 From: Henry Zhu Date: Fri, 31 Jan 2025 19:38:16 +0800 Subject: [PATCH] Make index page responsive (#80) * Make index page responsive * Refine index css styles --- beaverhabits/configs.py | 3 +- beaverhabits/frontend/habit_page.py | 3 +- beaverhabits/frontend/index_page.py | 108 +++++++++++++++++----------- beaverhabits/routes.py | 8 +-- uv.lock | 6 +- 5 files changed, 79 insertions(+), 49 deletions(-) diff --git a/beaverhabits/configs.py b/beaverhabits/configs.py index 7ccd99c8..d38cc7b0 100644 --- a/beaverhabits/configs.py +++ b/beaverhabits/configs.py @@ -39,12 +39,13 @@ class Settings(BaseSettings): TRUSTED_LOCAL_EMAIL: str = "" # Customization - INDEX_HABIT_ITEM_COUNT: int = 5 FIRST_DAY_OF_WEEK: int = calendar.MONDAY ENABLE_IOS_STANDALONE: bool = False ENABLE_DESKTOP_ALGIN_CENTER: bool = True INDEX_SHOW_HABIT_COUNT: bool = False + INDEX_DAYS_COUNT: int = 8 + def is_dev(self): return self.ENV == "dev" diff --git a/beaverhabits/frontend/habit_page.py b/beaverhabits/frontend/habit_page.py index f8be29d2..6ab52978 100644 --- a/beaverhabits/frontend/habit_page.py +++ b/beaverhabits/frontend/habit_page.py @@ -52,7 +52,8 @@ def card(link: str | None = None, padding: float = 3): def habit_page(today: datetime.date, habit: Habit): notes = [x for x in habit.records if x.text] notes.sort(key=lambda x: x.day, reverse=True) - masony = "lg:grid-cols-2" if notes else "lg:grid-cols-1" + # https://tailwindcss.com/docs/responsive-design#container-size-reference + masony = "lg:grid-cols-2" if notes else "" with grid(masony): habit_calendar = CalendarHeatmap.build(today, WEEKS_TO_DISPLAY, calendar.MONDAY) diff --git a/beaverhabits/frontend/index_page.py b/beaverhabits/frontend/index_page.py index 98cb2dc9..ee927c70 100644 --- a/beaverhabits/frontend/index_page.py +++ b/beaverhabits/frontend/index_page.py @@ -1,5 +1,7 @@ import datetime import os +from contextlib import contextmanager +from typing import Iterable from nicegui import ui @@ -8,15 +10,12 @@ from beaverhabits.frontend.components import HabitCheckBox, IndexBadge, link from beaverhabits.frontend.layout import layout from beaverhabits.storage.meta import get_root_path -from beaverhabits.storage.storage import HabitList, HabitListBuilder, HabitStatus +from beaverhabits.storage.storage import Habit, HabitList, HabitListBuilder, HabitStatus -HABIT_LIST_RECORD_COUNT = settings.INDEX_HABIT_ITEM_COUNT +HABIT_LIST_RECORD_COUNT = settings.INDEX_DAYS_COUNT - -def grid(columns, rows): - g = ui.grid(columns=columns, rows=rows) - g.classes("w-full gap-0 items-center") - return g +LEFT_ITEM_CLASSES = "w-32 sm:w-36 truncate self-center" +RIGHT_ITEM_CLASSES = "w-10 self-center" def week_headers(days: list[datetime.date]): @@ -33,52 +32,81 @@ def day_headers(days: list[datetime.date]): yield "#" +@contextmanager +def row(): + with ui.row().classes("pl-4 pr-0 py-0").classes(f"no-wrap gap-0"): + yield + + +@contextmanager +def card(): + with ui.card().classes("shadow-none gap-1.5 p-0"): + with row(): + yield + + +@contextmanager +def flex(height: int): + # Responsive flex container + with ui.element("div") as f: + # Auto hide flex items when it overflows the flex parent + f.classes("flex flex-row-reverse w-full justify-evenly") + # Auto ajust gap with screen size + f.classes("gap-x-0.5 sm:gap-x-1.5") + # Auto hide overflowed items + f.classes(f"overflow-hidden h-{height}") + yield f + + +def name(habit: Habit): + # truncate name + redirect_page = os.path.join(get_root_path(), "habits", str(habit.id)) + name = link(habit.name, target=redirect_page) + name.classes(LEFT_ITEM_CLASSES) + + +def headers(labels: Iterable[str]): + with flex(4): + for text in labels: + label = ui.label(text) + label.classes(RIGHT_ITEM_CLASSES) + label.style( + "font-size: 85%; font-weight: 500; color: #9e9e9e; text-align: center" + ) + + +def checkboxes(habit: Habit, days: list[datetime.date]): + with flex(10): + ticked_days = set(habit.ticked_days) + for day in days: + checkbox = HabitCheckBox(habit, day, day in ticked_days) + checkbox.classes(RIGHT_ITEM_CLASSES) + + @ui.refreshable def habit_list_ui(days: list[datetime.date], habit_list: HabitList): active_habits = HabitListBuilder(habit_list).status(HabitStatus.ACTIVE).build() if not active_habits: - ui.label("List is empty.").classes("mx-auto w-80") + ui.label("List is empty.") return - # Calculate column count - name_columns, date_columns = 4, 2 - count_columns = 2 if settings.INDEX_SHOW_HABIT_COUNT else 0 - columns = name_columns + len(days) * date_columns + count_columns - - row_compat_classes = "pl-4 pr-1 py-0" - left_classes, right_classes = ( - # grid 4 - f"col-span-{name_columns} truncate", - # grid 2 2 2 2 2 - f"col-span-{date_columns} px-1.5 justify-self-center", - ) - header_styles = "font-size: 85%; font-weight: 500; color: #9e9e9e" + days = list(reversed(days)) with ui.column().classes("gap-1.5"): # Date Headers - with grid(columns, 2).classes(row_compat_classes): + with ui.column().classes("gap-0"): for it in (week_headers(days), day_headers(days)): - ui.label("").classes(left_classes) - for label in it: - ui.label(label).classes(right_classes).style(header_styles) + with row(): + ui.label("").classes(LEFT_ITEM_CLASSES) + headers(it) # Habit List for habit in active_habits: - with ui.card().classes(row_compat_classes).classes("shadow-none"): - with grid(columns, 1): - # truncate name - root_path = get_root_path() - redirect_page = os.path.join(root_path, "habits", str(habit.id)) - name = link(habit.name, target=redirect_page).classes(left_classes) - name.style(f"max-width: {52 * name_columns / date_columns}px;") - - ticked_days = set(habit.ticked_days) - for day in days: - checkbox = HabitCheckBox(habit, day, day in ticked_days) - checkbox.classes(right_classes) - - if settings.INDEX_SHOW_HABIT_COUNT: - IndexBadge(habit).classes(right_classes) + with card(): + name(habit) + checkboxes(habit, days) + if settings.INDEX_SHOW_HABIT_COUNT: + IndexBadge(habit).classes(RIGHT_ITEM_CLASSES) def index_page_ui(days: list[datetime.date], habits: HabitList): diff --git a/beaverhabits/routes.py b/beaverhabits/routes.py index 015fb5d8..1a34e569 100644 --- a/beaverhabits/routes.py +++ b/beaverhabits/routes.py @@ -30,21 +30,21 @@ @ui.page("/demo") async def demo_index_page() -> None: - days = await dummy_days(settings.INDEX_HABIT_ITEM_COUNT) + days = await dummy_days(settings.INDEX_DAYS_COUNT) habit_list = views.get_or_create_session_habit_list(days) index_page_ui(days, habit_list) @ui.page("/demo/add") async def demo_add_page() -> None: - days = await dummy_days(settings.INDEX_HABIT_ITEM_COUNT) + days = await dummy_days(settings.INDEX_DAYS_COUNT) habit_list = views.get_or_create_session_habit_list(days) add_page_ui(habit_list) @ui.page("/demo/order") async def demo_order_page() -> None: - days = await dummy_days(settings.INDEX_HABIT_ITEM_COUNT) + days = await dummy_days(settings.INDEX_DAYS_COUNT) habit_list = views.get_or_create_session_habit_list(days) order_page_ui(habit_list) @@ -78,7 +78,7 @@ async def demo_export() -> None: async def index_page( user: User = Depends(current_active_user), ) -> None: - days = await dummy_days(settings.INDEX_HABIT_ITEM_COUNT) + days = await dummy_days(settings.INDEX_DAYS_COUNT) habit_list = await views.get_user_habit_list(user) index_page_ui(days, habit_list) diff --git a/uv.lock b/uv.lock index 57f17a55..391d4c5c 100644 --- a/uv.lock +++ b/uv.lock @@ -481,16 +481,16 @@ wheels = [ [[package]] name = "fastapi" -version = "0.115.7" +version = "0.115.8" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydantic" }, { name = "starlette" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a2/f5/3f921e59f189e513adb9aef826e2841672d50a399fead4e69afdeb808ff4/fastapi-0.115.7.tar.gz", hash = "sha256:0f106da6c01d88a6786b3248fb4d7a940d071f6f488488898ad5d354b25ed015", size = 293177 } +sdist = { url = "https://files.pythonhosted.org/packages/a2/b2/5a5dc4affdb6661dea100324e19a7721d5dc524b464fe8e366c093fd7d87/fastapi-0.115.8.tar.gz", hash = "sha256:0ce9111231720190473e222cdf0f07f7206ad7e53ea02beb1d2dc36e2f0741e9", size = 295403 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e6/7f/bbd4dcf0faf61bc68a01939256e2ed02d681e9334c1a3cef24d5f77aba9f/fastapi-0.115.7-py3-none-any.whl", hash = "sha256:eb6a8c8bf7f26009e8147111ff15b5177a0e19bb4a45bc3486ab14804539d21e", size = 94777 }, + { url = "https://files.pythonhosted.org/packages/8f/7d/2d6ce181d7a5f51dedb8c06206cbf0ec026a99bf145edd309f9e17c3282f/fastapi-0.115.8-py3-none-any.whl", hash = "sha256:753a96dd7e036b34eeef8babdfcfe3f28ff79648f86551eb36bfc1b0bf4a8cbf", size = 94814 }, ] [[package]]