import * as React from "react"; | |
type Theme = "system" | "light" | "dark"; | |
const STORAGE_KEY = "theme"; | |
const VALID_THEMES: Theme[] = ["system", "light", "dark"]; | |
const DARK_MODE_MEDIA_QUERY = "(prefers-color-scheme: dark)"; | |
function getAppTheme(): Theme { | |
if (typeof window !== "undefined") { | |
const storage = window.localStorage.getItem(STORAGE_KEY) as Theme; | |
const config: Theme = VALID_THEMES.includes(storage) ? storage : "system"; | |
if (config === "system") { | |
return window.matchMedia(DARK_MODE_MEDIA_QUERY).matches | |
? "dark" | |
: "light"; | |
} else { | |
return config; | |
} | |
} else { | |
return "system"; | |
} | |
} | |
function setAppTheme(theme: Theme) { | |
if (VALID_THEMES.includes(theme)) { | |
window.localStorage.setItem(STORAGE_KEY, theme); | |
if (theme !== "system") { | |
window.document.documentElement.setAttribute("data-theme", theme); | |
} else { | |
window.document.documentElement.removeAttribute("data-theme"); | |
} | |
} | |
} | |
const ThemeContext = React.createContext(undefined); | |
export function AppThemeProvider({ children }: { children: React.ReactNode }) { | |
const [theme, setTheme] = React.useState(getAppTheme); | |
const handleThemeChange = (event: MediaQueryListEvent) => { | |
const theme = event.matches ? "dark" : "light"; | |
setTheme(theme); | |
}; | |
React.useEffect(() => { | |
setAppTheme(theme); | |
}, [theme]); | |
React.useEffect(() => { | |
const mediaQuery = window.matchMedia(DARK_MODE_MEDIA_QUERY); | |
mediaQuery.addListener(handleThemeChange); | |
return () => { | |
mediaQuery.removeListener(handleThemeChange); | |
}; | |
}, []); | |
return ( | |
<ThemeContext.Provider value={{ theme }}>{children}</ThemeContext.Provider> | |
); | |
} | |
export default function useAppTheme() { | |
const { theme } = React.useContext(ThemeContext); | |
return theme; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This comment has been minimized.
const theme = useTheme()