| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354 |
- import { create } from "zustand";
- import { Button } from "./button";
- type ToastKind = "success" | "error" | "info";
- interface ToastMessage {
- id: string;
- title: string;
- kind: ToastKind;
- }
- const useToastStore = create<{
- items: ToastMessage[];
- push: (title: string, kind?: ToastKind) => void;
- remove: (id: string) => void;
- }>((set) => ({
- items: [],
- push: (title, kind = "info") => {
- const id = crypto.randomUUID();
- set((state) => ({ items: [...state.items, { id, title, kind }] }));
- window.setTimeout(() => set((state) => ({ items: state.items.filter((item) => item.id !== id) })), 3200);
- },
- remove: (id) => set((state) => ({ items: state.items.filter((item) => item.id !== id) })),
- }));
- export const toast = {
- success: (title: string) => useToastStore.getState().push(title, "success"),
- error: (title: string) => useToastStore.getState().push(title, "error"),
- info: (title: string) => useToastStore.getState().push(title, "info"),
- };
- export function Toaster() {
- const { items, remove } = useToastStore();
- return (
- <div className="fixed bottom-4 right-4 z-[60] flex w-80 flex-col gap-2">
- {items.map((item) => (
- <div
- key={item.id}
- className={[
- "rounded-md border bg-surface-elevated p-3 text-sm shadow-glow",
- item.kind === "success" ? "border-emerald-500/25" : item.kind === "error" ? "border-red-500/25" : "border-border",
- ].join(" ")}
- role={item.kind === "error" ? "alert" : "status"}
- >
- <div className="flex items-center justify-between gap-3">
- <span>{item.title}</span>
- <Button variant="ghost" size="sm" onClick={() => remove(item.id)}>
- Dismiss
- </Button>
- </div>
- </div>
- ))}
- </div>
- );
- }
|