useGlobalSettings.tsx 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. import { createContext, useContext, useState, useEffect, type ReactNode } from 'react';
  2. import i18n from '../i18n';
  3. // 安全的localStorage访问
  4. const safeGetLocalStorage = (key: string, defaultValue: any = null) => {
  5. try {
  6. if (typeof window !== 'undefined' && window.localStorage) {
  7. return window.localStorage.getItem(key);
  8. }
  9. } catch (error) {
  10. console.warn('localStorage access error:', error);
  11. }
  12. return defaultValue;
  13. };
  14. // 安全的localStorage设置
  15. const safeSetLocalStorage = (key: string, value: string) => {
  16. try {
  17. if (typeof window !== 'undefined' && window.localStorage) {
  18. window.localStorage.setItem(key, value);
  19. }
  20. } catch (error) {
  21. console.warn('localStorage set error:', error);
  22. }
  23. };
  24. // 安全的系统主题检测
  25. const getSystemTheme = () => {
  26. try {
  27. if (typeof window !== 'undefined' && window.matchMedia) {
  28. return window.matchMedia('(prefers-color-scheme: dark)').matches;
  29. }
  30. } catch (error) {
  31. console.warn('System theme detection error:', error);
  32. }
  33. return false; // 默认浅色主题
  34. };
  35. // 主题类型定义
  36. interface ThemeClasses {
  37. bg: string;
  38. text: string;
  39. textMuted: string;
  40. textLight: string;
  41. border: string;
  42. borderStrong: string;
  43. bgSecondary: string;
  44. cardBg: string;
  45. cardHover: string;
  46. inputBorder: string;
  47. buttonPrimary: string;
  48. highlight: string;
  49. navBg: string;
  50. navBorder: string;
  51. marker: string;
  52. codeBlock: string;
  53. shadow: string;
  54. }
  55. // Context 类型定义
  56. interface GlobalSettingsContextType {
  57. isDark: boolean;
  58. currentLang: 'zh' | 'en';
  59. toggleTheme: () => void;
  60. toggleLang: () => void;
  61. setTheme: (dark: boolean) => void;
  62. setLanguage: (lang: 'zh' | 'en') => void;
  63. themeClasses: ThemeClasses;
  64. }
  65. // 创建 Context
  66. const GlobalSettingsContext = createContext<GlobalSettingsContextType | null>(null);
  67. // 生成主题类
  68. const getThemeClasses = (isDark: boolean): ThemeClasses => ({
  69. bg: isDark ? 'bg-stone-900' : 'bg-[#FAFAFA]',
  70. text: isDark ? 'text-stone-100' : 'text-stone-800',
  71. textMuted: isDark ? 'text-stone-400' : 'text-stone-500',
  72. textLight: isDark ? 'text-stone-500' : 'text-stone-400',
  73. border: isDark ? 'border-stone-800' : 'border-stone-200',
  74. borderStrong: isDark ? 'border-stone-700' : 'border-stone-300',
  75. bgSecondary: isDark ? 'bg-stone-800' : 'bg-white',
  76. cardBg: isDark ? 'bg-stone-800/50' : 'bg-white',
  77. cardHover: isDark ? 'hover:bg-stone-800' : 'hover:bg-stone-50',
  78. inputBorder: isDark ? 'border-stone-700 focus:border-stone-500' : 'border-stone-300 focus:border-stone-900',
  79. buttonPrimary: isDark ? 'bg-stone-100 text-stone-900 hover:bg-white' : 'bg-stone-900 text-white hover:bg-stone-800',
  80. highlight: isDark ? 'text-stone-100' : 'text-stone-900',
  81. navBg: isDark ? 'bg-stone-900/90' : 'bg-white/90',
  82. navBorder: isDark ? 'border-stone-800' : 'border-stone-100',
  83. marker: isDark ? 'bg-stone-100' : 'bg-yellow-200',
  84. codeBlock: isDark ? 'bg-stone-800 border-stone-700 text-stone-300' : 'bg-slate-50 border-slate-200 text-slate-700',
  85. shadow: isDark ? 'shadow-none' : 'shadow-sm'
  86. });
  87. // Provider 组件
  88. export const GlobalSettingsProvider = ({ children }: { children: ReactNode }) => {
  89. // 主题状态管理
  90. const getInitialTheme = () => {
  91. const saved = safeGetLocalStorage('theme');
  92. if (saved) return saved === 'dark';
  93. return getSystemTheme();
  94. };
  95. const [isDark, setIsDark] = useState(getInitialTheme);
  96. // 语言状态管理
  97. const getInitialLanguage = (): 'zh' | 'en' => {
  98. const saved = safeGetLocalStorage('i18nextLng') || i18n.language;
  99. return saved?.startsWith('zh') ? 'zh' : 'en';
  100. };
  101. const [currentLang, setCurrentLang] = useState<'zh' | 'en'>(getInitialLanguage);
  102. // 同步主题到localStorage和document
  103. useEffect(() => {
  104. safeSetLocalStorage('theme', isDark ? 'dark' : 'light');
  105. try {
  106. if (typeof window !== 'undefined' && document.documentElement) {
  107. document.documentElement.setAttribute('data-theme', isDark ? 'dark' : 'light');
  108. }
  109. } catch (error) {
  110. console.warn('Document theme setting error:', error);
  111. }
  112. }, [isDark]);
  113. // 同步语言到i18n
  114. useEffect(() => {
  115. if (i18n.language !== currentLang) {
  116. i18n.changeLanguage(currentLang);
  117. }
  118. }, [currentLang]);
  119. // 监听系统主题变化
  120. useEffect(() => {
  121. try {
  122. if (typeof window !== 'undefined' && window.matchMedia) {
  123. const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
  124. const handleChange = (e: MediaQueryListEvent) => {
  125. if (!safeGetLocalStorage('theme')) {
  126. setIsDark(e.matches);
  127. }
  128. };
  129. mediaQuery.addEventListener('change', handleChange);
  130. return () => mediaQuery.removeEventListener('change', handleChange);
  131. }
  132. } catch (error) {
  133. console.warn('System theme listener error:', error);
  134. }
  135. }, []);
  136. // 监听i18n语言变化
  137. useEffect(() => {
  138. const handleLanguageChanged = (lng: string) => {
  139. const newLang = lng?.startsWith('zh') ? 'zh' : 'en';
  140. if (newLang !== currentLang) {
  141. setCurrentLang(newLang);
  142. }
  143. };
  144. i18n.on('languageChanged', handleLanguageChanged);
  145. return () => {
  146. i18n.off('languageChanged', handleLanguageChanged);
  147. };
  148. }, [currentLang]);
  149. const toggleTheme = () => {
  150. setIsDark(prev => !prev);
  151. };
  152. const toggleLang = () => {
  153. setCurrentLang(prev => prev === 'zh' ? 'en' : 'zh');
  154. };
  155. const setTheme = (dark: boolean) => {
  156. setIsDark(dark);
  157. };
  158. const setLanguage = (lang: 'zh' | 'en') => {
  159. setCurrentLang(lang);
  160. };
  161. const value: GlobalSettingsContextType = {
  162. isDark,
  163. currentLang,
  164. toggleTheme,
  165. toggleLang,
  166. setTheme,
  167. setLanguage,
  168. themeClasses: getThemeClasses(isDark)
  169. };
  170. return (
  171. <GlobalSettingsContext.Provider value= { value } >
  172. { children }
  173. </GlobalSettingsContext.Provider>
  174. );
  175. };
  176. // Hook 用于访问全局设置
  177. export const useGlobalSettings = (): GlobalSettingsContextType => {
  178. const context = useContext(GlobalSettingsContext);
  179. if (!context) {
  180. throw new Error('useGlobalSettings must be used within a GlobalSettingsProvider');
  181. }
  182. return context;
  183. };