1.3 KB54 lines
Blame
1"use client";
2
3import { createContext, useContext, useEffect, useState } from "react";
4
5type Theme = "light" | "dark";
6
7interface ThemeContextValue {
8 theme: Theme;
9 toggle: () => void;
10}
11
12const ThemeContext = createContext<ThemeContextValue>({
13 theme: "light",
14 toggle: () => {},
15});
16
17export function ThemeProvider({ children }: { children: React.ReactNode }) {
18 const [theme, setTheme] = useState<Theme>("light");
19 const [mounted, setMounted] = useState(false);
20
21 useEffect(() => {
22 const stored = localStorage.getItem("grove_theme") as Theme | null;
23 if (stored) {
24 setTheme(stored);
25 } else if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
26 setTheme("dark");
27 }
28 setMounted(true);
29 }, []);
30
31 useEffect(() => {
32 if (!mounted) return;
33 document.documentElement.setAttribute("data-theme", theme);
34 localStorage.setItem("grove_theme", theme);
35 }, [theme, mounted]);
36
37 const toggle = () => setTheme((t) => (t === "light" ? "dark" : "light"));
38
39 // Prevent flash of wrong theme
40 if (!mounted) {
41 return <div style={{ visibility: "hidden" }}>{children}</div>;
42 }
43
44 return (
45 <ThemeContext.Provider value={{ theme, toggle }}>
46 {children}
47 </ThemeContext.Provider>
48 );
49}
50
51export function useTheme() {
52 return useContext(ThemeContext);
53}
54