ThemeProvider.tsx 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. 'use client'
  2. import { ConfigProvider, theme, App } from 'antd'
  3. import { useEffect, useState, createContext, useContext } from 'react'
  4. type ThemeMode = 'light' | 'dark' | 'system'
  5. interface ThemeContextType {
  6. mode: ThemeMode
  7. setMode: (mode: ThemeMode) => void
  8. isDark: boolean
  9. }
  10. const ThemeContext = createContext<ThemeContextType | undefined>(undefined)
  11. export function useTheme() {
  12. const context = useContext(ThemeContext)
  13. if (!context) {
  14. throw new Error('useTheme must be used within ThemeProvider')
  15. }
  16. return context
  17. }
  18. export function ThemeProvider({ children }: { children: React.ReactNode }) {
  19. const [mode, setModeState] = useState<ThemeMode>('system')
  20. const [isDark, setIsDark] = useState(false)
  21. const [mounted, setMounted] = useState(false)
  22. // 初始化:从 localStorage 读取保存的主题设置,并检测系统主题
  23. useEffect(() => {
  24. setMounted(true)
  25. // 从 localStorage 读取保存的主题设置
  26. const savedMode = localStorage.getItem('theme-mode') as ThemeMode
  27. if (savedMode && ['light', 'dark', 'system'].includes(savedMode)) {
  28. setModeState(savedMode)
  29. }
  30. // 检测系统主题
  31. const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
  32. const currentMode =
  33. savedMode && ['light', 'dark', 'system'].includes(savedMode)
  34. ? savedMode
  35. : 'system'
  36. if (currentMode === 'system') {
  37. setIsDark(mediaQuery.matches)
  38. } else {
  39. setIsDark(currentMode === 'dark')
  40. }
  41. }, [])
  42. // 检测系统主题变化
  43. useEffect(() => {
  44. if (!mounted) return
  45. const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
  46. const updateTheme = () => {
  47. if (mode === 'system') {
  48. setIsDark(mediaQuery.matches)
  49. } else {
  50. setIsDark(mode === 'dark')
  51. }
  52. }
  53. updateTheme()
  54. // 监听系统主题变化
  55. mediaQuery.addEventListener('change', updateTheme)
  56. return () => mediaQuery.removeEventListener('change', updateTheme)
  57. }, [mode, mounted])
  58. // 更新 HTML 的 color-scheme 属性
  59. useEffect(() => {
  60. document.documentElement.style.colorScheme = isDark ? 'dark' : 'light'
  61. const meta = document.querySelector('meta[name="color-scheme"]')
  62. if (meta) {
  63. meta.setAttribute('content', isDark ? 'dark' : 'light')
  64. }
  65. }, [isDark])
  66. const setMode = (newMode: ThemeMode) => {
  67. setModeState(newMode)
  68. localStorage.setItem('theme-mode', newMode)
  69. }
  70. return (
  71. <ThemeContext.Provider value={{ mode, setMode, isDark }}>
  72. <ConfigProvider
  73. theme={{
  74. algorithm: isDark ? theme.darkAlgorithm : theme.defaultAlgorithm,
  75. }}
  76. >
  77. <App>{children}</App>
  78. </ConfigProvider>
  79. </ThemeContext.Provider>
  80. )
  81. }