import { createContext, useContext, useState, useCallback, useRef } from 'react'; import type { ReactNode } from 'react'; import { X, CheckCircle, AlertCircle, AlertTriangle, Info } from 'lucide-react'; // Types type ToastType = 'success' | 'error' | 'warning' | 'info'; interface Toast { id: string; type: ToastType; message: ReactNode; duration?: number; onClose?: () => void; } interface ToastContextValue { showToast: (message: ReactNode, type?: ToastType, duration?: number, onClose?: () => void) => string; success: (message: ReactNode, duration?: number, onClose?: () => void) => string; error: (message: ReactNode, duration?: number, onClose?: () => void) => string; warning: (message: ReactNode, duration?: number, onClose?: () => void) => string; info: (message: ReactNode, duration?: number, onClose?: () => void) => string; removeToast: (id: string) => void; } // Context const ToastContext = createContext(null); // Hook export function useToast() { const context = useContext(ToastContext); if (!context) { throw new Error('useToast must be used within a ToastProvider'); } return context; } // Icons map const icons = { success: , error: , warning: , info: , }; // Styles map const styles = { success: 'border-green-500/20 bg-green-900/90 text-green-100', error: 'border-red-500/20 bg-red-900/90 text-red-100', warning: 'border-amber-500/20 bg-amber-900/90 text-amber-100', info: 'border-blue-500/20 bg-blue-900/90 text-blue-100', }; // Provider Component export function ToastProvider({ children }: { children: React.ReactNode }) { const [toasts, setToasts] = useState([]); const timersRef = useRef>(new Map()); const removeToast = useCallback((id: string) => { setToasts((prev) => { const toast = prev.find(t => t.id === id); if (toast && toast.onClose) { toast.onClose(); } return prev.filter((t) => t.id !== id); }); if (timersRef.current.has(id)) { clearTimeout(timersRef.current.get(id)); timersRef.current.delete(id); } }, []); const showToast = useCallback((message: ReactNode, type: ToastType = 'info', duration = 3000, onClose?: () => void) => { const id = Math.random().toString(36).substring(2, 9); const newToast: Toast = { id, type, message, duration, onClose }; setToasts((prev) => [...prev, newToast]); if (duration > 0) { const timer = window.setTimeout(() => { removeToast(id); }, duration); timersRef.current.set(id, timer); } return id; }, [removeToast]); // Convenience methods const success = useCallback((msg: ReactNode, d?: number, onClose?: () => void) => showToast(msg, 'success', d, onClose), [showToast]); const error = useCallback((msg: ReactNode, d = 3000, onClose?: () => void) => showToast(msg, 'error', d, onClose), [showToast]); const warning = useCallback((msg: ReactNode, d = 3000, onClose?: () => void) => showToast(msg, 'warning', d, onClose), [showToast]); const info = useCallback((msg: ReactNode, d?: number, onClose?: () => void) => showToast(msg, 'info', d, onClose), [showToast]); return ( {children} {/* Toast Container - Top Right */}
{toasts.map((toast) => (
{icons[toast.type]}
{toast.message}
))}
); }