"use client"; import { AlertTriangle, CheckCircle2, Info, X } from "lucide-react"; import { useEffect, useState } from "react"; import { cn } from "@/lib/utils"; type ToastTone = "signal" | "vermilion" | "neutral"; interface Toast { id: number; title: string; description?: string; tone: ToastTone; } let nextId = 1; const listeners = new Set<(t: Toast[]) => void>(); let store: Toast[] = []; function emit() { store = [...store]; listeners.forEach((l) => l(store)); } export function toast(t: { title: string; description?: string; tone?: ToastTone; }) { const item: Toast = { id: nextId++, tone: t.tone ?? "neutral", ...t }; store = [...store, item]; listeners.forEach((l) => l(store)); setTimeout(() => { store = store.filter((x) => x.id !== item.id); emit(); }, 4200); } export function useToast() { return { toast }; } const icons = { signal: CheckCircle2, vermilion: AlertTriangle, neutral: Info, }; export function Toaster({ position = "bottom-right" }: { position?: string }) { const [items, setItems] = useState([]); useEffect(() => { listeners.add(setItems); setItems(store); return () => { listeners.delete(setItems); }; }, []); const pos = position === "bottom-right" ? "bottom-[calc(1rem+env(safe-area-inset-bottom))] right-[calc(1rem+env(safe-area-inset-right))]" : position === "top-right" ? "top-4 right-4" : "bottom-4 left-1/2 -translate-x-1/2"; return (
{items.map((t) => { const Icon = icons[t.tone]; return (
{t.title}
{t.description && (
{t.description}
)}
); })}
); }