swarm-ui: derive theme override from colors.css instead of duplicating hex values
mara's review: the theme override read as a hand-copied duplicate of colors.css's hex values instead of deriving from it. Restructured colors.css to declare each palette's 16 hex values exactly once (--mocha-baseNN, --latte-baseNN) and have every activation block (the default, the prefers-color-scheme media query, and two new :root[data-theme='light'|'dark'] blocks) just re-point the active --baseNN slot at one of those two raw palettes via var() - never a second copy of a hex value. theme-apply.ts simplifies to match: it now only toggles a data-theme attribute on <html>, same shape motion-apply.ts already had. No palette values live in JS at all anymore. Re-verified the override still genuinely outranks the media query with the new mechanism: same seed-localStorage-while-forcing-the- opposite-OS-preference test as before, both directions still render the stored override correctly.
This commit is contained in:
parent
a08aacfdf6
commit
afe627e0a9
3 changed files with 200 additions and 163 deletions
|
|
@ -1,22 +1,13 @@
|
|||
// Applies the stored theme override (see `settings-storage.ts`) by
|
||||
// setting the 16 base16 custom properties inline on `<html>` when the
|
||||
// override isn't "system", and clearing them (letting the
|
||||
// `prefers-color-scheme` media query in `@hive/shared/colors.css`
|
||||
// resume control) when it is. An inline style on an element always
|
||||
// outranks a stylesheet rule regardless of media query or specificity —
|
||||
// colors.css's own comment on its light-mode block already names this
|
||||
// as the intended mechanism for a future per-user override, so this
|
||||
// isn't a new architectural decision, just the promised implementation.
|
||||
//
|
||||
// The two palettes below are a deliberate, commented duplication of
|
||||
// colors.css's two `:root` blocks, not a second source of truth for
|
||||
// them — colors.css stays authoritative, this only exists because
|
||||
// *outranking* a stylesheet rule needs the values inline, and CSS has
|
||||
// no "read the media-query block's own values" primitive to borrow
|
||||
// from instead. Both palettes are tiny (16 hex values each) and change
|
||||
// rarely, so hand-keeping them in sync is a reasonable cost next to the
|
||||
// alternative (restructuring colors.css's cascade to avoid the
|
||||
// duplication) for a "keep it small" first cut.
|
||||
// setting `data-theme="light"|"dark"` on `<html>`, cleared when the
|
||||
// override is "system" (letting the `prefers-color-scheme` media
|
||||
// query in `@hive/shared/colors.css` resume control). No palette
|
||||
// values live here — `colors.css`'s own `:root[data-theme='light']` /
|
||||
// `:root[data-theme='dark']` blocks are what actually apply the
|
||||
// colours, by re-pointing at the same `--mocha-baseNN` /
|
||||
// `--latte-baseNN` custom properties its media-query block already
|
||||
// uses. This file only ever toggles the attribute; the values come
|
||||
// from colors.css, not a duplicate copy here.
|
||||
import { useEffect } from 'preact/hooks';
|
||||
import { useLocalSetting } from './settings-storage.js';
|
||||
|
||||
|
|
@ -28,46 +19,6 @@ export function useThemeOverride() {
|
|||
return useLocalSetting<ThemeOverride>(THEME_OVERRIDE_KEY, 'system');
|
||||
}
|
||||
|
||||
// Keep in sync with `frontend/packages/shared/src/colors.css`'s two
|
||||
// `:root` blocks by hand — see the module comment above for why this
|
||||
// duplication exists instead of a shared source.
|
||||
const DARK: Record<string, string> = {
|
||||
'--base00': '#1e1e2e',
|
||||
'--base01': '#181825',
|
||||
'--base02': '#313244',
|
||||
'--base03': '#45475a',
|
||||
'--base04': '#585b70',
|
||||
'--base05': '#cdd6f4',
|
||||
'--base06': '#f5e0dc',
|
||||
'--base07': '#b4befe',
|
||||
'--base08': '#f38ba8',
|
||||
'--base09': '#fab387',
|
||||
'--base0A': '#f9e2af',
|
||||
'--base0B': '#a6e3a1',
|
||||
'--base0C': '#89dceb',
|
||||
'--base0D': '#89b4fa',
|
||||
'--base0E': '#cba6f7',
|
||||
'--base0F': '#f5c2e7',
|
||||
};
|
||||
const LIGHT: Record<string, string> = {
|
||||
'--base00': '#eff1f5',
|
||||
'--base01': '#e6e9ef',
|
||||
'--base02': '#ccd0da',
|
||||
'--base03': '#bcc0cc',
|
||||
'--base04': '#acb0be',
|
||||
'--base05': '#4c4f69',
|
||||
'--base06': '#dc8a78',
|
||||
'--base07': '#7287fd',
|
||||
'--base08': '#9c0b2a',
|
||||
'--base09': '#883201',
|
||||
'--base0A': '#6d450e',
|
||||
'--base0B': '#235818',
|
||||
'--base0C': '#025374',
|
||||
'--base0D': '#0843b8',
|
||||
'--base0E': '#6311ce',
|
||||
'--base0F': '#8f166e',
|
||||
};
|
||||
|
||||
// Mounted once, high in the tree (`Shell`) — every route renders
|
||||
// through one `<Shell>`, so one mount point applies the override
|
||||
// regardless of which page is showing, and `useLocalSetting`'s
|
||||
|
|
@ -78,11 +29,10 @@ export function useApplyThemeOverride(): void {
|
|||
const [override] = useThemeOverride();
|
||||
useEffect(() => {
|
||||
const root = document.documentElement;
|
||||
const palette = override === 'light' ? LIGHT : override === 'dark' ? DARK : null;
|
||||
if (!palette) {
|
||||
for (const slot of Object.keys(DARK)) root.style.removeProperty(slot);
|
||||
return;
|
||||
if (override === 'system') {
|
||||
delete root.dataset.theme;
|
||||
} else {
|
||||
root.dataset.theme = override;
|
||||
}
|
||||
for (const [slot, hex] of Object.entries(palette)) root.style.setProperty(slot, hex);
|
||||
}, [override]);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue