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:
co-authored by
Claude Sonnet 4.6
parent
6710c8b231
commit
8cc2ef0732
@@ -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 };
|
||||
}
|
||||
Reference in New Issue
Block a user