import React, { createContext, useContext, useState, useCallback, ReactNode, useEffect, useRef, useMemo } from 'react'; import { X, CheckCircle, AlertTriangle, Info, AlertCircle, Trash2 } from 'lucide-react'; // Toast Types export type ToastType = 'success' | 'error' | 'warning' | 'info' | 'delete'; export interface Toast { id: string; type: ToastType; message: string; description?: string; duration?: number; } interface ToastContextType { toasts: Toast[]; showToast: (type: ToastType, message: string, description?: string, duration?: number) => void; removeToast: (id: string) => void; success: (message: string, description?: string) => void; error: (message: string, description?: string) => void; warning: (message: string, description?: string) => void; info: (message: string, description?: string) => void; deleted: (message: string, description?: string) => void; addToast: (type: string, title: string, message?: string) => void; } const ToastContext = createContext(undefined); export const useToast = () => { const context = useContext(ToastContext); if (!context) { throw new Error('useToast must be used within a ToastProvider'); } return context; }; // Toast Component - DataVision Styled (Stable - No Blinking) const ToastItem = React.memo(({ toast, onRemove }: { toast: Toast; onRemove: (id: string) => void }) => { const [animationState, setAnimationState] = useState<'entering' | 'visible' | 'exiting'>('entering'); const timerRef = useRef | null>(null); const isMountedRef = useRef(true); useEffect(() => { isMountedRef.current = true; // Enter animation - use requestAnimationFrame for smoother transition requestAnimationFrame(() => { if (isMountedRef.current) { setAnimationState('visible'); } }); // Auto-remove after duration if (toast.duration && toast.duration > 0) { timerRef.current = setTimeout(() => { if (isMountedRef.current) { setAnimationState('exiting'); setTimeout(() => { if (isMountedRef.current) { onRemove(toast.id); } }, 200); } }, toast.duration); } return () => { isMountedRef.current = false; if (timerRef.current) clearTimeout(timerRef.current); }; }, []); // Empty deps - only run on mount const handleRemove = useCallback(() => { if (timerRef.current) clearTimeout(timerRef.current); setAnimationState('exiting'); setTimeout(() => onRemove(toast.id), 200); }, [toast.id, onRemove]); const configs = { success: { icon: , bg: 'bg-emerald-500/10', border: 'border-emerald-500/30', iconColor: 'text-emerald-400', }, error: { icon: , bg: 'bg-red-500/10', border: 'border-red-500/30', iconColor: 'text-red-400', }, warning: { icon: , bg: 'bg-amber-500/10', border: 'border-amber-500/30', iconColor: 'text-amber-400', }, info: { icon: , bg: 'bg-blue-500/10', border: 'border-blue-500/30', iconColor: 'text-blue-400', }, delete: { icon: , bg: 'bg-red-500/10', border: 'border-red-500/30', iconColor: 'text-red-400', }, }; const config = configs[toast.type]; const getAnimationClasses = () => { switch (animationState) { case 'entering': return 'opacity-0 translate-y-4 scale-95'; case 'exiting': return 'opacity-0 translate-x-8 scale-95'; default: return 'opacity-100 translate-y-0 translate-x-0 scale-100'; } }; return (
{config.icon}

{toast.message}

{toast.description && (

{toast.description}

)}
); }); ToastItem.displayName = 'ToastItem'; export const ToastProvider = ({ children }: { children: ReactNode }) => { const [toasts, setToasts] = useState([]); const toastIdCounter = useRef(0); const removeToast = useCallback((id: string) => { setToasts((prev) => prev.filter((t) => t.id !== id)); }, []); const showToast = useCallback((type: ToastType, message: string, description?: string, duration = 5000) => { const id = `toast-${++toastIdCounter.current}-${Date.now()}`; const newToast: Toast = { id, type, message, description, duration }; setToasts((prev) => [...prev, newToast]); }, []); const success = useCallback((message: string, description?: string) => showToast('success', message, description), [showToast]); const error = useCallback((message: string, description?: string) => showToast('error', message, description), [showToast]); const warning = useCallback((message: string, description?: string) => showToast('warning', message, description), [showToast]); const info = useCallback((message: string, description?: string) => showToast('info', message, description), [showToast]); const deleted = useCallback((message: string, description?: string) => showToast('delete', message, description, 4000), [showToast]); const addToast = useCallback((type: string, title: string, message?: string) => showToast((type as ToastType) || 'info', title, message), [showToast]); const contextValue = useMemo(() => ({ toasts, showToast, removeToast, success, error, warning, info, deleted, addToast }), [toasts, showToast, removeToast, success, error, warning, info, deleted, addToast]); return ( {children} {/* Toast Container - Bottom Right with DataVision styling */}
{toasts.map((toast) => ( ))}
); };