码桶

发现社区成员的开源项目

奶狗 /

ng-webot

公开
main
ng-webot/web/src/lib/theme.tsx
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)
}