'use client' import * as React from 'react' import * as RadixToast from '@radix-ui/react-toast' import { X, CheckCircle2, AlertCircle, AlertTriangle, Info } from 'lucide-react' import { cva, type VariantProps } from 'class-variance-authority' import { cn } from '@/lib/utils' // ── Types ──────────────────────────────────────────────────────────────────── export type ToastVariant = 'default' | 'success' | 'error' | 'warning' | 'info' export interface ToastData { id: string title: string description?: string variant?: ToastVariant duration?: number } // ── Store (singleton for imperative toasts) ─────────────────────────────────── type Listener = (toasts: ToastData[]) => void let toastList: ToastData[] = [] const listeners = new Set() function emit() { listeners.forEach((fn) => fn([...toastList])) } export const toast = { show(data: Omit) { const id = Math.random().toString(36).slice(2, 9) toastList = [...toastList, { id, ...data }] emit() return id }, success(title: string, description?: string) { return this.show({ title, description, variant: 'success' }) }, error(title: string, description?: string) { return this.show({ title, description, variant: 'error' }) }, warning(title: string, description?: string) { return this.show({ title, description, variant: 'warning' }) }, info(title: string, description?: string) { return this.show({ title, description, variant: 'info' }) }, dismiss(id: string) { toastList = toastList.filter((t) => t.id !== id) emit() }, } function useToastStore() { const [toasts, setToasts] = React.useState([]) React.useEffect(() => { setToasts([...toastList]) listeners.add(setToasts) return () => { listeners.delete(setToasts) } }, []) return toasts } // ── Style variants ──────────────────────────────────────────────────────────── const toastVariants = cva( [ 'group pointer-events-auto relative flex w-full items-start gap-3 overflow-hidden', 'rounded-lg border p-4 shadow-lg transition-all', 'data-[state=open]:animate-slide-up data-[state=closed]:animate-slide-down-out', 'data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)]', 'data-[swipe=cancel]:translate-x-0', 'data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=end]:animate-fade-out', ].join(' '), { variants: { variant: { default: 'bg-surface-800 border-surface-700 text-surface-100', success: 'bg-surface-800 border-green-800/60 text-surface-100', error: 'bg-surface-800 border-red-800/60 text-surface-100', warning: 'bg-surface-800 border-yellow-800/60 text-surface-100', info: 'bg-surface-800 border-blue-800/60 text-surface-100', }, }, defaultVariants: { variant: 'default' }, } ) const variantIcons: Record = { default: null, success: