SPB Git
1.2 KB · 15 lines tsx
1"use client";2import * as React from "react";3import { cn } from "@/lib/utils";4type T = { id: number; title: string; description?: string; tone?: "neutral" | "success" | "danger" | "ai" };5let push: ((t: Omit<T, "id">) => void) | null = null;6export function toast(title: string, opts: Omit<T, "id" | "title"> = {}) { push?.({ title, ...opts }); }7export function Toaster() {8  const [items, set] = React.useState<T[]>([]);9  React.useEffect(() => { push = (t) => { const id = Date.now() + Math.random(); set((s) => [...s, { ...t, id }]); setTimeout(() => set((s) => s.filter((x) => x.id !== id)), 4200); }; return () => { push = null; }; }, []);10  return <div className="pointer-events-none fixed bottom-4 right-4 z-[100] flex flex-col gap-2 safe-bottom">{items.map((t) => (11    <div key={t.id} className={cn("pointer-events-auto w-80 rounded-lg border bg-bg-elev p-3 shadow-pop fade-up", t.tone === "danger" ? "border-danger/40" : t.tone === "success" ? "border-success/40" : t.tone === "ai" ? "border-ai/40" : "border-line")}>12      <div className="text-sm font-medium">{t.title}</div>{t.description && <div className="mt-0.5 text-[13px] text-fg-muted">{t.description}</div>}13    </div>))}</div>;14}15
Files · main
Menu

Theme