Init 2
This commit is contained in:
1 parent
ce00c4c671
commit
bdb9b72fac
66 files changed
+9553
-1547
No files matched your search
@@ -1,90 +1,25 @@
|
||||
"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
|
||||
}
|
||||
import { ThemeProvider as NextThemesProvider } from "next-themes"
|
||||
import type { ComponentProps } from "react"
|
||||
|
||||
/** Обёртка над next-themes: тот же storageKey `rl-theme`, класс на `<html>`. */
|
||||
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 {}
|
||||
}
|
||||
|
||||
...props
|
||||
}: ComponentProps<typeof NextThemesProvider>) {
|
||||
return (
|
||||
<ThemeContext.Provider value={{ theme, resolvedTheme, setTheme }}>
|
||||
<NextThemesProvider
|
||||
attribute="class"
|
||||
defaultTheme="dark"
|
||||
enableSystem
|
||||
storageKey="rl-theme"
|
||||
disableTransitionOnChange
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</ThemeContext.Provider>
|
||||
</NextThemesProvider>
|
||||
)
|
||||
}
|
||||
|
||||
// ── hook (drop-in replacement for next-themes useTheme) ───────────────────────
|
||||
|
||||
export function useTheme(): ThemeContextValue {
|
||||
return useContext(ThemeContext)
|
||||
}
|
||||
export { useTheme } from "next-themes"
|
||||
Reference in new issue
Block a user