/** * Component: Toast Notification System * Documentation: documentation/frontend/components.md */ 'use client'; import { createContext, useContext, useState, useCallback, ReactNode } from 'react'; export type ToastType = 'success' | 'error' | 'info' | 'warning'; export interface Toast { id: string; message: string; type: ToastType; duration?: number; } interface ToastContextType { toasts: Toast[]; addToast: (message: string, type: ToastType, duration?: number) => void; removeToast: (id: string) => void; success: (message: string, duration?: number) => void; error: (message: string, duration?: number) => void; info: (message: string, duration?: number) => void; warning: (message: string, duration?: number) => void; } const ToastContext = createContext(undefined); export function ToastProvider({ children }: { children: ReactNode }) { const [toasts, setToasts] = useState([]); const removeToast = useCallback((id: string) => { setToasts((prev) => prev.filter((toast) => toast.id !== id)); }, []); const addToast = useCallback( (message: string, type: ToastType, duration: number = 5000) => { const id = `toast-${Date.now()}-${Math.random()}`; const newToast: Toast = { id, message, type, duration }; setToasts((prev) => [...prev, newToast]); if (duration > 0) { setTimeout(() => { removeToast(id); }, duration); } }, [removeToast] ); const success = useCallback( (message: string, duration?: number) => addToast(message, 'success', duration), [addToast] ); const error = useCallback( (message: string, duration?: number) => addToast(message, 'error', duration), [addToast] ); const info = useCallback( (message: string, duration?: number) => addToast(message, 'info', duration), [addToast] ); const warning = useCallback( (message: string, duration?: number) => addToast(message, 'warning', duration), [addToast] ); return ( {children} ); } export function useToast() { const context = useContext(ToastContext); if (context === undefined) { throw new Error('useToast must be used within a ToastProvider'); } return context; } function ToastContainer({ toasts, onRemove, }: { toasts: Toast[]; onRemove: (id: string) => void; }) { if (toasts.length === 0) return null; return (
{toasts.map((toast) => ( ))}
); } function ToastItem({ toast, onRemove }: { toast: Toast; onRemove: (id: string) => void }) { const colors = { success: 'bg-green-50 dark:bg-green-900/30 border-green-200 dark:border-green-800 text-green-800 dark:text-green-200', error: 'bg-red-50 dark:bg-red-900/30 border-red-200 dark:border-red-800 text-red-800 dark:text-red-200', warning: 'bg-yellow-50 dark:bg-yellow-900/30 border-yellow-200 dark:border-yellow-800 text-yellow-800 dark:text-yellow-200', info: 'bg-blue-50 dark:bg-blue-900/30 border-blue-200 dark:border-blue-800 text-blue-800 dark:text-blue-200', }; const icons = { success: ( ), error: ( ), warning: ( ), info: ( ), }; return (
{icons[toast.type]}
{toast.message}
); }