fix: gate OnAfterRenderAsync body-class JS call; hoist dark popover token

Only stamps body class on firstRender or _isDarkMode change; adds base call.
Hoists duplicate dark popover mix value to --deepdrft-popover-surface-dark in :root;
both .deepdrft-theme-dark and body.deepdrft-theme-dark reference it via var().
This commit is contained in:
daniel-c-harvey
2026-06-20 00:21:53 -04:00
parent b5106d090f
commit 30999b038c
2 changed files with 22 additions and 12 deletions
+11 -2
View File
@@ -42,6 +42,7 @@
private string _audioPlayerClass = "minimized";
private const string DarkModeKey = "darkMode";
private bool _isDarkMode = false;
private bool? _lastAppliedDarkMode = null;
private PersistingComponentStateSubscription _persistingSubscription;
[Inject] public required PersistentComponentState PersistentState { get; set; }
@@ -70,10 +71,18 @@
// Sync dark mode class on <body> so portaled MudBlazor elements (popovers, menus, selects)
// inherit --deepdrft-popover-surface from body.deepdrft-theme-dark rather than from :root only.
// Popovers portal outside the ThemeWrapperClass div, so only a body-level class can reach them.
// Gated: only fires on first render or when _isDarkMode actually changes, to avoid redundant
// JS calls on unrelated re-renders (e.g. audio player minimize/expand).
protected override async Task OnAfterRenderAsync(bool firstRender)
{
await JS.InvokeVoidAsync("eval",
$"document.body.classList.toggle('deepdrft-theme-dark', {_isDarkMode.ToString().ToLower()})");
await base.OnAfterRenderAsync(firstRender);
if (firstRender || _isDarkMode != _lastAppliedDarkMode)
{
_lastAppliedDarkMode = _isDarkMode;
await JS.InvokeVoidAsync("eval",
$"document.body.classList.toggle('deepdrft-theme-dark', {_isDarkMode.ToString().ToLower()})");
}
}
// Theme wrapper class for CSS targeting
@@ -87,10 +87,12 @@
/* Popover surface (Phase 18). Default MudBlazor popovers (selects/menus/tooltips/share
body) bind this. Light uses a very subtle navy wash (4%) — near the page background but
just perceptibly off-white so the popover reads as an elevated surface. Dark uses a
bluer navy (colour-mix of navy-mid + green-accent at 20%), defined in the
body.deepdrft-theme-dark block below so it reaches portaled popover content (popovers
portal to <body>, outside the .deepdrft-theme-dark wrapper div). Bespoke dark-glass
panels (visualizer/queue/privacy) do NOT bind this — they keep --deepdrft-panel-ground. */
bluer navy (colour-mix of navy-mid + green-accent at 20%), defined once in
--deepdrft-popover-surface-dark below and referenced by both the .deepdrft-theme-dark
wrapper block and the body.deepdrft-theme-dark block so portaled popover content (which
portals to <body>, outside the wrapper div) is also reached. Bespoke dark-glass panels
(visualizer/queue/privacy) do NOT bind this — they keep --deepdrft-panel-ground. */
--deepdrft-popover-surface-dark: color-mix(in srgb, var(--deepdrft-navy-mid) 80%, var(--deepdrft-green-accent) 20%);
--deepdrft-popover-surface: color-mix(in srgb, var(--deepdrft-navy) 4%, var(--deepdrft-white));
/* Fixed-nav height — single source of truth shared by the frosted-glass nav
@@ -167,17 +169,16 @@
--deepdrft-play-chip-soft: color-mix(in srgb, var(--deepdrft-green-accent) 30%, transparent);
/* Popover surface (Phase 18) — within .deepdrft-theme-dark wrapper this value applies to
non-portaled elements only. Portaled MudBlazor popovers live at <body> level; the
body.deepdrft-theme-dark block below is the authoritative dark value for those. Keep
this in sync with that block for non-portaled surfaces (drawers, inline menus). */
--deepdrft-popover-surface: color-mix(in srgb, var(--deepdrft-navy-mid) 80%, var(--deepdrft-green-accent) 20%);
non-portaled elements only (drawers, inline menus). Portaled MudBlazor popovers live at
<body> level; the body.deepdrft-theme-dark block below uses the same source token. */
--deepdrft-popover-surface: var(--deepdrft-popover-surface-dark);
}
/* Portal-scope dark popover surface. MudBlazor popovers (selects, menus, share body) portal
to <body>, placing them outside the .deepdrft-theme-dark wrapper div. MainLayout.razor syncs
deepdrft-theme-dark onto <body> via JS after each render, so this selector reaches portaled
content. The value mirrors the .deepdrft-theme-dark block above — bluer navy
content. Resolved from --deepdrft-popover-surface-dark (defined in :root above) — bluer navy
(navy-mid + 20% green-accent tint) rather than the pure charcoal #162437. */
body.deepdrft-theme-dark {
--deepdrft-popover-surface: color-mix(in srgb, var(--deepdrft-navy-mid) 80%, var(--deepdrft-green-accent) 20%);
--deepdrft-popover-surface: var(--deepdrft-popover-surface-dark);
}