import * as React from 'react'; import { createPortal } from 'react-dom'; import { cn } from '../../lib/utils'; type Toast = { id: string; title?: string; message: string; type?: 'success' | 'error' | 'info'; }; export function useToaster(inlineHost?: HTMLElement | null) { const [toasts, setToasts] = React.useState([]); const [mounted, setMounted] = React.useState(false); React.useEffect(() => { setMounted(true); }, []); const remove = React.useCallback((id: string) => { setToasts((prev) => prev.map((t) => (t.id === id ? { ...t, _removing: true } : t)) ); setTimeout(() => { setToasts((prev) => prev.filter((t) => t.id !== id)); }, 200); }, []); const push = React.useCallback( (t: Omit) => { const id = `toast-${Date.now()}-${Math.random() .toString(36) .slice(2, 9)}`; setToasts((prev) => [...prev, { id, ...t }]); setTimeout(() => remove(id), 5000); }, [remove] ); if (!mounted) { return { push, portal: null } as any; } const isInline = !!inlineHost; const containerClass = cn( isInline ? 'mb-4 space-y-2 max-w-[576px]' : 'fixed top-4 right-4 z-[100005] space-y-2 pointer-events-none' ); const portalContent = (
{toasts.map((t) => (
{t.title && (
{t.title}
)}
{t.message}
))}
); if (isInline && inlineHost) { const portal = createPortal(portalContent, inlineHost); return { push, portal } as const; } const portal = createPortal(portalContent, document.body); return { push, portal } as const; }