WFDASH-WEBAPP/src/components/theme-bootstrap.tsx
Dave 9ce6c97772 feat(theme): the picker reads the real palette, not a hand-kept copy
The theme registry carried a hand-maintained set of hex swatches for
every palette, and they had drifted away from the CSS the app actually
paints. The picker previewed colours the product never showed.

The registry now holds only what cannot be read from CSS — id, label,
tagline, fonts, radius — and the picker, the Settings swatches and the
preview cards resolve --background, --primary and --foreground off the
theme itself. A preview can no longer disagree with the screen it
previews.

themes.css is rewritten so every palette carries a real dark surface,
and the palettes that are dark by nature stay dark whichever way the
mode switch is set. The pre-hydration script validates the stored theme
id against the registry, so a retired id falls back to WFDASH instead of
painting an unstyled page, and the old "warm" mode migrates to Light.

The legacy text-{warn,success,info}-foreground classes now resolve to
the semantic text colour and switch to the filled ink only on an exact
solid fill, so one class reads correctly on both a tinted badge and a
filled button. A solid focus outline replaces the faint ring, and the
charts and quote status badges take their colours from tokens rather
than hard-coded dark strokes.

Tests: theme-bootstrap.test.tsx covers saved-theme recovery and the
storage-blocked path; e2e/themes.spec.ts walks every palette in both
modes checking surface contrast, tinted and solid semantic pairs, focus
colour, chart tokens and the destructive hover, and asserts that the
picker preview matches the applied theme.
2026-11-05 00:40:40 +02:00

31 lines
1.2 KiB
TypeScript

import { DEFAULT_THEME_ID, THEMES } from "@/lib/themes";
/**
* Inline script that runs in <head> before hydration.
*
* Reads the persisted theme id from localStorage and applies it to
* <html> so the user never sees a flash of the default theme.
*
* The emitted script is self-contained; its allowlist comes from the registry.
*/
export function ThemeBootstrapScript() {
const code = `
(function() {
try {
var validIds = ${JSON.stringify(THEMES.map((t) => t.id))};
var themeId = localStorage.getItem('wfdash-theme');
if (validIds.indexOf(themeId) === -1) themeId = ${JSON.stringify(DEFAULT_THEME_ID)};
var root = document.documentElement;
root.setAttribute('data-theme', themeId);
// Older versions stored "warm". Migrate before next-themes reads it.
var mode = localStorage.getItem('wfdash-mode');
if (mode && mode !== 'light' && mode !== 'dark') {
localStorage.setItem('wfdash-mode', 'light');
}
} catch (e) {
// localStorage may be blocked — fall back to the SSR-rendered theme
}
})();
`;
return <script dangerouslySetInnerHTML={{ __html: code }} />;
}