chore: initialize from frontend template 4dc033c
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
import {
|
||||
createContext,
|
||||
useContext,
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
type ReactNode,
|
||||
} from "react";
|
||||
|
||||
import type { ColorSchemePreference } from "../../application/ports/in/application-api.ts";
|
||||
import {
|
||||
normalizeColorSchemePreference,
|
||||
resolveColorScheme,
|
||||
} from "../../application/policies/color-scheme.ts";
|
||||
import { useApplication } from "./application-provider.tsx";
|
||||
|
||||
export type ThemeContextValue = Readonly<{
|
||||
preference: ColorSchemePreference;
|
||||
resolvedTheme: "light" | "dark";
|
||||
setPreference(preference: ColorSchemePreference): void;
|
||||
}>;
|
||||
|
||||
const ThemeContext = createContext<ThemeContextValue | null>(null);
|
||||
|
||||
function systemPrefersDark(): boolean {
|
||||
return (
|
||||
typeof window.matchMedia === "function" &&
|
||||
window.matchMedia("(prefers-color-scheme: dark)").matches
|
||||
);
|
||||
}
|
||||
|
||||
export function ThemeProvider({
|
||||
children,
|
||||
}: Readonly<{ children: ReactNode }>) {
|
||||
const { preferences } = useApplication();
|
||||
const [preference, updatePreference] = useState<ColorSchemePreference>(
|
||||
preferences.getColorScheme,
|
||||
);
|
||||
const [darkSystemTheme, setDarkSystemTheme] = useState(systemPrefersDark);
|
||||
const resolvedTheme = resolveColorScheme(preference, darkSystemTheme);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window.matchMedia !== "function") return undefined;
|
||||
const query = window.matchMedia("(prefers-color-scheme: dark)");
|
||||
const update = (event: MediaQueryListEvent) =>
|
||||
setDarkSystemTheme(event.matches);
|
||||
setDarkSystemTheme(query.matches);
|
||||
query.addEventListener("change", update);
|
||||
return () => query.removeEventListener("change", update);
|
||||
}, []);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
document.documentElement.dataset.theme = resolvedTheme;
|
||||
document.documentElement.dataset.themePreference = preference;
|
||||
document.documentElement.style.colorScheme = resolvedTheme;
|
||||
}, [preference, resolvedTheme]);
|
||||
|
||||
const value = useMemo<ThemeContextValue>(
|
||||
() =>
|
||||
Object.freeze({
|
||||
preference,
|
||||
resolvedTheme,
|
||||
setPreference(next: ColorSchemePreference) {
|
||||
const normalized = normalizeColorSchemePreference(next);
|
||||
updatePreference(normalized);
|
||||
preferences.setColorScheme(normalized);
|
||||
},
|
||||
}),
|
||||
[preference, preferences, resolvedTheme],
|
||||
);
|
||||
|
||||
return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;
|
||||
}
|
||||
|
||||
export function useTheme(): ThemeContextValue {
|
||||
const theme = useContext(ThemeContext);
|
||||
if (!theme) throw new Error("ThemeProvider is required");
|
||||
return theme;
|
||||
}
|
||||
Reference in New Issue
Block a user