Files
seclusion/apps/web/src/stores/uiStore.ts
charilezhou 5c1a998192 feat(web): 实现自定义主题功能
支持 6 套预设主题(默认、海洋、森林、日落、玫瑰、紫罗兰)和自定义主色色相滑块,
通过动态注入 CSS 变量实现主题切换,使用 localStorage 持久化存储,
添加 SSR 初始化脚本避免首次加载颜色闪烁。

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 18:22:22 +08:00

80 lines
2.4 KiB
TypeScript

import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
import type { UIStore } from './types';
import { STORAGE_KEYS } from '@/config/constants';
import {
DEFAULT_THEME_CONFIG,
getHueFromConfig,
getPresetById,
} from '@/config/theme-presets';
import { applyTheme } from '@/lib/theme-service';
export const useUIStore = create<UIStore>()(
persist(
(set, get) => ({
theme: 'system',
themeConfig: DEFAULT_THEME_CONFIG,
sidebarOpen: false, // 移动端侧边栏默认关闭
sidebarCollapsed: false,
setTheme: (theme) => set({ theme }),
setThemeConfig: (config) => {
set({ themeConfig: config });
// 应用主题
const hue = getHueFromConfig(config);
applyTheme(hue);
},
applyPreset: (presetId) => {
// 验证预设是否存在
const preset = getPresetById(presetId);
if (!preset) {
console.warn(`Theme preset "${presetId}" not found, using default`);
presetId = 'default';
}
get().setThemeConfig({ type: 'preset', presetId });
},
applyCustomPrimaryHue: (hue) => {
// 确保色相在有效范围内
const normalizedHue = ((hue % 360) + 360) % 360;
get().setThemeConfig({ type: 'custom', primaryHue: normalizedHue });
},
toggleSidebar: () =>
set((state) => ({ sidebarOpen: !state.sidebarOpen })),
setSidebarOpen: (open) => set({ sidebarOpen: open }),
setSidebarCollapsed: (collapsed) =>
set({ sidebarCollapsed: collapsed }),
}),
{
name: STORAGE_KEYS.UI,
storage: createJSONStorage(() => localStorage),
partialize: (state) => ({
theme: state.theme,
themeConfig: state.themeConfig,
sidebarCollapsed: state.sidebarCollapsed,
}),
// hydration 完成后应用主题
onRehydrateStorage: () => (state) => {
if (state?.themeConfig) {
const hue = getHueFromConfig(state.themeConfig);
applyTheme(hue);
}
},
}
)
);
// Selector hooks
export const useTheme = () => useUIStore((state) => state.theme);
export const useThemeConfig = () => useUIStore((state) => state.themeConfig);
export const useSidebarOpen = () => useUIStore((state) => state.sidebarOpen);
export const useSidebarCollapsed = () =>
useUIStore((state) => state.sidebarCollapsed);