Files
nexarch/web/shl/theme/themeInit.ts
patrickandClaude Sonnet 5 7a06eb4bc1 UX-02: dunkles erscheinungsbild & theming
Blockierendes Init-Skript (themeInitScript) setzt Theme-CSS-Variablen vor
dem ersten Paint, verhindert das Aufblitzen des falschen Erscheinungsbilds.
resolveInitialScheme vereinheitlicht "gespeicherte Wahl vor Systempraeferenz
vor Hell" fuer Skript und React-Anfangszustand. In beide Frontend-Layouts
(dms-browser, pdf-viewer) eingebunden.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-18 01:22:27 +02:00

46 lines
2.3 KiB
TypeScript

// Reine Funktionen ohne "use client" — bewusst NICHT in ThemeProvider.tsx,
// weil Next.js jeden Export einer "use client"-Datei als Client-Referenz
// behandelt: ein Server Component (RootLayout) kann eine solche Funktion
// dann nicht mehr direkt AUFRUFEN, um ihren Rückgabewert (den Skript-Text)
// serverseitig zu rendern — das schlug beim ersten Anlauf genau so fehl.
import { cssVariables, type ColorScheme } from "../tokens/tokens";
export const THEME_STORAGE_KEY = "nexarch-shl-theme";
function readStoredScheme(): ColorScheme | null {
if (typeof window === "undefined") return null;
try {
const stored = window.localStorage.getItem(THEME_STORAGE_KEY);
return stored === "light" || stored === "dark" ? stored : null;
} catch {
return null;
}
}
function systemPrefersDark(): boolean {
if (typeof window === "undefined" || !window.matchMedia) return false;
return window.matchMedia("(prefers-color-scheme: dark)").matches;
}
// resolveInitialScheme ist die EINE Stelle, die "gespeicherte Wahl vor
// Systempräferenz vor Hell" entscheidet (Akzeptanzkriterien 2+3) — sowohl
// das blockierende Init-Skript (themeInitScript, verhindert das Aufblitzen
// des falschen Erscheinungsbilds, Pflichtprüfung 2) als auch der
// React-Anfangszustand in ThemeProvider rufen dieselbe Logik auf, damit
// beide nie auseinanderlaufen.
export function resolveInitialScheme(): ColorScheme {
return readStoredScheme() ?? (systemPrefersDark() ? "dark" : "light");
}
// themeInitScript liefert ein Skript, das VOR dem ersten Render synchron
// im <head> ausgeführt wird (siehe RootLayout je Frontend,
// `strategy="beforeInteractive"`) — es setzt dieselben CSS-Variablen wie
// cssVariables() bereits vor dem ersten Paint, damit kein falsches
// Erscheinungsbild kurz aufblitzt, bevor React mountet.
export function themeInitScript(): string {
const varsByScheme = { light: cssVariables("light"), dark: cssVariables("dark") };
return `(function(){try{var vars=${JSON.stringify(varsByScheme)};var stored=localStorage.getItem(${JSON.stringify(
THEME_STORAGE_KEY,
)});var scheme=(stored==="light"||stored==="dark")?stored:(window.matchMedia&&window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light");var v=vars[scheme];var root=document.documentElement;for(var k in v){root.style.setProperty(k,v[k]);}root.dataset.shlTheme=scheme;}catch(e){}})();`;
}