"use client"; import { useCallback, useEffect, useRef, useState } from "react"; import { flushSync } from "react-dom"; import { buttonVariants } from "@/components/ui/button"; import { Tooltip, TooltipContent, TooltipTrigger, } from "@/components/ui/tooltip"; import { cn } from "@/lib/utils/ui"; import { RiMoonClearLine, RiSunLine } from "@remixicon/react"; interface AnimatedThemeTogglerProps extends React.ComponentPropsWithoutRef<"button"> { duration?: number; } export const AnimatedThemeToggler = ({ className, duration = 400, ...props }: AnimatedThemeTogglerProps) => { const [isDark, setIsDark] = useState(false); const buttonRef = useRef(null); useEffect(() => { const updateTheme = () => { setIsDark(document.documentElement.classList.contains("dark")); }; updateTheme(); const observer = new MutationObserver(updateTheme); observer.observe(document.documentElement, { attributes: true, attributeFilter: ["class"], }); return () => observer.disconnect(); }, []); const toggleTheme = useCallback(async () => { if (!buttonRef.current) return; await document.startViewTransition(() => { flushSync(() => { const newTheme = !isDark; setIsDark(newTheme); document.documentElement.classList.toggle("dark"); localStorage.setItem("theme", newTheme ? "dark" : "light"); }); }).ready; const { top, left, width, height } = buttonRef.current.getBoundingClientRect(); const x = left + width / 2; const y = top + height / 2; const maxRadius = Math.hypot( Math.max(left, window.innerWidth - left), Math.max(top, window.innerHeight - top) ); document.documentElement.animate( { clipPath: [ `circle(0px at ${x}px ${y}px)`, `circle(${maxRadius}px at ${x}px ${y}px)`, ], }, { duration, easing: "ease-in-out", pseudoElement: "::view-transition-new(root)", } ); }, [isDark, duration]); return ( {isDark ? "Tema claro" : "Tema escuro"} ); };