146 lines
3.5 KiB
TypeScript
146 lines
3.5 KiB
TypeScript
import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react';
|
|
|
|
// 主题类型
|
|
export type ThemeType = 'dark' | 'light';
|
|
|
|
// 主题配置
|
|
export interface ThemeConfig {
|
|
type: ThemeType;
|
|
name: string;
|
|
colors: {
|
|
background: string;
|
|
backgroundSecondary: string;
|
|
text: string;
|
|
textSecondary: string;
|
|
primary: string;
|
|
primaryHover: string;
|
|
border: string;
|
|
accent: string;
|
|
success: string;
|
|
warning: string;
|
|
error: string;
|
|
card: string;
|
|
cardHover: string;
|
|
input: string;
|
|
inputBorder: string;
|
|
overlay: string;
|
|
};
|
|
}
|
|
|
|
// 主题配置
|
|
const themes: Record<ThemeType, ThemeConfig> = {
|
|
dark: {
|
|
type: 'dark',
|
|
name: '深色主题',
|
|
colors: {
|
|
background: 'slate-900',
|
|
backgroundSecondary: 'slate-800',
|
|
text: 'white',
|
|
textSecondary: 'slate-400',
|
|
primary: 'blue-500',
|
|
primaryHover: 'blue-400',
|
|
border: 'blue-800',
|
|
accent: 'cyan-400',
|
|
success: 'emerald-500',
|
|
warning: 'amber-500',
|
|
error: 'rose-500',
|
|
card: 'slate-800',
|
|
cardHover: 'slate-700',
|
|
input: 'slate-800',
|
|
inputBorder: 'blue-800',
|
|
overlay: 'slate-900/50',
|
|
},
|
|
},
|
|
light: {
|
|
type: 'light',
|
|
name: '红白主题',
|
|
colors: {
|
|
background: 'white',
|
|
backgroundSecondary: 'red-50',
|
|
text: 'gray-900',
|
|
textSecondary: 'gray-600',
|
|
primary: 'red-600',
|
|
primaryHover: 'red-500',
|
|
border: 'red-200',
|
|
accent: 'red-700',
|
|
success: 'green-600',
|
|
warning: 'amber-500',
|
|
error: 'red-600',
|
|
card: 'white',
|
|
cardHover: 'red-50',
|
|
input: 'white',
|
|
inputBorder: 'red-200',
|
|
overlay: 'black/30',
|
|
},
|
|
},
|
|
};
|
|
|
|
// 主题上下文
|
|
interface ThemeContextType {
|
|
theme: ThemeType;
|
|
themeConfig: ThemeConfig;
|
|
toggleTheme: () => void;
|
|
setTheme: (theme: ThemeType) => void;
|
|
}
|
|
|
|
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
|
|
|
|
// 主题提供者
|
|
interface ThemeProviderProps {
|
|
children: ReactNode;
|
|
}
|
|
|
|
export const ThemeProvider: React.FC<ThemeProviderProps> = ({ children }) => {
|
|
const [theme, setThemeState] = useState<ThemeType>(() => {
|
|
// 从 localStorage 读取保存的主题
|
|
const saved = localStorage.getItem('strategy-theme');
|
|
return (saved as ThemeType) || 'dark';
|
|
});
|
|
|
|
// 切换主题
|
|
const toggleTheme = () => {
|
|
setThemeState((prev) => (prev === 'dark' ? 'light' : 'dark'));
|
|
};
|
|
|
|
// 设置特定主题
|
|
const setTheme = (newTheme: ThemeType) => {
|
|
setThemeState(newTheme);
|
|
};
|
|
|
|
// 保存到 localStorage
|
|
useEffect(() => {
|
|
localStorage.setItem('strategy-theme', theme);
|
|
// 更新文档类名
|
|
document.documentElement.classList.remove('dark', 'light');
|
|
document.documentElement.classList.add(theme);
|
|
}, [theme]);
|
|
|
|
const themeConfig = themes[theme];
|
|
|
|
return (
|
|
<ThemeContext.Provider value={{ theme, themeConfig, toggleTheme, setTheme }}>
|
|
{children}
|
|
</ThemeContext.Provider>
|
|
);
|
|
};
|
|
|
|
// 使用主题的 Hook
|
|
export const useTheme = (): ThemeContextType => {
|
|
const context = useContext(ThemeContext);
|
|
if (!context) {
|
|
throw new Error('useTheme must be used within a ThemeProvider');
|
|
}
|
|
return context;
|
|
};
|
|
|
|
// 主题工具函数 - 生成动态类名
|
|
export const getThemeClasses = (colorKey: keyof ThemeConfig['colors']): string => {
|
|
const { themeConfig } = useTheme();
|
|
return themeConfig.colors[colorKey];
|
|
};
|
|
|
|
// 组合类名工具函数
|
|
export const cn = (...classes: (string | undefined | null | false)[]): string => {
|
|
return classes.filter(Boolean).join(' ');
|
|
};
|