'use client'; import { cn } from '@/lib/utils'; import { Undo2 } from 'lucide-react'; import { useEffect, useState, type FC, type ReactNode } from 'react'; import { AnimatePresence, motion, MotionConfig } from 'motion/react'; import useMeasure from 'react-use-measure'; export interface TimedUndoActionProps { initialSeconds?: number; deleteLabel?: string; undoLabel?: string; icon?: ReactNode; onConfirm?: () => void; compact?: boolean; /** Controlled mode: external isDeleting state */ isDeleting?: boolean; /** Controlled mode: external countdown value */ countDown?: number; /** Controlled mode: called when user clicks to toggle */ onToggle?: () => void; } export const TimedUndoAction: FC = ({ initialSeconds = 10, deleteLabel = 'Delete Account', undoLabel = 'Cancel Delete', icon, onConfirm, compact = false, isDeleting: controlledIsDeleting, countDown: controlledCountDown, onToggle, }) => { const isControlled = controlledIsDeleting !== undefined; const [uncontrolledIsDeleting, setUncontrolledIsDeleting] = useState(false); const [uncontrolledCountDown, setUncontrolledCountDown] = useState(initialSeconds); const [ref, bounds] = useMeasure({ offsetSize: true }); const isDeleting = isControlled ? controlledIsDeleting! : uncontrolledIsDeleting; const countDown = isControlled ? (controlledCountDown ?? initialSeconds) : uncontrolledCountDown; const handleClick = () => { if (isControlled) { onToggle?.(); } else { setUncontrolledIsDeleting((prev) => { const next = !prev; if (next) setUncontrolledCountDown(initialSeconds); return next; }); } }; // Uncontrolled: tick the countdown useEffect(() => { if (isControlled || !uncontrolledIsDeleting) return; const id = setInterval(() => { setUncontrolledCountDown((p) => Math.max(0, p - 1)); }, 1000); return () => clearInterval(id); }, [isControlled, uncontrolledIsDeleting]); // Uncontrolled: fire onConfirm when countdown hits 0 useEffect(() => { if (isControlled || !uncontrolledIsDeleting || uncontrolledCountDown > 0) return; setUncontrolledIsDeleting(false); setUncontrolledCountDown(initialSeconds); onConfirm?.(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [isControlled, uncontrolledIsDeleting, uncontrolledCountDown]); return (
0 ? bounds.width : 'auto', }} onClick={handleClick} >
{isDeleting && ( {icon ?? } )}
{isDeleting && ( {countDown} )}
); }; function AnimatedText({ text, className, delayStep = 0.014, }: { text: string; className?: string; delayStep?: number; }) { const chars = text.split(''); return ( {chars.map((char, i) => ( {char} ))} ); } export default TimedUndoAction;