import { createContext, useContext, useState, useCallback, type ReactNode, type HTMLAttributes, } from "react"; export type ToastVariant = "success" | "error" | "warning" | "info"; export interface Toast { id: string; message: string; variant?: ToastVariant; duration?: number; } export interface ToastContextValue { showToast: (message: string, variant?: ToastVariant, duration?: number) => void; removeToast: (id: string) => void; } const ToastContext = createContext(null); export function useToast() { const context = useContext(ToastContext); if (!context) { throw new Error("useToast must be used within a ToastProvider"); } return context; } export interface ToastProviderProps { children: ReactNode; } export function ToastProvider({ children }: ToastProviderProps) { const [toasts, setToasts] = useState([]); const removeToast = useCallback((id: string) => { setToasts((prev) => prev.filter((toast) => toast.id !== id)); }, []); const showToast = useCallback( (message: string, variant: ToastVariant = "info", duration: number = 5000) => { const id = `toast-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; const newToast: Toast = { id, message, variant, duration }; setToasts((prev) => [...prev, newToast]); if (duration > 0) { setTimeout(() => { removeToast(id); }, duration); } }, [removeToast] ); return ( {children} ); } export interface ToastContainerProps extends HTMLAttributes { toasts: Toast[]; onRemove: (id: string) => void; } function ToastContainer({ toasts, onRemove, className = "" }: ToastContainerProps) { if (toasts.length === 0) return null; return (
{toasts.map((toast) => ( ))}
); } interface ToastItemProps { toast: Toast; onRemove: (id: string) => void; } function ToastItem({ toast, onRemove }: ToastItemProps) { const variantStyles: Record = { success: "bg-green-500 text-white border-green-600", error: "bg-red-500 text-white border-red-600", warning: "bg-yellow-500 text-white border-yellow-600", info: "bg-blue-500 text-white border-blue-600", }; const icon: Record = { success: ( ), error: ( ), warning: ( ), info: ( ), }; return (
{icon[toast.variant || "info"]} {toast.message}
); } // Helper function to show toasts outside of React components let toastContextValue: ToastContextValue | null = null; export function setToastContext(context: ToastContextValue | null) { toastContextValue = context; } export interface ToastOptions { variant?: ToastVariant; duration?: number; } export function toast(message: string, options?: ToastOptions) { if (!toastContextValue) { console.warn("Toast context not available. Make sure ToastProvider is mounted."); return; } toastContextValue.showToast( message, options?.variant || "info", options?.duration || 5000 ); }