Init commit
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
"use client"
|
||||
|
||||
import { createContext, useContext, useEffect, useState } from "react"
|
||||
|
||||
// ── types ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
type ThemeValue = "dark" | "light" | "system"
|
||||
type ResolvedTheme = "dark" | "light"
|
||||
|
||||
interface ThemeContextValue {
|
||||
theme: ThemeValue
|
||||
resolvedTheme: ResolvedTheme
|
||||
setTheme: (t: ThemeValue) => void
|
||||
}
|
||||
|
||||
// ── context ────────────────────────────────────────────────────────────────────
|
||||
|
||||
const ThemeContext = createContext<ThemeContextValue>({
|
||||
theme: "dark",
|
||||
resolvedTheme: "dark",
|
||||
setTheme: () => {},
|
||||
})
|
||||
|
||||
// ── provider ───────────────────────────────────────────────────────────────────
|
||||
|
||||
export interface ThemeProviderProps {
|
||||
children: React.ReactNode
|
||||
defaultTheme?: ThemeValue
|
||||
/** kept for API compat with next-themes — unused, class is always used */
|
||||
attribute?: string
|
||||
disableTransitionOnChange?: boolean
|
||||
}
|
||||
|
||||
export function ThemeProvider({
|
||||
children,
|
||||
defaultTheme = "dark",
|
||||
disableTransitionOnChange = false,
|
||||
}: ThemeProviderProps) {
|
||||
const [theme, setThemeState] = useState<ThemeValue>(() => {
|
||||
if (typeof window === "undefined") return defaultTheme
|
||||
return (localStorage.getItem("rl-theme") as ThemeValue | null) ?? defaultTheme
|
||||
})
|
||||
|
||||
const [systemDark, setSystemDark] = useState<boolean>(() => {
|
||||
if (typeof window === "undefined") return true
|
||||
return window.matchMedia("(prefers-color-scheme: dark)").matches
|
||||
})
|
||||
|
||||
// Track system preference
|
||||
useEffect(() => {
|
||||
const mq = window.matchMedia("(prefers-color-scheme: dark)")
|
||||
const handler = (e: MediaQueryListEvent) => setSystemDark(e.matches)
|
||||
mq.addEventListener("change", handler)
|
||||
return () => mq.removeEventListener("change", handler)
|
||||
}, [])
|
||||
|
||||
const resolvedTheme: ResolvedTheme =
|
||||
theme === "system" ? (systemDark ? "dark" : "light") : theme
|
||||
|
||||
// Apply class to <html>
|
||||
useEffect(() => {
|
||||
const root = document.documentElement
|
||||
|
||||
if (disableTransitionOnChange) {
|
||||
root.style.setProperty("transition", "none")
|
||||
void root.offsetHeight // force reflow
|
||||
setTimeout(() => root.style.removeProperty("transition"), 0)
|
||||
}
|
||||
|
||||
root.classList.toggle("dark", resolvedTheme === "dark")
|
||||
root.classList.toggle("light", resolvedTheme === "light")
|
||||
}, [resolvedTheme, disableTransitionOnChange])
|
||||
|
||||
const setTheme = (t: ThemeValue) => {
|
||||
setThemeState(t)
|
||||
try { localStorage.setItem("rl-theme", t) } catch {}
|
||||
}
|
||||
|
||||
return (
|
||||
<ThemeContext.Provider value={{ theme, resolvedTheme, setTheme }}>
|
||||
{children}
|
||||
</ThemeContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
// ── hook (drop-in replacement for next-themes useTheme) ───────────────────────
|
||||
|
||||
export function useTheme(): ThemeContextValue {
|
||||
return useContext(ThemeContext)
|
||||
}
|
||||
Reference in New Issue
Block a user