Spaces:
Sleeping
Sleeping
| "use client"; | |
| import { useEffect, useState } from "react"; | |
| import { CheckCircle2, Info, X, XCircle } from "lucide-react"; | |
| import { dismiss, subscribe, type Toast } from "@/lib/toast"; | |
| const STYLE = { | |
| success: { icon: CheckCircle2, ring: "ring-emerald-500/30", color: "text-emerald-400" }, | |
| error: { icon: XCircle, ring: "ring-rose-500/30", color: "text-rose-400" }, | |
| info: { icon: Info, ring: "ring-brand-500/30", color: "text-brand-300" }, | |
| } as const; | |
| export default function Toaster() { | |
| const [toasts, setToasts] = useState<Toast[]>([]); | |
| useEffect(() => subscribe(setToasts), []); | |
| return ( | |
| <div className="pointer-events-none fixed bottom-5 right-5 z-50 flex w-80 flex-col gap-2"> | |
| {toasts.map((t) => { | |
| const s = STYLE[t.kind]; | |
| const Icon = s.icon; | |
| return ( | |
| <div | |
| key={t.id} | |
| className={`pointer-events-auto flex animate-slide-in items-start gap-3 rounded-xl bg-surface-850/95 p-3.5 shadow-soft ring-1 ${s.ring} backdrop-blur`} | |
| > | |
| <Icon size={18} className={`mt-0.5 shrink-0 ${s.color}`} /> | |
| <div className="min-w-0 flex-1"> | |
| <p className="text-sm font-medium text-white">{t.title}</p> | |
| {t.description && ( | |
| <p className="mt-0.5 break-words text-xs text-white/50"> | |
| {t.description} | |
| </p> | |
| )} | |
| </div> | |
| <button | |
| onClick={() => dismiss(t.id)} | |
| className="shrink-0 text-white/30 transition hover:text-white" | |
| > | |
| <X size={14} /> | |
| </button> | |
| </div> | |
| ); | |
| })} | |
| </div> | |
| ); | |
| } | |