Skip to content

Commit

Permalink
Make index page responsive (#80)
Browse files Browse the repository at this point in the history
* Make index page responsive

* Refine index css styles
  • Loading branch information
daya0576 authored Jan 31, 2025
1 parent 6382ab1 commit 776f7d9
Show file tree
Hide file tree
Showing 5 changed files with 79 additions and 49 deletions.
3 changes: 2 additions & 1 deletion beaverhabits/configs.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
3 changes: 2 additions & 1 deletion beaverhabits/frontend/habit_page.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
108 changes: 68 additions & 40 deletions beaverhabits/frontend/index_page.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import datetime
import os
from contextlib import contextmanager
from typing import Iterable

from nicegui import ui

Expand All @@ -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]):
Expand All @@ -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):
Expand Down
8 changes: 4 additions & 4 deletions beaverhabits/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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)

Expand Down
6 changes: 3 additions & 3 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 776f7d9

Please sign in to comment.