import { useState, useCallback } from 'react'; /** * Toast message type */ export type ToastType = 'success' | 'error' | 'warning' | 'info'; /** * Toast message object */ export interface Toast { id: string; type: ToastType; message: string; duration?: number; } /** * Toast configuration options */ export interface ToastOptions { /** * Duration in milliseconds before auto-dismiss * @default 5000 */ duration?: number; /** * Maximum number of toasts to show at once * @default 5 */ maxToasts?: number; } /** * Return type for useToast hook */ export interface UseToastReturn { /** * Array of active toasts */ toasts: Toast[]; /** * Show a success toast */ success: (message: string, duration?: number) => void; /** * Show an error toast */ error: (message: string, duration?: number) => void; /** * Show a warning toast */ warning: (message: string, duration?: number) => void; /** * Show an info toast */ info: (message: string, duration?: number) => void; /** * Show a toast with custom type */ show: (type: ToastType, message: string, duration?: number) => void; /** * Dismiss a toast by ID */ dismiss: (id: string) => void; /** * Dismiss all toasts */ dismissAll: () => void; } /** * Hook for managing toast notifications * * Provides a simple API for showing and managing toast messages with * automatic dismissal and maximum toast limits. * * @example * ```typescript * function MyComponent() { * const toast = useToast({ duration: 3000 }); * * const handleClick = () => { * toast.success('Operation successful!'); * }; * * return ( *
* * {toast.toasts.map((t) => ( * toast.dismiss(t.id)} * /> * ))} *
* ); * } * ``` */ export function useToast(options: ToastOptions = {}): UseToastReturn { const { duration: defaultDuration = 5000, maxToasts = 5 } = options; const [toasts, setToasts] = useState([]); const show = useCallback( (type: ToastType, message: string, duration: number = defaultDuration) => { const id = `toast-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; const newToast: Toast = { id, type, message, duration, }; setToasts((prev) => { // Add new toast and enforce maxToasts limit const updated = [...prev, newToast]; if (updated.length > maxToasts) { return updated.slice(updated.length - maxToasts); } return updated; }); // Auto-dismiss after duration if (duration > 0) { setTimeout(() => { setToasts((prev) => prev.filter((t) => t.id !== id)); }, duration); } }, [defaultDuration, maxToasts] ); const success = useCallback( (message: string, duration?: number) => { show('success', message, duration); }, [show] ); const error = useCallback( (message: string, duration?: number) => { show('error', message, duration); }, [show] ); const warning = useCallback( (message: string, duration?: number) => { show('warning', message, duration); }, [show] ); const info = useCallback( (message: string, duration?: number) => { show('info', message, duration); }, [show] ); const dismiss = useCallback((id: string) => { setToasts((prev) => prev.filter((t) => t.id !== id)); }, []); const dismissAll = useCallback(() => { setToasts([]); }, []); return { toasts, success, error, warning, info, show, dismiss, dismissAll, }; }