mirror of
https://github.com/block/buzz.git
synced 2026-11-06 01:11:33 +00:00
Signed-off-by: Fizz <8a675edd33677aa0389f6650d467b2041fb0df4ca820eacb009babb95e3715d4@sprout-oss.stage.blox.sqprod.co> Co-authored-by: npub13fn4ahfnvaa2qwylvegdgeajqs0mph6v4qsw4jcqnw4mjh3hzh2quuucm5 <8a675edd33677aa0389f6650d467b2041fb0df4ca820eacb009babb95e3715d4@sprout-oss.stage.blox.sqprod.co>
87 lines
2.1 KiB
TypeScript
87 lines
2.1 KiB
TypeScript
import {
|
|
type ReactNode,
|
|
createContext,
|
|
useCallback,
|
|
useContext,
|
|
useEffect,
|
|
useState,
|
|
} from "react";
|
|
|
|
type Theme = "light" | "dark" | "system";
|
|
|
|
type ThemeContextValue = {
|
|
theme: Theme;
|
|
isDark: boolean;
|
|
setTheme: (theme: Theme) => void;
|
|
};
|
|
|
|
const ThemeContext = createContext<ThemeContextValue | undefined>(undefined);
|
|
|
|
function getSystemDark(): boolean {
|
|
return window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
}
|
|
|
|
function getInitialTheme(): Theme {
|
|
if (!import.meta.env.DEV) return "system";
|
|
|
|
const previewTheme = new URLSearchParams(window.location.search).get(
|
|
"previewTheme",
|
|
);
|
|
return previewTheme === "light" || previewTheme === "dark"
|
|
? previewTheme
|
|
: "system";
|
|
}
|
|
|
|
function applyClass(isDark: boolean) {
|
|
const root = document.documentElement;
|
|
root.classList.remove("light", "dark");
|
|
root.classList.add(isDark ? "dark" : "light");
|
|
}
|
|
|
|
export function ThemeProvider({ children }: { children: ReactNode }) {
|
|
const [theme, setThemeState] = useState<Theme>(getInitialTheme);
|
|
const [isDark, setIsDark] = useState<boolean>(() => {
|
|
const initialTheme = getInitialTheme();
|
|
const dark =
|
|
initialTheme === "system" ? getSystemDark() : initialTheme === "dark";
|
|
applyClass(dark);
|
|
return dark;
|
|
});
|
|
|
|
useEffect(() => {
|
|
const dark = theme === "system" ? getSystemDark() : theme === "dark";
|
|
applyClass(dark);
|
|
setIsDark(dark);
|
|
}, [theme]);
|
|
|
|
useEffect(() => {
|
|
if (theme !== "system") return;
|
|
|
|
const mq = window.matchMedia("(prefers-color-scheme: dark)");
|
|
const handler = (e: MediaQueryListEvent) => {
|
|
applyClass(e.matches);
|
|
setIsDark(e.matches);
|
|
};
|
|
mq.addEventListener("change", handler);
|
|
return () => mq.removeEventListener("change", handler);
|
|
}, [theme]);
|
|
|
|
const setTheme = useCallback((t: Theme) => {
|
|
setThemeState(t);
|
|
}, []);
|
|
|
|
return (
|
|
<ThemeContext.Provider value={{ theme, isDark, setTheme }}>
|
|
{children}
|
|
</ThemeContext.Provider>
|
|
);
|
|
}
|
|
|
|
export function useTheme() {
|
|
const context = useContext(ThemeContext);
|
|
if (!context) {
|
|
throw new Error("useTheme must be used within a ThemeProvider");
|
|
}
|
|
return context;
|
|
}
|