{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "toast",
  "title": "Toast",
  "description": "Growl-style notifications: call toast() and an Alert slides in from the corner.",
  "dependencies": [
    "lucide-react"
  ],
  "devDependencies": [
    "tw-animate-css"
  ],
  "registryDependencies": [
    "@aqua/alert"
  ],
  "files": [
    {
      "path": "registry/aqua/ui/toast.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { XIcon } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Alert, AlertDescription, AlertTitle } from \"@/registry/aqua/ui/alert\"\n\ntype ToastVariant = \"default\" | \"warning\" | \"destructive\"\n\nexport type ToastOptions = {\n  title: string\n  description?: string\n  variant?: ToastVariant\n  duration?: number\n}\n\ntype ToastItem = ToastOptions & {\n  id: number\n  leaving: boolean\n}\n\nlet toastCount = 0\nlet toasts: ToastItem[] = []\nconst listeners = new Set<(items: ToastItem[]) => void>()\n\nfunction emit() {\n  for (const listener of listeners) listener(toasts)\n}\n\nfunction subscribe(listener: (items: ToastItem[]) => void) {\n  listeners.add(listener)\n  return () => {\n    listeners.delete(listener)\n  }\n}\n\nfunction getSnapshot() {\n  return toasts\n}\n\nfunction toast(options: ToastOptions) {\n  const id = ++toastCount\n  toasts = [...toasts, { duration: 5000, ...options, id, leaving: false }]\n  emit()\n  return id\n}\n\nfunction dismissToast(id: number) {\n  if (!toasts.some((item) => item.id === id && !item.leaving)) return\n  toasts = toasts.map((item) =>\n    item.id === id ? { ...item, leaving: true } : item\n  )\n  emit()\n  setTimeout(() => {\n    toasts = toasts.filter((item) => item.id !== id)\n    emit()\n  }, 200)\n}\n\nfunction Toaster({ className }: { className?: string }) {\n  const items = React.useSyncExternalStore(subscribe, getSnapshot, getSnapshot)\n  const scheduled = React.useRef(new Set<number>())\n\n  React.useEffect(() => {\n    for (const item of items) {\n      if (item.leaving || scheduled.current.has(item.id)) continue\n      scheduled.current.add(item.id)\n      setTimeout(() => dismissToast(item.id), item.duration)\n    }\n  }, [items])\n\n  return (\n    <div\n      className={cn(\n        \"pointer-events-none fixed right-4 top-4 z-[100] flex w-80 max-w-[calc(100vw-2rem)] flex-col gap-2.5\",\n        className\n      )}\n    >\n      {items.map((item) => (\n        <Alert\n          key={item.id}\n          variant={item.variant}\n          className={cn(\n            \"group pointer-events-auto relative shadow-[0_1px_3px_rgba(20,30,50,0.12),0_10px_28px_rgba(20,30,50,0.3)]\",\n            item.leaving\n              ? \"animate-out fade-out-0 slide-out-to-right-8 fill-mode-forwards duration-200\"\n              : \"animate-in fade-in-0 slide-in-from-right-8 duration-300\"\n          )}\n        >\n          <AlertTitle>{item.title}</AlertTitle>\n          {item.description ? (\n            <AlertDescription>{item.description}</AlertDescription>\n          ) : null}\n          <button\n            type=\"button\"\n            aria-label=\"Dismiss notification\"\n            onClick={() => dismissToast(item.id)}\n            className=\"absolute -left-2 -top-2 flex size-5 items-center justify-center rounded-full border border-[#5a5f66] bg-[radial-gradient(circle_at_35%_30%,#9aa0a8_0%,#5f646b_60%,#43474d_100%)] text-white opacity-0 shadow-[inset_0_1px_0_rgba(255,255,255,0.4),0_1px_2px_rgba(20,30,50,0.4)] transition-opacity focus-visible:opacity-100 focus-visible:outline-none focus-visible:ring-[3px] focus-visible:ring-[var(--aqua-ring,#6cb0f7)]/70 group-hover:opacity-100\"\n          >\n            <XIcon className=\"size-3\" strokeWidth={3} />\n          </button>\n        </Alert>\n      ))}\n    </div>\n  )\n}\n\nexport { toast, dismissToast, Toaster }\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}