lightswind
Version:
A collection of beautifully crafted React Components, Blocks & Templates for Modern Developers. Create stunning web applications effortlessly by using our 160+ professional and animated react components.
213 lines (211 loc) • 9.68 kB
JavaScript
// @ts-nocheck
"use client";
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
import { useCallback, useEffect, useRef, useState } from "react";
import { Moon, Sun } from "lucide-react";
import { flushSync } from "react-dom";
import { cn } from "../../lib/utils";
// 3. Component and export are renamed
export const ToggleTheme = ({ className, duration = 400, animationType = "circle-spread", ...props }) => {
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;
// Wait for the DOM update to complete within the View Transition
await document.startViewTransition(() => {
flushSync(() => {
const newTheme = !isDark;
setIsDark(newTheme);
document.documentElement.classList.toggle("dark");
localStorage.setItem("theme", newTheme ? "dark" : "light");
});
}).ready;
// Calculate coordinates and dimensions for spatial animations
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));
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;
// 4. Implement a switch to handle all animation types
switch (animationType) {
// --- Existing/Refined Types ---
case "circle-spread":
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)",
});
break;
case "round-morph":
document.documentElement.animate([
{ opacity: 0, transform: "scale(0.8) rotate(5deg)" },
{ opacity: 1, transform: "scale(1) rotate(0deg)" },
], {
duration: duration * 1.2,
easing: "cubic-bezier(0.68, -0.55, 0.265, 1.55)",
pseudoElement: "::view-transition-new(root)",
});
break;
case "swipe-left":
document.documentElement.animate({
clipPath: [
`inset(0 0 0 ${viewportWidth}px)`,
`inset(0 0 0 0)`,
],
}, {
duration,
easing: "cubic-bezier(0.2, 0, 0, 1)",
pseudoElement: "::view-transition-new(root)",
});
break;
case "swipe-up":
document.documentElement.animate({
clipPath: [
`inset(${viewportHeight}px 0 0 0)`,
`inset(0 0 0 0)`,
],
}, {
duration,
easing: "cubic-bezier(0.2, 0, 0, 1)",
pseudoElement: "::view-transition-new(root)",
});
break;
// --- New Advanced Types ---
case "diag-down-right":
document.documentElement.animate({
clipPath: [
`polygon(0 0, 0 0, 0 0, 0 0)`,
`polygon(0 0, 100% 0, 100% 100%, 0 100%)`,
],
}, {
duration: duration * 1.5,
easing: "cubic-bezier(0.4, 0, 0.2, 1)",
pseudoElement: "::view-transition-new(root)",
});
break;
case "fade-in-out":
document.documentElement.animate({
opacity: [0, 1],
}, {
duration: duration * 0.5,
easing: "ease-in-out",
pseudoElement: "::view-transition-new(root)",
});
break;
case "shrink-grow":
document.documentElement.animate([
{ transform: "scale(0.9)", opacity: 0 },
{ transform: "scale(1)", opacity: 1 },
], {
duration: duration * 1.2,
easing: "cubic-bezier(0.19, 1, 0.22, 1)",
pseudoElement: "::view-transition-new(root)",
});
document.documentElement.animate([
{ transform: "scale(1)", opacity: 1 },
{ transform: "scale(1.05)", opacity: 0 },
], {
duration: duration * 1.2,
easing: "cubic-bezier(0.19, 1, 0.22, 1)",
pseudoElement: "::view-transition-old(root)",
});
break;
case "flip-x-in":
const styleElement = document.createElement('style');
styleElement.textContent = `
::view-transition-group(root) { perspective: 1000px; }
::view-transition-old(root) { transform-origin: center; animation: flip-out 400ms forwards; }
::view-transition-new(root) { transform-origin: center; animation: flip-in 400ms forwards; }
@keyframes flip-out { from { transform: rotateY(0deg); opacity: 1; } to { transform: rotateY(-90deg); opacity: 0; } }
@keyframes flip-in { from { transform: rotateY(90deg); opacity: 0; } to { transform: rotateY(0deg); opacity: 1; } }
`;
document.head.appendChild(styleElement);
break;
case "split-vertical":
document.documentElement.animate([{ opacity: 0 }, { opacity: 1 }], {
duration: duration * 0.75,
easing: "ease-in",
pseudoElement: "::view-transition-new(root)",
});
document.documentElement.animate([
{ clipPath: 'inset(0 0 0 0)', transform: 'none' },
{ clipPath: 'inset(0 40% 0 40%)', transform: 'scale(1.2)' },
{ clipPath: 'inset(0 50% 0 50%)', transform: 'scale(1)' },
], {
duration: duration * 1.5,
easing: "cubic-bezier(0.68, -0.55, 0.265, 1.55)",
pseudoElement: "::view-transition-old(root)",
});
break;
// --- IMPLEMENTATION FOR MISSING TYPES ---
case "swipe-right":
document.documentElement.animate({
clipPath: [
`inset(0 ${viewportWidth}px 0 0)`,
`inset(0 0 0 0)`,
],
}, {
duration,
easing: "cubic-bezier(0.2, 0, 0, 1)",
pseudoElement: "::view-transition-new(root)",
});
break;
case "swipe-down":
document.documentElement.animate({
clipPath: [
`inset(0 0 ${viewportHeight}px 0)`,
`inset(0 0 0 0)`,
],
}, {
duration,
easing: "cubic-bezier(0.2, 0, 0, 1)",
pseudoElement: "::view-transition-new(root)",
});
break;
case "wave-ripple":
document.documentElement.animate({
clipPath: [
`circle(0% at 50% 50%)`,
`circle(${maxRadius}px at 50% 50%)`,
],
}, {
duration: duration * 1.5,
easing: "cubic-bezier(0.68, -0.55, 0.265, 1.55)",
pseudoElement: "::view-transition-new(root)",
});
break;
case "none":
default:
// No custom animation runs
break;
}
}, [isDark, duration, animationType]);
return (_jsxs(_Fragment, { children: [_jsx("button", { ref: buttonRef, onClick: toggleTheme, className: cn("p-2 rounded-full transition-colors duration-300", isDark ? "hover:text-amber-400" : "hover:text-primarylw", className), ...props, children: isDark ? _jsx(Sun, { className: "h-6 w-6" }) : _jsx(Moon, { className: "h-6 w-6" }) }), animationType !== 'flip-x-in' && (_jsx("style", { dangerouslySetInnerHTML: {
__html: `
::view-transition-old(root),
::view-transition-new(root) {
animation: none;
mix-blend-mode: normal;
}
`,
} }))] }));
};