diff --git a/docs/tables_frontend_overhaul_implementation_plan.md b/docs/tables_frontend_overhaul_implementation_plan.md index 72cb7e8..87ca5e5 100644 --- a/docs/tables_frontend_overhaul_implementation_plan.md +++ b/docs/tables_frontend_overhaul_implementation_plan.md @@ -43,6 +43,7 @@ It is intentionally implementation-focused: | 2026-03-21 | P1.3 | Completed | Added explicit light, dark, and system theme modes in the token layer and introduced a scoped `ThemeState` service for later shell controls. | | 2026-03-21 | P1.4 | Completed | Added shared browser-storage wrappers, persisted theme mode in `localStorage`, and initialize/apply theme state from the layout on interactive render. | | 2026-03-21 | P1.5 | Completed | Replaced the permanent sidebar layout with a sticky top app shell and mobile bottom navigation backed by dedicated shell components. | +| 2026-03-21 | P1.6 | Completed | Added explicit shell slots for nav, omnibox, shortcuts, and utilities; switched shell navigation to `Play`, `Tables`, `Curation`, and `Tools`; and wired the first live theme control into the shell. | ### Lessons Learned @@ -55,6 +56,7 @@ It is intentionally implementation-focused: - Theme mode selection can be prepared independently of persistence. Splitting those concerns keeps the theme CSS and the storage wiring reviewable. - Shared UI state events in Blazor should stay synchronous unless the event contract is async-aware. Layout refresh can be triggered safely with `InvokeAsync(StateHasChanged)` from a synchronous handler. - Extracting shell markup into dedicated components is lower-risk than continuing to evolve `MainLayout.razor` directly. It isolates responsive frame work from page content and keeps later nav changes localized. +- Once a Razor component exposes multiple named `RenderFragment` parameters, the page body must be passed explicitly through ``. That pattern is now the baseline for shell composition here. ## Target Outcomes @@ -261,7 +263,7 @@ Create the implementation foundation so the visual overhaul does not start with | `P1.3` | Completed | Explicit light, dark, and system modes now exist in CSS, backed by a scoped `ThemeState` service. | | `P1.4` | Completed | Theme mode now persists through a shared storage service and is applied from the layout during interactive startup. | | `P1.5` | Completed | The sidebar is gone; pages now render inside a sticky top-shell with a mobile bottom nav. | -| `P1.6` | Pending | Shell slots and nav utilities depend on the new shell. | +| `P1.6` | Completed | The shell now has explicit nav, omnibox, shortcut, and utility slots, plus a live theme selector and destination-model navigation. | | `P1.7` | Pending | Skip link and landmark work will land with the shell markup. | | `P1.8` | Pending | Tools-specific shell emphasis is final Phase 1 polish. | diff --git a/src/RolemasterDb.App/Components/Layout/MainLayout.razor b/src/RolemasterDb.App/Components/Layout/MainLayout.razor index 7c88ccb..10fc78c 100644 --- a/src/RolemasterDb.App/Components/Layout/MainLayout.razor +++ b/src/RolemasterDb.App/Components/Layout/MainLayout.razor @@ -3,7 +3,15 @@ @inject RolemasterDb.App.Frontend.AppState.ThemeState ThemeState - @Body + + + + + + + + @Body +
diff --git a/src/RolemasterDb.App/Components/Pages/Curation.razor b/src/RolemasterDb.App/Components/Pages/Curation.razor new file mode 100644 index 0000000..479030c --- /dev/null +++ b/src/RolemasterDb.App/Components/Pages/Curation.razor @@ -0,0 +1,8 @@ +@page "/curation" + +Curation + +
+

Curation

+

The dedicated queue-first curation workflow lands in Phase 4. The shell route exists now so navigation can move to the target product model before the workflow rewrite begins.

+
diff --git a/src/RolemasterDb.App/Components/Pages/Tools.razor b/src/RolemasterDb.App/Components/Pages/Tools.razor new file mode 100644 index 0000000..47b14c8 --- /dev/null +++ b/src/RolemasterDb.App/Components/Pages/Tools.razor @@ -0,0 +1,13 @@ +@page "/tools" + +Tools + +
+

Tools

+

Diagnostics and API documentation move under this destination in later phases. The landing page is in place now so the shell can navigate with the target destination model.

+ +
+ Open diagnostics + Open API docs +
+
diff --git a/src/RolemasterDb.App/Components/Shell/AppShell.razor b/src/RolemasterDb.App/Components/Shell/AppShell.razor index 73f399d..289eea0 100644 --- a/src/RolemasterDb.App/Components/Shell/AppShell.razor +++ b/src/RolemasterDb.App/Components/Shell/AppShell.razor @@ -2,23 +2,51 @@
- + @if (PrimaryNavContent is null) + { + + } + else + { + @PrimaryNavContent + } +
+ +
+ @if (OmniboxContent is not null) + { + @OmniboxContent + }
- @HeaderActions + @UtilityContent
+ + @if (ShortcutContent is not null) + { +
+ @ShortcutContent +
+ }
@@ -37,5 +65,17 @@ public RenderFragment? ChildContent { get; set; } [Parameter] - public RenderFragment? HeaderActions { get; set; } + public RenderFragment? BrandContent { get; set; } + + [Parameter] + public RenderFragment? PrimaryNavContent { get; set; } + + [Parameter] + public RenderFragment? OmniboxContent { get; set; } + + [Parameter] + public RenderFragment? ShortcutContent { get; set; } + + [Parameter] + public RenderFragment? UtilityContent { get; set; } } diff --git a/src/RolemasterDb.App/Components/Shell/AppShell.razor.css b/src/RolemasterDb.App/Components/Shell/AppShell.razor.css index edeea16..5efebb0 100644 --- a/src/RolemasterDb.App/Components/Shell/AppShell.razor.css +++ b/src/RolemasterDb.App/Components/Shell/AppShell.razor.css @@ -13,7 +13,7 @@ .app-shell-bar { display: grid; - grid-template-columns: auto minmax(0, 1fr) auto; + grid-template-columns: auto minmax(0, 1fr) minmax(14rem, 20rem) auto; align-items: center; gap: 1rem; padding: 0.75rem 1rem; @@ -73,6 +73,10 @@ min-width: 0; } +.app-shell-header-omnibox { + min-width: 0; +} + .app-shell-header-actions { display: flex; align-items: center; @@ -81,6 +85,10 @@ min-height: 2.75rem; } +.app-shell-shortcuts { + margin-top: 0.75rem; +} + .app-shell-main { flex: 1 1 auto; min-width: 0; @@ -108,7 +116,7 @@ } .app-shell-bar { - grid-template-columns: minmax(0, 1fr) auto; + grid-template-columns: minmax(0, 1fr) auto auto; gap: 0.75rem; padding: 0.7rem 0.85rem; } @@ -120,6 +128,10 @@ .app-shell-header-nav { display: none; } + + .app-shell-header-actions { + gap: 0.5rem; + } } @media (min-width: 768px) { @@ -131,3 +143,13 @@ display: none; } } + +@media (max-width: 1023.98px) { + .app-shell-bar { + grid-template-columns: auto minmax(0, 1fr) auto; + } + + .app-shell-header-nav { + display: none; + } +} diff --git a/src/RolemasterDb.App/Components/Shell/ShellOmniboxTrigger.razor b/src/RolemasterDb.App/Components/Shell/ShellOmniboxTrigger.razor new file mode 100644 index 0000000..4fe555f --- /dev/null +++ b/src/RolemasterDb.App/Components/Shell/ShellOmniboxTrigger.razor @@ -0,0 +1,3 @@ + diff --git a/src/RolemasterDb.App/Components/Shell/ShellOmniboxTrigger.razor.css b/src/RolemasterDb.App/Components/Shell/ShellOmniboxTrigger.razor.css new file mode 100644 index 0000000..54274af --- /dev/null +++ b/src/RolemasterDb.App/Components/Shell/ShellOmniboxTrigger.razor.css @@ -0,0 +1,19 @@ +.shell-omnibox-trigger { + width: 100%; + display: inline-flex; + align-items: center; + justify-content: flex-start; + min-height: 2.75rem; + padding: 0.65rem 0.9rem; + border: 1px solid var(--border-default); + border-radius: 999px; + background: color-mix(in srgb, var(--surface-2) 84%, transparent); + color: var(--text-secondary); + box-sizing: border-box; +} + +.shell-omnibox-trigger-label { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} diff --git a/src/RolemasterDb.App/Components/Shell/ShellPrimaryNav.razor b/src/RolemasterDb.App/Components/Shell/ShellPrimaryNav.razor index eeeb87b..a87f038 100644 --- a/src/RolemasterDb.App/Components/Shell/ShellPrimaryNav.razor +++ b/src/RolemasterDb.App/Components/Shell/ShellPrimaryNav.razor @@ -1,18 +1,18 @@
- Lookup Desk + Play - Critical Tables + Tables - - API Surface + + Curation - - Diagnostics + + Tools
diff --git a/src/RolemasterDb.App/Components/Shell/ShellThemeSwitcher.razor b/src/RolemasterDb.App/Components/Shell/ShellThemeSwitcher.razor new file mode 100644 index 0000000..62fd485 --- /dev/null +++ b/src/RolemasterDb.App/Components/Shell/ShellThemeSwitcher.razor @@ -0,0 +1,48 @@ +@implements IDisposable +@inject RolemasterDb.App.Frontend.AppState.ThemeState ThemeState + + + +@code { + private string CurrentValue => + ThemeState.CurrentMode switch + { + RolemasterDb.App.Frontend.AppState.ThemeMode.Light => "light", + RolemasterDb.App.Frontend.AppState.ThemeMode.Dark => "dark", + _ => "system" + }; + + protected override void OnInitialized() + { + ThemeState.Changed += HandleThemeChanged; + } + + private async Task HandleChanged(ChangeEventArgs args) + { + var nextMode = args.Value?.ToString() switch + { + "light" => RolemasterDb.App.Frontend.AppState.ThemeMode.Light, + "dark" => RolemasterDb.App.Frontend.AppState.ThemeMode.Dark, + _ => RolemasterDb.App.Frontend.AppState.ThemeMode.System + }; + + await ThemeState.SetModeAsync(nextMode); + } + + private void HandleThemeChanged() + { + _ = InvokeAsync(StateHasChanged); + } + + public void Dispose() + { + ThemeState.Changed -= HandleThemeChanged; + } +} diff --git a/src/RolemasterDb.App/Components/Shell/ShellThemeSwitcher.razor.css b/src/RolemasterDb.App/Components/Shell/ShellThemeSwitcher.razor.css new file mode 100644 index 0000000..aa09ed6 --- /dev/null +++ b/src/RolemasterDb.App/Components/Shell/ShellThemeSwitcher.razor.css @@ -0,0 +1,27 @@ +.shell-theme-switcher { + display: inline-flex; + align-items: center; + gap: 0.6rem; + color: var(--text-secondary); +} + +.shell-theme-switcher-label { + font-size: 0.82rem; + white-space: nowrap; +} + +.shell-theme-switcher-select { + min-width: 7.5rem; + min-height: 2.75rem; + padding-block: 0.55rem; +} + +@media (max-width: 767.98px) { + .shell-theme-switcher-label { + display: none; + } + + .shell-theme-switcher-select { + min-width: 5.75rem; + } +}