码桶
发现社区成员的开源项目
theme.tsx1.9 KB
import { createContext, useContext, useEffect, useState, type ReactNode } from 'react'
export type Theme = 'light' | 'dark' | 'system'
export type Resolved = 'light' | 'dark'
const KEY = 'app-theme'
function systemPref(): Resolved {
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
}
function resolveTheme(t: Theme): Resolved {
return t === 'system' ? systemPref() : t
}
export function applyTheme(t: Theme) {
const r = resolveTheme(t)
document.documentElement.classList.toggle('dark', r === 'dark')
}
interface ThemeCtx {
theme: Theme
resolved: Resolved
setTheme: (t: Theme) => void
}
const Ctx = createContext<ThemeCtx>({ theme: 'system', resolved: 'light', setTheme: () => {} })
export function ThemeProvider({ children }: { children: ReactNode }) {
const [theme, setThemeState] = useState<Theme>(() => {
try {
const v = localStorage.getItem(KEY) as Theme | null
if (v === 'light' || v === 'dark' || v === 'system') return v
} catch { /* ignore */ }
return 'system'
})
const [resolved, setResolved] = useState<Resolved>(() => resolveTheme(theme))
useEffect(() => {
applyTheme(theme)
setResolved(resolveTheme(theme))
try { localStorage.setItem(KEY, theme) } catch { /* ignore */ }
}, [theme])
// 跟随系统:监听系统主题变化
useEffect(() => {
if (theme !== 'system') return
const mq = window.matchMedia('(prefers-color-scheme: dark)')
const handler = () => {
applyTheme('system')
setResolved(resolveTheme('system'))
}
mq.addEventListener('change', handler)
return () => mq.removeEventListener('change', handler)
}, [theme])
return (
<Ctx.Provider value={{ theme, resolved, setTheme: setThemeState }}>{children}</Ctx.Provider>
)
}
export function useTheme() {
return useContext(Ctx)
}