"use client"; import { AnimatePresence, motion, PanInfo } from "framer-motion"; import { useEffect, useRef, useState } from "react"; export interface ToastData { id: string; message: string; type: "success" | "error" | "info" | "warning"; } interface ToastProps { toasts: ToastData[]; removeToast: (id: string) => void; } const TOAST_DURATION = 5000; const SWIPE_THRESHOLD = 100; export default function Toast({ toasts, removeToast }: ToastProps) { return (
{toasts.map((toast) => ( removeToast(toast.id)} /> ))}
); } function ToastItem({ toast, onRemove, }: { toast: ToastData; onRemove: () => void; }) { const [isPaused, setIsPaused] = useState(false); const [progress, setProgress] = useState(100); const shouldRemoveRef = useRef(false); useEffect(() => { if (isPaused) return; const interval = setInterval(() => { setProgress((prev) => { const newProgress = prev - 100 / (TOAST_DURATION / 10); if (newProgress <= 0) { shouldRemoveRef.current = true; return 0; } return newProgress; }); }, 10); return () => clearInterval(interval); }, [isPaused]); useEffect(() => { if (shouldRemoveRef.current) { onRemove(); } }, [progress, onRemove]); const handleDragEnd = (_: unknown, info: PanInfo) => { if (Math.abs(info.offset.x) > SWIPE_THRESHOLD) { onRemove(); } }; const config = { success: { icon: ( ), color: "text-green-500", progressColor: "bg-green-500", }, error: { icon: ( ), color: "text-red-500", progressColor: "bg-red-500", }, info: { icon: ( ), color: "text-blue-500", progressColor: "bg-blue-500", }, warning: { icon: ( ), color: "text-yellow-500", progressColor: "bg-yellow-500", }, }[toast.type]; return ( setIsPaused(true)} onMouseLeave={() => setIsPaused(false)} className="relative bg-white dark:bg-gray-800 rounded-lg shadow-lg overflow-hidden border border-gray-200 dark:border-gray-700 cursor-grab active:cursor-grabbing" >
{config.icon}

{toast.message}

{/* Progress bar */}
); }