import { createContext, useCallback, useContext, useMemo, useRef, useState } from 'react' import type { ReactNode } from 'react' import './toast.css' export type ToastType = 'success' | 'error' | 'info' interface ToastItem { id: string message: string type: ToastType visible: boolean } interface ToastContextValue { showToast: (message: string, type?: ToastType, duration?: number) => void } const ToastContext = createContext(undefined) export function ToastProvider({ children }: { children: ReactNode }) { const [toasts, setToasts] = useState([]) const counterRef = useRef(0) const showToast = useCallback((message: string, type: ToastType = 'info', duration = 4000) => { const id = `toast_${Date.now()}_${counterRef.current++}` setToasts((prev) => [...prev, { id, message, type, visible: false }]) // Trigger enter animation on next frame. requestAnimationFrame(() => { setToasts((prev) => prev.map((t) => (t.id === id ? { ...t, visible: true } : t))) }) setTimeout(() => { setToasts((prev) => prev.map((t) => (t.id === id ? { ...t, visible: false } : t))) setTimeout(() => { setToasts((prev) => prev.filter((t) => t.id !== id)) }, 300) }, duration) }, []) const value = useMemo(() => ({ showToast }), [showToast]) return ( {children}
{toasts.map((t) => (
{t.message}
))}
) } export function useToast(): ToastContextValue { const ctx = useContext(ToastContext) if (!ctx) { throw new Error('useToast must be used within a ToastProvider') } return ctx }