Files
frontend-helper/src/lib/use-theme.ts
T

45 lines
1.6 KiB
TypeScript

"use client";
import { useCallback, useSyncExternalStore } from "react";
import { applyTheme, currentTheme, DEFAULT_THEME, THEME_KEY, type Theme } from "./theme";
const listeners = new Set<() => void>();
function subscribe(listener: () => void): () => void {
listeners.add(listener);
const onStorage = (event: StorageEvent) => {
// Another tab changed the theme: follow it.
if (event.key !== THEME_KEY) return;
const next = event.newValue === "light" || event.newValue === "dark" ? event.newValue : DEFAULT_THEME;
applyTheme(next, document.documentElement, null);
listeners.forEach((l) => l());
};
window.addEventListener("storage", onStorage);
return () => {
listeners.delete(listener);
window.removeEventListener("storage", onStorage);
};
}
const getSnapshot = (): Theme => currentTheme(document.documentElement);
const getServerSnapshot = (): Theme => DEFAULT_THEME;
function safeStorage(): Storage | null {
try {
return window.localStorage;
} catch {
return null;
}
}
/** Shared theme state for every toggle: the current theme and a setter. */
export function useTheme(): { theme: Theme; setTheme: (theme: Theme) => void; toggleTheme: () => void } {
const theme = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
const setTheme = useCallback((next: Theme) => {
applyTheme(next, document.documentElement, safeStorage());
listeners.forEach((l) => l());
}, []);
const toggleTheme = useCallback(() => setTheme(getSnapshot() === "dark" ? "light" : "dark"), [setTheme]);
return { theme, setTheme, toggleTheme };
}