| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207 |
- import { createContext, useContext, useState, useEffect, type ReactNode } from 'react';
- import i18n from '../i18n';
- // 安全的localStorage访问
- const safeGetLocalStorage = (key: string, defaultValue: any = null) => {
- try {
- if (typeof window !== 'undefined' && window.localStorage) {
- return window.localStorage.getItem(key);
- }
- } catch (error) {
- console.warn('localStorage access error:', error);
- }
- return defaultValue;
- };
- // 安全的localStorage设置
- const safeSetLocalStorage = (key: string, value: string) => {
- try {
- if (typeof window !== 'undefined' && window.localStorage) {
- window.localStorage.setItem(key, value);
- }
- } catch (error) {
- console.warn('localStorage set error:', error);
- }
- };
- // 安全的系统主题检测
- const getSystemTheme = () => {
- try {
- if (typeof window !== 'undefined' && window.matchMedia) {
- return window.matchMedia('(prefers-color-scheme: dark)').matches;
- }
- } catch (error) {
- console.warn('System theme detection error:', error);
- }
- return false; // 默认浅色主题
- };
- // 主题类型定义
- interface ThemeClasses {
- bg: string;
- text: string;
- textMuted: string;
- textLight: string;
- border: string;
- borderStrong: string;
- bgSecondary: string;
- cardBg: string;
- cardHover: string;
- inputBorder: string;
- buttonPrimary: string;
- highlight: string;
- navBg: string;
- navBorder: string;
- marker: string;
- codeBlock: string;
- shadow: string;
- }
- // Context 类型定义
- interface GlobalSettingsContextType {
- isDark: boolean;
- currentLang: 'zh' | 'en';
- toggleTheme: () => void;
- toggleLang: () => void;
- setTheme: (dark: boolean) => void;
- setLanguage: (lang: 'zh' | 'en') => void;
- themeClasses: ThemeClasses;
- }
- // 创建 Context
- const GlobalSettingsContext = createContext<GlobalSettingsContextType | null>(null);
- // 生成主题类
- const getThemeClasses = (isDark: boolean): ThemeClasses => ({
- bg: isDark ? 'bg-stone-900' : 'bg-[#FAFAFA]',
- text: isDark ? 'text-stone-100' : 'text-stone-800',
- textMuted: isDark ? 'text-stone-400' : 'text-stone-500',
- textLight: isDark ? 'text-stone-500' : 'text-stone-400',
- border: isDark ? 'border-stone-800' : 'border-stone-200',
- borderStrong: isDark ? 'border-stone-700' : 'border-stone-300',
- bgSecondary: isDark ? 'bg-stone-800' : 'bg-white',
- cardBg: isDark ? 'bg-stone-800/50' : 'bg-white',
- cardHover: isDark ? 'hover:bg-stone-800' : 'hover:bg-stone-50',
- inputBorder: isDark ? 'border-stone-700 focus:border-stone-500' : 'border-stone-300 focus:border-stone-900',
- buttonPrimary: isDark ? 'bg-stone-100 text-stone-900 hover:bg-white' : 'bg-stone-900 text-white hover:bg-stone-800',
- highlight: isDark ? 'text-stone-100' : 'text-stone-900',
- navBg: isDark ? 'bg-stone-900/90' : 'bg-white/90',
- navBorder: isDark ? 'border-stone-800' : 'border-stone-100',
- marker: isDark ? 'bg-stone-100' : 'bg-yellow-200',
- codeBlock: isDark ? 'bg-stone-800 border-stone-700 text-stone-300' : 'bg-slate-50 border-slate-200 text-slate-700',
- shadow: isDark ? 'shadow-none' : 'shadow-sm'
- });
- // Provider 组件
- export const GlobalSettingsProvider = ({ children }: { children: ReactNode }) => {
- // 主题状态管理
- const getInitialTheme = () => {
- const saved = safeGetLocalStorage('theme');
- if (saved) return saved === 'dark';
- return getSystemTheme();
- };
- const [isDark, setIsDark] = useState(getInitialTheme);
- // 语言状态管理
- const getInitialLanguage = (): 'zh' | 'en' => {
- const saved = safeGetLocalStorage('i18nextLng') || i18n.language;
- return saved?.startsWith('zh') ? 'zh' : 'en';
- };
- const [currentLang, setCurrentLang] = useState<'zh' | 'en'>(getInitialLanguage);
- // 同步主题到localStorage和document
- useEffect(() => {
- safeSetLocalStorage('theme', isDark ? 'dark' : 'light');
- try {
- if (typeof window !== 'undefined' && document.documentElement) {
- document.documentElement.setAttribute('data-theme', isDark ? 'dark' : 'light');
- }
- } catch (error) {
- console.warn('Document theme setting error:', error);
- }
- }, [isDark]);
- // 同步语言到i18n
- useEffect(() => {
- if (i18n.language !== currentLang) {
- i18n.changeLanguage(currentLang);
- }
- }, [currentLang]);
- // 监听系统主题变化
- useEffect(() => {
- try {
- if (typeof window !== 'undefined' && window.matchMedia) {
- const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
- const handleChange = (e: MediaQueryListEvent) => {
- if (!safeGetLocalStorage('theme')) {
- setIsDark(e.matches);
- }
- };
- mediaQuery.addEventListener('change', handleChange);
- return () => mediaQuery.removeEventListener('change', handleChange);
- }
- } catch (error) {
- console.warn('System theme listener error:', error);
- }
- }, []);
- // 监听i18n语言变化
- useEffect(() => {
- const handleLanguageChanged = (lng: string) => {
- const newLang = lng?.startsWith('zh') ? 'zh' : 'en';
- if (newLang !== currentLang) {
- setCurrentLang(newLang);
- }
- };
- i18n.on('languageChanged', handleLanguageChanged);
- return () => {
- i18n.off('languageChanged', handleLanguageChanged);
- };
- }, [currentLang]);
- const toggleTheme = () => {
- setIsDark(prev => !prev);
- };
- const toggleLang = () => {
- setCurrentLang(prev => prev === 'zh' ? 'en' : 'zh');
- };
- const setTheme = (dark: boolean) => {
- setIsDark(dark);
- };
- const setLanguage = (lang: 'zh' | 'en') => {
- setCurrentLang(lang);
- };
- const value: GlobalSettingsContextType = {
- isDark,
- currentLang,
- toggleTheme,
- toggleLang,
- setTheme,
- setLanguage,
- themeClasses: getThemeClasses(isDark)
- };
- return (
- <GlobalSettingsContext.Provider value= { value } >
- { children }
- </GlobalSettingsContext.Provider>
- );
- };
- // Hook 用于访问全局设置
- export const useGlobalSettings = (): GlobalSettingsContextType => {
- const context = useContext(GlobalSettingsContext);
- if (!context) {
- throw new Error('useGlobalSettings must be used within a GlobalSettingsProvider');
- }
- return context;
- };
|