Add theming infrastructure (task 08)

CSS-variable multi-theme system (default + warm) × {light, dark, system}; per-user theme/theme_mode columns; no-flash pre-paint script; useTheme hook + ThemePicker component on /settings.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
ginnoir
2026-05-06 02:50:05 -05:00
co-authored by Claude Sonnet 4.6
parent 6710c8b231
commit 8cc2ef0732
12 changed files with 540 additions and 3 deletions
+50
View File
@@ -0,0 +1,50 @@
"use client";
import { useCallback, useState } from "react";
import type { ThemeId, ThemeMode } from "@/modules/_core/themes";
import { setUserTheme } from "@/app/settings/actions";
function applyTheme(theme: ThemeId, mode: ThemeMode) {
const html = document.documentElement;
html.setAttribute("data-theme", theme);
const dark =
mode === "dark" ||
(mode === "system" &&
window.matchMedia("(prefers-color-scheme: dark)").matches);
html.classList.toggle("dark", dark);
try {
localStorage.setItem("theme", theme);
localStorage.setItem("themeMode", mode);
} catch {
// storage blocked
}
}
export function useTheme(
initialTheme: ThemeId = "default",
initialMode: ThemeMode = "system",
signedIn = false,
) {
const [theme, setThemeState] = useState<ThemeId>(initialTheme);
const [mode, setModeState] = useState<ThemeMode>(initialMode);
const setTheme = useCallback(
(next: ThemeId) => {
setThemeState(next);
applyTheme(next, mode);
if (signedIn) void setUserTheme({ theme: next, mode });
},
[mode, signedIn],
);
const setMode = useCallback(
(next: ThemeMode) => {
setModeState(next);
applyTheme(theme, next);
if (signedIn) void setUserTheme({ theme, mode: next });
},
[theme, signedIn],
);
return { theme, mode, setTheme, setMode };
}