"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 */}
);
}