feat: add auto theme mode (follow system preference)

- Add 'auto' to Theme type
- Implement cycleTheme (light -> dark -> auto)
- Add autoTheme icon (sun with half-filled center)
- Listen to system theme changes in auto mode

Also includes some Prettier formatting fixes.
This commit is contained in:
XYenon
2025-12-24 00:02:59 +08:00
parent 561e06503c
commit 5f7df33469
3 changed files with 134 additions and 60 deletions

View File

@@ -8,63 +8,72 @@ import { persist } from 'zustand/middleware';
import type { Theme } from '@/types';
import { STORAGE_KEY_THEME } from '@/utils/constants';
type ResolvedTheme = 'light' | 'dark';
interface ThemeState {
theme: Theme;
resolvedTheme: ResolvedTheme;
setTheme: (theme: Theme) => void;
toggleTheme: () => void;
cycleTheme: () => void;
initializeTheme: () => void;
}
const getSystemTheme = (): ResolvedTheme => {
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
return 'dark';
}
return 'light';
};
const applyTheme = (resolved: ResolvedTheme) => {
if (resolved === 'dark') {
document.documentElement.setAttribute('data-theme', 'dark');
} else {
document.documentElement.removeAttribute('data-theme');
}
};
export const useThemeStore = create<ThemeState>()(
persist(
(set, get) => ({
theme: 'light',
theme: 'auto',
resolvedTheme: 'light',
setTheme: (theme) => {
// 应用主题到 DOM
if (theme === 'dark') {
document.documentElement.setAttribute('data-theme', 'dark');
} else {
document.documentElement.removeAttribute('data-theme');
}
set({ theme });
const resolved: ResolvedTheme = theme === 'auto' ? getSystemTheme() : theme;
applyTheme(resolved);
set({ theme, resolvedTheme: resolved });
},
toggleTheme: () => {
cycleTheme: () => {
const { theme, setTheme } = get();
const newTheme: Theme = theme === 'light' ? 'dark' : 'light';
setTheme(newTheme);
const order: Theme[] = ['light', 'dark', 'auto'];
const currentIndex = order.indexOf(theme);
const nextTheme = order[(currentIndex + 1) % order.length];
setTheme(nextTheme);
},
initializeTheme: () => {
const { theme, setTheme } = get();
// 检查系统偏好
if (
!localStorage.getItem(STORAGE_KEY_THEME) &&
window.matchMedia &&
window.matchMedia('(prefers-color-scheme: dark)').matches
) {
setTheme('dark');
return;
}
// 应用已保存的主题
setTheme(theme);
// 监听系统主题变化(仅在用户未手动设置时
// 监听系统主题变化(仅在 auto 模式下生效
if (window.matchMedia) {
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
if (!localStorage.getItem(STORAGE_KEY_THEME)) {
setTheme(e.matches ? 'dark' : 'light');
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
const { theme: currentTheme } = get();
if (currentTheme === 'auto') {
const resolved = getSystemTheme();
applyTheme(resolved);
set({ resolvedTheme: resolved });
}
});
}
}
},
}),
{
name: STORAGE_KEY_THEME
name: STORAGE_KEY_THEME,
}
)
);