feat(ui): implement dark mode with system preference support
- Add useDarkMode hook for managing theme state and persistence - Integrate dark mode toggle button in app header - Update all components with dark variant styles using Tailwind v4 - Create centralized style utilities for consistent theming - Support system color scheme preference as fallback - Store user preference in localStorage for persistence
This commit is contained in:
80
src/hooks/useDarkMode.ts
Normal file
80
src/hooks/useDarkMode.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
export function useDarkMode() {
|
||||
// 初始设为 false,挂载后在 useEffect 中加载真实值
|
||||
const [isDarkMode, setIsDarkMode] = useState<boolean>(false);
|
||||
const [isInitialized, setIsInitialized] = useState(false);
|
||||
|
||||
// 组件挂载后加载初始值(兼容 Tauri 环境)
|
||||
useEffect(() => {
|
||||
if (typeof window === 'undefined') return;
|
||||
|
||||
try {
|
||||
// 尝试读取已保存的偏好
|
||||
const saved = localStorage.getItem('darkMode');
|
||||
if (saved !== null) {
|
||||
const savedBool = saved === 'true';
|
||||
setIsDarkMode(savedBool);
|
||||
console.log('[DarkMode] Loaded from localStorage:', savedBool);
|
||||
} else {
|
||||
// 回退到系统偏好
|
||||
const prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
setIsDarkMode(prefersDark);
|
||||
console.log('[DarkMode] Using system preference:', prefersDark);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[DarkMode] Error loading preference:', error);
|
||||
setIsDarkMode(false);
|
||||
}
|
||||
|
||||
setIsInitialized(true);
|
||||
}, []); // 仅在首次挂载时运行
|
||||
|
||||
// 将 dark 类应用到文档根节点
|
||||
useEffect(() => {
|
||||
if (!isInitialized) return;
|
||||
|
||||
// 添加短暂延迟以确保 Tauri 中 DOM 已就绪
|
||||
const timer = setTimeout(() => {
|
||||
try {
|
||||
if (isDarkMode) {
|
||||
document.documentElement.classList.add('dark');
|
||||
console.log('[DarkMode] Added dark class to document');
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark');
|
||||
console.log('[DarkMode] Removed dark class from document');
|
||||
}
|
||||
|
||||
// 检查类名是否已成功应用
|
||||
const hasClass = document.documentElement.classList.contains('dark');
|
||||
console.log('[DarkMode] Document has dark class:', hasClass);
|
||||
} catch (error) {
|
||||
console.error('[DarkMode] Error applying dark class:', error);
|
||||
}
|
||||
}, 0);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, [isDarkMode, isInitialized]);
|
||||
|
||||
// 将偏好保存到 localStorage
|
||||
useEffect(() => {
|
||||
if (!isInitialized) return;
|
||||
|
||||
try {
|
||||
localStorage.setItem('darkMode', isDarkMode.toString());
|
||||
console.log('[DarkMode] Saved to localStorage:', isDarkMode);
|
||||
} catch (error) {
|
||||
console.error('[DarkMode] Error saving preference:', error);
|
||||
}
|
||||
}, [isDarkMode, isInitialized]);
|
||||
|
||||
const toggleDarkMode = () => {
|
||||
setIsDarkMode(prev => {
|
||||
const newValue = !prev;
|
||||
console.log('[DarkMode] Toggling from', prev, 'to', newValue);
|
||||
return newValue;
|
||||
});
|
||||
};
|
||||
|
||||
return { isDarkMode, toggleDarkMode };
|
||||
}
|
||||
Reference in New Issue
Block a user