91 lines
2.3 KiB
TypeScript
91 lines
2.3 KiB
TypeScript
"use client"
|
|
|
|
import * as React from "react"
|
|
|
|
type Theme = "light" | "dark" | "system"
|
|
|
|
const ThemeContext = React.createContext<{
|
|
theme: Theme
|
|
setTheme: (theme: Theme) => void
|
|
resolvedTheme: "light" | "dark"
|
|
}>({
|
|
theme: "system",
|
|
setTheme: () => {},
|
|
resolvedTheme: "light",
|
|
})
|
|
|
|
export function useTheme() {
|
|
return React.useContext(ThemeContext)
|
|
}
|
|
|
|
function getSystemTheme(): "light" | "dark" {
|
|
if (typeof window === "undefined") return "light"
|
|
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"
|
|
}
|
|
|
|
export function ThemeProvider({
|
|
children,
|
|
attribute = "class",
|
|
defaultTheme = "system",
|
|
enableSystem = true,
|
|
}: {
|
|
children: React.ReactNode
|
|
attribute?: string
|
|
defaultTheme?: Theme
|
|
enableSystem?: boolean
|
|
disableTransitionOnChange?: boolean
|
|
}) {
|
|
const [theme, setThemeState] = React.useState<Theme>(defaultTheme)
|
|
const [resolvedTheme, setResolvedTheme] = React.useState<"light" | "dark">("light")
|
|
const [mounted, setMounted] = React.useState(false)
|
|
|
|
// Read saved theme from localStorage on mount
|
|
React.useEffect(() => {
|
|
const saved = localStorage.getItem("theme") as Theme | null
|
|
if (saved) {
|
|
setThemeState(saved)
|
|
}
|
|
setMounted(true)
|
|
}, [])
|
|
|
|
// Resolve system theme and apply to document
|
|
React.useEffect(() => {
|
|
const resolved = theme === "system" ? getSystemTheme() : theme
|
|
setResolvedTheme(resolved)
|
|
|
|
const root = document.documentElement
|
|
root.classList.remove("light", "dark")
|
|
root.classList.add(resolved)
|
|
}, [theme])
|
|
|
|
// Listen for system theme changes
|
|
React.useEffect(() => {
|
|
if (!enableSystem) return
|
|
|
|
const mql = window.matchMedia("(prefers-color-scheme: dark)")
|
|
const handler = () => {
|
|
if (theme === "system") {
|
|
const resolved = getSystemTheme()
|
|
setResolvedTheme(resolved)
|
|
const root = document.documentElement
|
|
root.classList.remove("light", "dark")
|
|
root.classList.add(resolved)
|
|
}
|
|
}
|
|
|
|
mql.addEventListener("change", handler)
|
|
return () => mql.removeEventListener("change", handler)
|
|
}, [theme, enableSystem])
|
|
|
|
const setTheme = React.useCallback((newTheme: Theme) => {
|
|
setThemeState(newTheme)
|
|
localStorage.setItem("theme", newTheme)
|
|
}, [])
|
|
|
|
return (
|
|
<ThemeContext.Provider value={{ theme, setTheme, resolvedTheme }}>
|
|
{children}
|
|
</ThemeContext.Provider>
|
|
)
|
|
}
|