From 8c97f5945370b820e78fcaf0a3dc32960b65e4f9 Mon Sep 17 00:00:00 2001 From: John O'Keefe Date: Tue, 27 Oct 2026 10:22:13 -0400 Subject: [PATCH] feat(dashboard): move This Week stats into the library bar as a popover MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The "This Week" reading-stats strip leaves its full-width row below the subbar and now lives inside the library bar itself, between the switcher and the customize/refresh actions. Expanding it opens an absolutely positioned popover that FLOATS over the collections — opening the stats no longer shifts the page down. - LibrarySwitcher gains an optional `center` component slot rendered between the left group and the actions group (nil = unused). Series and collections call sites pass nil and render byte-identical output. - DashboardWeekWidget redesigned for the slot: compact inline summary (icon, "This Week", minutes; pages/sessions/books/streak reveal at md/lg/xl) and a card popover panel with the labeled grid, sync note, and the Full stats link moved in from the old summary row. - Panel anchoring is responsive: below `sm` it anchors to the sticky bar itself (details goes static; bar-anchored left-4/right-4, i.e. screen-centered); at `sm`+ the details is relative again and the panel centers on the widget (w-[30rem], translate-x centering). - On phones the summary collapses to the chart icon + chevron (bar row width is scarce); "This Week"/minutes reveal at `sm`, and the "Your Library" title hides below `sm` to keep the row from overlapping. Icon-only toggle carries an aria-label. - web/src/dashboard.ts: click-outside and Escape close the popover, re-querying #week-widget per event because htmx replaces it on library switches. Rendering untouched (behavior-only island). - id="week-widget" and all htmx wiring unchanged: week-refresh refetch + the /dashboard/week-widget fragment still drive library-scoped refreshes behind the switch fade; stats-off suppression now yields an empty slot via weekWidgetComponent instead of a hidden strip. - web/static/style.css rebuilt for the new utilities. Known behavior (unchanged or deliberate): the bar is sticky, so stats stay visible while scrolling; a library switch re-renders the details closed; scopes with no current-week reading show zeros (e.g. Manga). --- templates/collections.templ | 2 +- templates/collections_templ.go | 2 +- templates/dashboard.templ | 91 ++++--- templates/dashboard_templ.go | 356 ++++++++++++++-------------- templates/library_switcher.templ | 14 +- templates/library_switcher_templ.go | 45 ++-- templates/series.templ | 2 +- templates/series_templ.go | 2 +- web/src/dashboard.ts | 17 ++ web/static/style.css | 2 +- 10 files changed, 299 insertions(+), 234 deletions(-) diff --git a/templates/collections.templ b/templates/collections.templ index 2b75af37..d62017b9 100644 --- a/templates/collections.templ +++ b/templates/collections.templ @@ -218,7 +218,7 @@ templ CollectionDetail(user User, collection CollectionData, books []handlers.Bo @Header(user, "/collections") - @LibrarySwitcher(libData, libraryID, "") + @LibrarySwitcher(libData, libraryID, "", nil)
diff --git a/templates/collections_templ.go b/templates/collections_templ.go index c7bcf329..2da1cf73 100644 --- a/templates/collections_templ.go +++ b/templates/collections_templ.go @@ -613,7 +613,7 @@ func CollectionDetail(user User, collection CollectionData, books []handlers.Boo if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = LibrarySwitcher(libData, libraryID, "").Render(ctx, templ_7745c5c3_Buffer) + templ_7745c5c3_Err = LibrarySwitcher(libData, libraryID, "", nil).Render(ctx, templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/templates/dashboard.templ b/templates/dashboard.templ index dfdfc091..b045f7b6 100644 --- a/templates/dashboard.templ +++ b/templates/dashboard.templ @@ -18,7 +18,7 @@ templ Dashboard(user User, sections []handlers.SectionData, allSections []handle @Header(user, "/dashboard") - @LibrarySwitcher(libData, currentLibraryID, "Your Library", DashboardActions()) + @LibrarySwitcher(libData, currentLibraryID, "Your Library", weekWidgetComponent(week, activeLibType), DashboardActions()) ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if MediaBadgeIcon(item.Badge) != "" { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "\">") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -505,105 +491,105 @@ func BookCard(item handlers.BookInfo) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if item.Author != "" { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "\">") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var26 string templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs(item.Author) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 169, Col: 19} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 164, Col: 19} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if item.HasConflict { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, "\" title=\"Resolve progress conflict\">") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -611,38 +597,38 @@ func BookCard(item handlers.BookInfo) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } else if item.FormatGroup == "audiobook" { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "\" title=\"Listen\">") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -650,38 +636,38 @@ func BookCard(item handlers.BookInfo) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 62, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 60, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 63, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 63, "\" title=\"Read\">") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -689,12 +675,12 @@ func BookCard(item handlers.BookInfo) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 66, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 64, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 67, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 65, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -723,7 +709,7 @@ func DashboardSettingsModal(sections []handlers.SectionData, hiddenCollections [ templ_7745c5c3_Var33 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 68, "

Customize Dashboard

Drag to reorder, toggle to show or hide.

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 67, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } for _, section := range sections { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 70, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 70, "\" draggable=\"true\">
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -770,120 +756,120 @@ func DashboardSettingsModal(sections []handlers.SectionData, hiddenCollections [ if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 73, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 71, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var36 string templ_7745c5c3_Var36, templ_7745c5c3_Err = templ.JoinStringErrs(section.Icon) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 234, Col: 43} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 229, Col: 43} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var36)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 74, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 72, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var37 string templ_7745c5c3_Var37, templ_7745c5c3_Err = templ.JoinStringErrs(section.Title) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 236, Col: 84} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 231, Col: 84} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var37)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 75, " ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 73, " ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if section.IsSystem { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 76, "System") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 74, "System") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 77, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 75, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if section.IsSystem { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 78, " ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 78, "\">Restore ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 81, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 81, ">
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 84, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 84, "\" class=\"w-full h-2 rounded-lg appearance-none cursor-pointer\" style=\"background-color: var(--border-strong);\" data-input-action=\"update-items-count\" target=\"items-count-display\">
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -917,12 +903,32 @@ type BookStatsData struct { FirstSession string } -// DashboardWeekWidget renders the collapsible "This Week" strip. The -// collapsed summary row reads at a glance on every screen size; the -// expanded body holds the full grid. The details element re-fetches -// itself through htmx when the body sees a `week-refresh` event -// (dispatched by the library switcher), carrying the CURRENT -// #library-select value, so the widget tracks the switched library. +// weekWidgetComponent adapts the optional week stats into the +// LibrarySwitcher's center slot. nil (stats unavailable or the stats +// service is off) = the slot stays empty, exactly as when the widget +// used to be suppressed entirely. +func weekWidgetComponent(week *WeekStatsData, activeLibType string) templ.Component { + if week == nil { + return nil + } + return DashboardWeekWidget(*week, activeLibType) +} + +// DashboardWeekWidget renders the "This Week" stats that live in the +// library bar's center slot (LibrarySwitcher's `center` component). +// The summary is a compact inline row whose numbers reveal themselves +// as width allows; the expanded body is an absolutely positioned +// popover that FLOATS over the page content — opening it never shifts +// the collections below. The details element re-fetches itself through +// htmx when the body sees a `week-refresh` event (dispatched by the +// library switcher), carrying the CURRENT #library-select value, so +// the widget tracks the switched library. Visible at every width — on +// narrow screens the summary collapses to the chart icon + chevron +// (tap target, aria-labeled; "This Week" + minutes reveal at `sm`, +// pages/sessions/books at md/lg/xl). The panel anchors to the +// BAR (full-width under it) below `sm` — the widget is too far left on +// phones for widget-centered anchoring — and centers on the widget at +// `sm` and up. func DashboardWeekWidget(week WeekStatsData, activeLibType string) templ.Component { return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context @@ -944,7 +950,7 @@ func DashboardWeekWidget(week WeekStatsData, activeLibType string) templ.Compone templ_7745c5c3_Var42 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 87, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 85, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -952,222 +958,218 @@ func DashboardWeekWidget(week WeekStatsData, activeLibType string) templ.Compone if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 88, " This Week ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 86, " ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 87, " ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 88, " ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 89, " ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 90, " ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if week.CurrentStreak > 0 { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 93, "🔥 ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 91, " ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 92, "-day streak") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 95, "Full stats") + templ_7745c5c3_Err = Icon("chevron-down", "h-4 w-4 shrink-0 transition-transform group-open:rotate-180").Render(ctx, templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = Icon("chevron-down", "h-4 w-4 transition-transform group-open:rotate-180").Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 96, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 93, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if activeLibType == "audiobooks" { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 97, "

Listening Time

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 94, "

Listening Time

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var48 string templ_7745c5c3_Var48, templ_7745c5c3_Err = templ.JoinStringErrs(fmtReadingMinutes(week.WeekMinutes)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 359, Col: 108} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 369, Col: 108} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var48)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 98, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 95, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var49 string templ_7745c5c3_Var49, templ_7745c5c3_Err = templ.JoinStringErrs(weekVsPrevLabel(week.WeekMinutes, week.PrevMinutes)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 360, Col: 122} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 370, Col: 122} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var49)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 99, "

Sessions

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 96, "

Sessions

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var50 string templ_7745c5c3_Var50, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", week.WeekSessions)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 364, Col: 109} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 374, Col: 109} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var50)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 100, "

Books Touched

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 97, "

Books Touched

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var51 string templ_7745c5c3_Var51, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", week.WeekBooks)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 368, Col: 106} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 378, Col: 106} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var51)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 101, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 98, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 102, "

Reading Time

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 99, "

Reading Time

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var52 string templ_7745c5c3_Var52, templ_7745c5c3_Err = templ.JoinStringErrs(fmtReadingMinutes(week.WeekMinutes)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 373, Col: 108} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 383, Col: 108} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var52)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 103, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 100, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var53 string templ_7745c5c3_Var53, templ_7745c5c3_Err = templ.JoinStringErrs(weekVsPrevLabel(week.WeekMinutes, week.PrevMinutes)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 374, Col: 122} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 384, Col: 122} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var53)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 104, "

Pages

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 101, "

Pages

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var54 string templ_7745c5c3_Var54, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", week.WeekPages)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 378, Col: 106} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 388, Col: 106} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var54)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 105, "

Sessions

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 102, "

Sessions

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var55 string templ_7745c5c3_Var55, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", week.WeekSessions)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 382, Col: 109} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 392, Col: 109} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var55)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 106, "

Books Touched

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 103, "

Books Touched

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var56 string templ_7745c5c3_Var56, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", week.WeekBooks)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 386, Col: 106} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 396, Col: 106} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var56)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 107, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 104, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 108, "

Synced from your reading devices ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 105, "

Synced from your reading devices ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -1176,7 +1178,7 @@ func DashboardWeekWidget(week WeekStatsData, activeLibType string) templ.Compone var templ_7745c5c3_Var57 string templ_7745c5c3_Var57, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf(" · 🔥 %d-day streak — keep it going today", week.CurrentStreak)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 394, Col: 89} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 405, Col: 90} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var57)) if templ_7745c5c3_Err != nil { @@ -1186,7 +1188,7 @@ func DashboardWeekWidget(week WeekStatsData, activeLibType string) templ.Compone var templ_7745c5c3_Var58 string templ_7745c5c3_Var58, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf(" · 🔥 %d-day streak — read today to keep it", week.CurrentStreak)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 396, Col: 91} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 407, Col: 92} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var58)) if templ_7745c5c3_Err != nil { @@ -1194,7 +1196,15 @@ func DashboardWeekWidget(week WeekStatsData, activeLibType string) templ.Compone } } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 109, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 106, "

Full stats") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = Icon("arrow-right", "h-4 w-4").Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 107, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/templates/library_switcher.templ b/templates/library_switcher.templ index 0ebdb912..27c38579 100644 --- a/templates/library_switcher.templ +++ b/templates/library_switcher.templ @@ -1,6 +1,13 @@ package templates -templ LibrarySwitcher(libData []LibraryData, currentLibraryID string, title string, actions ...templ.Component) { +// LibrarySwitcher renders the sticky subbar: title + library select on +// the left (title hidden below `sm` — phones need the row width for the +// select + actions + the dashboard's stats widget), optional action +// buttons on the right. `center` is an +// optional slot between them (nil = unused) — the dashboard passes its +// "This Week" stats widget here so it participates in the bar's +// justify-between layout as the middle group. +templ LibrarySwitcher(libData []LibraryData, currentLibraryID string, title string, center templ.Component, actions ...templ.Component) {
if title != "" { - { title } + } else { } @@ -33,6 +40,9 @@ templ LibrarySwitcher(libData []LibraryData, currentLibraryID string, title stri }
+ if center != nil { + @center + } if len(actions) > 0 {
for _, action := range actions { diff --git a/templates/library_switcher_templ.go b/templates/library_switcher_templ.go index a9b8ff18..fc7b88e8 100644 --- a/templates/library_switcher_templ.go +++ b/templates/library_switcher_templ.go @@ -8,7 +8,14 @@ package templates import "github.com/a-h/templ" import templruntime "github.com/a-h/templ/runtime" -func LibrarySwitcher(libData []LibraryData, currentLibraryID string, title string, actions ...templ.Component) templ.Component { +// LibrarySwitcher renders the sticky subbar: title + library select on +// the left (title hidden below `sm` — phones need the row width for the +// select + actions + the dashboard's stats widget), optional action +// buttons on the right. `center` is an +// optional slot between them (nil = unused) — the dashboard passes its +// "This Week" stats widget here so it participates in the bar's +// justify-between layout as the middle group. +func LibrarySwitcher(libData []LibraryData, currentLibraryID string, title string, center templ.Component, actions ...templ.Component) templ.Component { return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { @@ -34,14 +41,14 @@ func LibrarySwitcher(libData []LibraryData, currentLibraryID string, title strin return templ_7745c5c3_Err } if title != "" { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "