Files
Bookra/apps/frontend/src/providers/theme-provider.tsx
T
Tomas Dvorak 48c3e15a38 cleanup
2026-05-05 09:48:07 +02:00

89 lines
2.2 KiB
TypeScript

import {
createContext,
createEffect,
createSignal,
ParentComponent,
useContext,
} from "solid-js";
type Theme = "light" | "dark" | "system";
const STORAGE_KEY = "bookra-theme";
function getInitialTheme(): Theme {
if (typeof window === "undefined") return "system";
const stored = localStorage.getItem(STORAGE_KEY) as Theme | null;
return stored ?? "system";
}
function getResolvedTheme(theme: Theme): "light" | "dark" {
if (theme === "system") {
return window.matchMedia("(prefers-color-scheme: dark)").matches
? "dark"
: "light";
}
return theme;
}
type ThemeContextValue = {
theme: () => Theme;
resolvedTheme: () => "light" | "dark";
setTheme: (theme: Theme) => void;
toggle: () => void;
};
const ThemeContext = createContext<ThemeContextValue>();
export const ThemeProvider: ParentComponent = (props) => {
const [theme, setThemeSignal] = createSignal<Theme>(getInitialTheme());
const [resolvedTheme, setResolvedTheme] = createSignal<"light" | "dark">(
getResolvedTheme(getInitialTheme())
);
const applyTheme = (t: Theme) => {
const resolved = getResolvedTheme(t);
setResolvedTheme(resolved);
const root = document.documentElement;
root.classList.remove("light", "dark");
root.classList.add(resolved);
root.setAttribute("data-theme", resolved);
};
createEffect(() => {
const t = theme();
localStorage.setItem(STORAGE_KEY, t);
applyTheme(t);
});
if (typeof window !== "undefined") {
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
mediaQuery.addEventListener("change", () => {
if (theme() === "system") {
applyTheme("system");
}
});
}
const setTheme = (t: Theme) => setThemeSignal(t);
const toggle = () => {
const current = resolvedTheme();
setTheme(current === "light" ? "dark" : "light");
};
return (
<ThemeContext.Provider value={{ theme, resolvedTheme, setTheme, toggle }}>
{props.children}
</ThemeContext.Provider>
);
};
export function useTheme() {
const context = useContext(ThemeContext);
if (!context) {
throw new Error("ThemeProvider is missing from the component tree.");
}
return context;
}