import { useEffect, useRef, type HTMLAttributes } from "react"; import { createPortal } from "react-dom"; import { cn } from "./cn.js"; import { Icon, type IconName } from "./Icon.js"; export type ToastTone = "success" | "error" | "warning" | "info" | "loading"; export type ToastItem = { id: string; tone: ToastTone; title: string; description?: string; durationMs?: number | null; }; const toastIcons: Record = { success: "check", error: "alert", warning: "alert", info: "activity", loading: "refresh", }; const DEFAULT_TOAST_DURATION_MS = 10_000; export interface ToastCardProps extends HTMLAttributes { item: ToastItem; onDismiss?: (id: string) => void; } export function ToastCard({ item, onDismiss, className, ...props }: ToastCardProps) { return (
{item.title} {item.description ? {item.description} : null} {onDismiss ? ( ) : null}
); } function TimedToastCard({ item, onDismiss }: { item: ToastItem; onDismiss: (id: string) => void }) { const dismissRef = useRef(onDismiss); useEffect(() => { dismissRef.current = onDismiss; }, [onDismiss]); useEffect(() => { const duration = item.durationMs === undefined ? item.tone === "loading" ? null : DEFAULT_TOAST_DURATION_MS : item.durationMs; if (typeof duration !== "number" || duration <= 0) return undefined; const timer = window.setTimeout(() => dismissRef.current(item.id), duration); return () => window.clearTimeout(timer); }, [item.durationMs, item.id, item.tone]); return ; } export function ToastStack({ items, onDismiss }: { items: ToastItem[]; onDismiss: (id: string) => void }) { if (typeof document === "undefined" || items.length === 0) return null; return createPortal(
{items.map((item) => )}
, document.body, ); }