feat: 初始提交

This commit is contained in:
2026-05-26 18:19:42 +08:00
commit 7ebf5ee5dc
107 changed files with 9317 additions and 0 deletions

View File

@@ -0,0 +1,51 @@
import { useEffect, useState } from "react";
export const SIDEBAR_COLLAPSED_STORAGE_KEY = "sidebar.collapsed";
export function applyInitialSidebarCollapsed() {
const collapsed = readSidebarCollapsed();
applySidebarCollapsed(collapsed);
}
export function applySidebarCollapsed(collapsed: boolean, root: HTMLElement = document.documentElement) {
root.setAttribute("data-sidebar-collapsed", String(collapsed));
}
export function parseSidebarCollapsed(value: unknown): boolean {
return value === "true";
}
export function readSidebarCollapsed(storage: Storage = window.localStorage): boolean {
try {
return parseSidebarCollapsed(storage.getItem(SIDEBAR_COLLAPSED_STORAGE_KEY));
} catch {
return false;
}
}
export function useSidebarCollapsed() {
const [collapsed, setCollapsedState] = useState<boolean>(() => readSidebarCollapsed());
useEffect(() => {
applySidebarCollapsed(collapsed);
}, [collapsed]);
const setCollapsed = (nextCollapsed: boolean) => {
setCollapsedState(nextCollapsed);
writeSidebarCollapsed(nextCollapsed);
};
const toggleCollapsed = () => {
setCollapsed(!collapsed);
};
return { collapsed, setCollapsed, toggleCollapsed };
}
export function writeSidebarCollapsed(collapsed: boolean, storage: Storage = window.localStorage) {
try {
storage.setItem(SIDEBAR_COLLAPSED_STORAGE_KEY, String(collapsed));
} catch {
// 存储不可用时仅使用当前内存状态
}
}

View File

@@ -0,0 +1,73 @@
import { useEffect, useState } from "react";
export type EffectiveTheme = "dark" | "light";
export type ThemePreference = "dark" | "light" | "system";
export const THEME_PREFERENCE_STORAGE_KEY = "theme.preference";
export const THEME_MEDIA_QUERY = "(prefers-color-scheme: dark)";
export function applyInitialThemePreference() {
applyThemeMode(resolveEffectiveTheme(readThemePreference(), getSystemPrefersDark()));
}
export function applyThemeMode(theme: EffectiveTheme, root: HTMLElement = document.documentElement) {
root.setAttribute("theme-mode", theme);
}
export function getSystemPrefersDark(matchMedia: Window["matchMedia"] = window.matchMedia): boolean {
try {
return matchMedia(THEME_MEDIA_QUERY).matches;
} catch {
return false;
}
}
export function parseThemePreference(value: unknown): ThemePreference {
return value === "dark" || value === "light" || value === "system" ? value : "system";
}
export function readThemePreference(storage: Storage = window.localStorage): ThemePreference {
try {
return parseThemePreference(storage.getItem(THEME_PREFERENCE_STORAGE_KEY));
} catch {
return "system";
}
}
export function resolveEffectiveTheme(preference: ThemePreference, systemPrefersDark: boolean): EffectiveTheme {
if (preference === "dark" || preference === "light") return preference;
return systemPrefersDark ? "dark" : "light";
}
export function useThemePreference() {
const [preference, setPreferenceState] = useState<ThemePreference>(() => readThemePreference());
const [systemPrefersDark, setSystemPrefersDark] = useState(() => getSystemPrefersDark());
const effectiveTheme = resolveEffectiveTheme(preference, systemPrefersDark);
useEffect(() => {
applyThemeMode(effectiveTheme);
}, [effectiveTheme]);
useEffect(() => {
const mediaQueryList = window.matchMedia(THEME_MEDIA_QUERY);
const handleChange = (event: MediaQueryListEvent) => setSystemPrefersDark(event.matches);
mediaQueryList.addEventListener("change", handleChange);
return () => mediaQueryList.removeEventListener("change", handleChange);
}, []);
const setPreference = (nextPreference: ThemePreference) => {
setPreferenceState(nextPreference);
writeThemePreference(nextPreference);
};
return { effectiveTheme, preference, setPreference };
}
export function writeThemePreference(preference: ThemePreference, storage: Storage = window.localStorage) {
try {
storage.setItem(THEME_PREFERENCE_STORAGE_KEY, preference);
} catch {
// 存储不可用时仅使用当前内存状态,避免阻断 Dashboard 渲染。
}
}