feat(decnet_web/Layout): topbar dark/light toggle with circular reveal
User-facing theme toggle ships now that the design system has been audited end-to-end. A Sun/Moon button lives between the threat indicator and the SYSTEM status pill in the topbar — same slim 28x28 voice as the rest of the topbar controls, no chrome shouting at the user. Click coords drive a View Transitions API circle clip-path that grows from the cursor to the farthest viewport corner over 520ms with the project's standard --ease curve. Browsers without startViewTransition (older Firefox, Safari < 18) fall through to an unanimated swap — the hook returns instantly in that case. Persistence is two-tier: - localStorage decnet_theme — the user's saved preference, the thing the topbar toggle writes. Survives reloads, applies everywhere. - sessionStorage decnet_theme_lab — dev-mode lab override (Task 3). Tab-scoped, wins on boot so devs can A/B without nuking the saved preference. App.tsx hydrates both on first mount in the right order so the correct theme is on <html> before the first paint. useThemeToggle is a small hook in lib/ rather than a Layout-only helper so the same toggle can be reused later from a settings page or hotkey.
This commit is contained in:
@@ -183,16 +183,22 @@ function App() {
|
||||
} catch { /* fall through to default */ }
|
||||
document.documentElement.setAttribute('data-accent', accent);
|
||||
|
||||
/* Lab theme persists in sessionStorage so a tab reload keeps the
|
||||
* dev's chosen theme without leaking to other tabs or users. The
|
||||
* production user-facing toggle (localStorage `decnet_theme`)
|
||||
* arrives with the Config-page setting in a later task. */
|
||||
/* Theme hydration order on boot:
|
||||
* 1. localStorage `decnet_theme` — the saved user preference
|
||||
* from the topbar Sun/Moon toggle. Default = 'dark'.
|
||||
* 2. sessionStorage `decnet_theme_lab` — dev-mode lab override
|
||||
* (set from /theme-lab). Tab-scoped, wins on top so devs
|
||||
* can A/B without clobbering their saved preference. */
|
||||
let theme: 'dark' | 'light' = 'dark';
|
||||
try {
|
||||
const labTheme = sessionStorage.getItem('decnet_theme_lab');
|
||||
if (labTheme === 'light' || labTheme === 'dark') {
|
||||
document.documentElement.setAttribute('data-theme', labTheme);
|
||||
}
|
||||
const saved = localStorage.getItem('decnet_theme');
|
||||
if (saved === 'light' || saved === 'dark') theme = saved;
|
||||
} catch { /* ignore */ }
|
||||
try {
|
||||
const lab = sessionStorage.getItem('decnet_theme_lab');
|
||||
if (lab === 'light' || lab === 'dark') theme = lab;
|
||||
} catch { /* ignore */ }
|
||||
document.documentElement.setAttribute('data-theme', theme);
|
||||
}, []);
|
||||
|
||||
const handleLogin = (newToken: string) => setToken(newToken);
|
||||
|
||||
Reference in New Issue
Block a user