"use client" import React, { useState, useRef } from 'react'; import { Copy } from 'lucide-react'; import { Button } from "@/components/ui/button"; import { motion, TargetAndTransition, Variants } from 'motion/react'; import { cn } from '@/lib/utils'; interface CopyButtonProps { onCopy: () => Promise | void; className?: string; } const copyIconVariants: Variants = { idle: { opacity: 1, scale: 1, transition: { duration: 0.2, ease: "easeOut" } }, copying: { opacity: 0, scale: 0.8, transition: { duration: 0.2, ease: "easeOut" } }, copied: { opacity: 0, scale: 0.8, transition: { duration: 0.2, ease: "easeOut" } } }; const checkIconVariants: Variants = { idle: { opacity: 0, scale: 0.8, transition: { duration: 0.2, ease: "easeOut" } }, copying: { opacity: 0, scale: 0.8, transition: { duration: 0.2, ease: "easeOut" } }, copied: { opacity: 1, scale: 1, transition: { duration: 0.2, ease: "easeOut" } } }; const checkPathVariants: Variants = { idle: { pathLength: 0, opacity: 0, transition: { duration: 0.2, ease: "easeOut" } }, copying: { pathLength: 0, opacity: 1, transition: { duration: 0.2, ease: "easeOut" } }, copied: { pathLength: 1, opacity: 1, transition: { duration: 0.3, ease: "easeOut" } } }; const MotionButton = motion.create(Button); export const CopyButton: React.FC = ({ onCopy, className }) => { const [status, setStatus] = useState<"idle" | "copying" | "copied">("idle"); const [backgroundState, setBackgroundState] = useState<"hidden" | "entering" | "centered" | "leaving">("hidden"); const [entryDirection, setEntryDirection] = useState({ x: 0, y: 0 }); const [leaveDirection, setLeaveDirection] = useState({ x: 0, y: 0 }); const buttonRef = useRef(null); const handleCopy = async () => { if (status !== "idle") return; setStatus("copying"); await onCopy(); setTimeout(() => { setStatus("copied"); }, 100); setTimeout(() => { setStatus("idle"); }, 2000); }; const calculateDirection = (e: React.MouseEvent) => { if (!buttonRef.current) return { x: 0, y: 0 }; const rect = buttonRef.current.getBoundingClientRect(); const centerX = rect.left + rect.width / 2; const centerY = rect.top + rect.height / 2; const mouseX = e.clientX; const mouseY = e.clientY; const offsetX = mouseX - centerX; const offsetY = mouseY - centerY; return { x: offsetX, y: offsetY }; }; const handleMouseEnter = (e: React.MouseEvent) => { const direction = calculateDirection(e); setEntryDirection(direction); // phase 1: instantly spawn at cursor position setBackgroundState("entering"); // phase 2: animate to center after a brief moment setTimeout(() => { setBackgroundState("centered"); }, 10); }; const handleMouseLeave = (e: React.MouseEvent) => { const direction = calculateDirection(e); setLeaveDirection(direction); // phase 3: animate to leave direction setBackgroundState("leaving"); // reset to hidden after animation completes setTimeout(() => { setBackgroundState("hidden"); }, 150); }; const getBackgroundAnimation = () => { switch (backgroundState) { case "hidden": return { opacity: 0, x: entryDirection.x, y: entryDirection.y, scale: 0.6, transition: { duration: 0 } }; case "entering": return { opacity: 0, x: entryDirection.x, y: entryDirection.y, scale: 0.6, transition: { duration: 0 } }; case "centered": return { opacity: 1, x: 0, y: 0, scale: 1, transition: { duration: 0.15, ease: "easeOut" } }; case "leaving": return { opacity: 0, x: leaveDirection.x, y: leaveDirection.y, scale: 1, transition: { duration: 0.15, ease: "easeOut" } }; default: return { opacity: 0, x: 0, y: 0, scale: 0.6, transition: { duration: 0 } }; } }; return (
{/* animated Background */}
{/* Copy Icon */} {/* Check Icon */}
); };