"use client"; import { useEffect, useState } from "react"; import { usePathname } from "next/navigation"; import { Moon, Sun } from "lucide-react"; type Theme = "light" | "dark"; function readThemeCookie(): Theme { if (typeof document === "undefined") return "light"; return document.cookie.match(/(?:^|;\s*)j13-theme=dark/) ? "dark" : "light"; } function applyTheme(theme: Theme) { document.documentElement.classList.toggle("dark", theme === "dark"); document.cookie = `j13-theme=${theme}; path=/; max-age=31536000`; } /** * 明暗主题切换:cookie 为真相源;公开顶栏与后台顶栏共用。 * 首屏严格用 initialTheme,挂载后再按 cookie 校正,避免 hydration 不一致。 */ export default function ThemeToggle({ initialTheme = "light", className, style, /** icon = 仅图标按钮;labeled = 带「浅色/深色模式」文案(移动抽屉用) */ labelMode = "icon", iconSize = 17, }: { initialTheme?: Theme; className?: string; style?: React.CSSProperties; labelMode?: "icon" | "labeled"; iconSize?: number; }) { const pathname = usePathname(); const [theme, setTheme] = useState(initialTheme); const [ready, setReady] = useState(false); useEffect(() => { const saved = readThemeCookie(); document.documentElement.classList.toggle("dark", saved === "dark"); setTheme(saved); setReady(true); }, [pathname]); const toggle = () => { const next: Theme = theme === "dark" ? "light" : "dark"; setTheme(next); applyTheme(next); }; // 未挂载完成前始终展示 SSR 初值,保证与服务端 HTML 一致 const shown = ready ? theme : initialTheme; if (labelMode === "labeled") { return ( ); } return ( ); }