import { Moon, Sun } from "lucide-react"; import { useTheme } from "next-themes"; import { useEffect, useState } from "react"; import { flushSync } from "react-dom"; import { Button } from "~/components/ui/button"; import { cn } from "~/lib/utils"; export function ThemeToggle({ className }: { className?: string }) { const [mounted, setMounted] = useState(false); const { resolvedTheme, setTheme } = useTheme(); useEffect(() => { setMounted(true); }, []); const isDark = mounted && resolvedTheme === "dark"; const nextTheme = isDark ? "light" : "dark"; // 渐进式主题过渡:整页新旧快照交叉淡化(View Transitions 默认行为, // plus-lighter 混合,中途无灰闪)。.theme-vt 只在本次过渡期间挂在 html // 上,给 app.css 一个作用域去放缓时长/缓动,不影响其它 view transition // (如 play-card)。不支持或用户偏好减弱动效时,退回直接切换。 function handleToggle() { const prefersReduced = window.matchMedia("(prefers-reduced-motion: reduce)").matches; if (!document.startViewTransition || prefersReduced) { setTheme(nextTheme); return; } const root = document.documentElement; root.classList.add("theme-vt"); // flushSync:startViewTransition 的回调返回后浏览器就拍新快照, // 必须让 React 同步把主题类落到 DOM,不能等下一轮调度 const vt = document.startViewTransition(() => { flushSync(() => setTheme(nextTheme)); }); void vt.finished.finally(() => root.classList.remove("theme-vt")); } return ( ); }