Files
fotospiel-app/resources/js/hooks/use-appearance.ts
2025-12-11 12:18:08 +01:00

84 lines
2.4 KiB
TypeScript

import React, { createContext, useContext, useEffect, useMemo, useState } from 'react';
export type Appearance = 'light' | 'dark' | 'system';
type AppearanceContextValue = {
appearance: Appearance;
resolved: 'light' | 'dark';
updateAppearance: (mode: Appearance) => void;
};
const AppearanceContext = createContext<AppearanceContextValue>({
appearance: 'system',
resolved: 'light',
updateAppearance: () => {},
});
function resolveTheme(mode: Appearance): 'light' | 'dark' {
if (mode === 'dark') return 'dark';
if (mode === 'light') return 'light';
return window.matchMedia?.('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
}
function applyDocumentClass(theme: 'light' | 'dark') {
if (theme === 'dark') {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
}
export function AppearanceProvider({ children }: { children: React.ReactNode }) {
const [appearance, setAppearance] = useState<Appearance>(() => {
const stored = localStorage.getItem('theme') as Appearance | null;
return stored ?? 'system';
});
const [resolved, setResolved] = useState<'light' | 'dark'>(() => resolveTheme(appearance));
useEffect(() => {
const nextResolved = resolveTheme(appearance);
setResolved(nextResolved);
applyDocumentClass(nextResolved);
if (appearance === 'system') {
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
const listener = () => {
const resolvedTheme = mediaQuery.matches ? 'dark' : 'light';
setResolved(resolvedTheme);
applyDocumentClass(resolvedTheme);
};
mediaQuery.addEventListener('change', listener);
return () => mediaQuery.removeEventListener('change', listener);
}
return undefined;
}, [appearance]);
const updateAppearance = (mode: Appearance) => {
setAppearance(mode);
localStorage.setItem('theme', mode);
};
const value = useMemo(
() => ({
appearance,
resolved,
updateAppearance,
}),
[appearance, resolved]
);
return React.createElement(AppearanceContext.Provider, { value }, children);
}
export function useAppearance(): AppearanceContextValue {
return useContext(AppearanceContext);
}
export function initializeTheme() {
const stored = localStorage.getItem('theme') as Appearance | null;
const mode = stored ?? 'system';
const resolved = resolveTheme(mode);
applyDocumentClass(resolved);
}