Add dark mode toggle in the sidebar

Adds a persistent dark/light theme toggle (localStorage +
data-bs-theme on <html>) so it applies globally, including the
circuit editor route which has no visible sidebar. An inline
beforeInteractive script sets the theme before hydration to avoid a
flash of the wrong theme.

Bootstrap components pick up data-bs-theme automatically; the custom
circuit-grid CSS previously used ~150 hardcoded hex colors, all
converted to semantic tokens in globals.css with light/dark values.

Verified with a headless-browser pass across the project list, project
detail page, circuit editor, project-device drawer and a settings
modal in both themes; no console errors.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Julian Appel 2026-08-06 21:56:21 +02:00
parent a99980c47b
commit 64ccd1f829
4 changed files with 395 additions and 127 deletions

View file

@ -1,4 +1,5 @@
import type { Metadata } from "next";
import Script from "next/script";
import "bootstrap/dist/css/bootstrap.min.css";
import "./globals.css";
import { AppShell } from "../frontend/components/AppShell";
@ -8,10 +9,33 @@ export const metadata: Metadata = {
description: "Leistungsbilanz für elektrische Verbraucher und Stromkreislisten",
};
// Keep this key literal in sync with THEME_STORAGE_KEY in ThemeToggle.tsx.
// It must stay inline (not imported) so it runs before hydration and never
// flashes the wrong theme on load.
const THEME_INIT_SCRIPT = `
(function () {
try {
var stored = localStorage.getItem("leistungsbilanz:theme");
var theme =
stored === "dark" || stored === "light"
? stored
: window.matchMedia("(prefers-color-scheme: dark)").matches
? "dark"
: "light";
document.documentElement.setAttribute("data-bs-theme", theme);
} catch (error) {
// Storage/matchMedia unavailable: fall back to the default light theme.
}
})();
`;
export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) {
return (
<html lang="de">
<html lang="de" suppressHydrationWarning>
<body>
<Script id="theme-init" strategy="beforeInteractive">
{THEME_INIT_SCRIPT}
</Script>
<AppShell>{children}</AppShell>
</body>
</html>