{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "toast",
  "title": "Toast",
  "description": "Short messages that stack in a corner and leave on their own. Call toast() from anywhere.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/manniche/toast/toast.tsx",
      "content": "import { CircleAlert, CircleCheck, X } from 'lucide-react'\nimport { useEffect, useState, useSyncExternalStore } from 'react'\nimport { cn } from '@/lib/utils'\n\ntype Tone = 'default' | 'success' | 'error'\n\ntype Toast = {\n  id: number\n  title: string\n  description?: string\n  tone: Tone\n  /** Milliseconds before it leaves. Infinity keeps it until closed. */\n  duration: number\n  action?: { label: string; onClick: () => void }\n  leaving?: boolean\n}\n\nexport type ToastOptions = Partial<Pick<Toast, 'description' | 'tone' | 'duration' | 'action'>>\n\nlet items: Toast[] = []\nlet nextId = 1\nconst listeners = new Set<() => void>()\nconst emit = () => listeners.forEach((l) => l())\n\n/** Show a toast from anywhere. Returns its id, which `dismiss` takes. */\nexport function toast(title: string, options: ToastOptions = {}) {\n  const id = nextId++\n  const item: Toast = { id, title, tone: 'default', duration: 4000, ...options }\n  items = [...items, item].slice(-4)\n  emit()\n  return id\n}\n\nexport function dismiss(id: number) {\n  items = items.map((t) => (t.id === id ? { ...t, leaving: true } : t))\n  emit()\n  // Matches the leave animation; under reduced motion the item is simply gone a moment later.\n  setTimeout(() => {\n    items = items.filter((t) => t.id !== id)\n    emit()\n  }, 200)\n}\n\nfunction subscribe(l: () => void) {\n  listeners.add(l)\n  return () => listeners.delete(l)\n}\n\nfunction Item({ t }: { t: Toast }) {\n  const [paused, setPaused] = useState(false)\n\n  useEffect(() => {\n    if (paused || t.leaving || !Number.isFinite(t.duration)) return\n    const id = setTimeout(() => dismiss(t.id), t.duration)\n    return () => clearTimeout(id)\n  }, [paused, t.leaving, t.duration, t.id])\n\n  return (\n    <li\n      onPointerEnter={() => setPaused(true)}\n      onPointerLeave={() => setPaused(false)}\n      onFocus={() => setPaused(true)}\n      onBlur={() => setPaused(false)}\n      className={cn(\n        'pointer-events-auto flex w-full items-start gap-3 rounded-xl border bg-card p-3 pl-4 text-card-foreground shadow-lg',\n        t.leaving\n          ? 'animate-[manniche-toast-out_200ms_ease-in_forwards]'\n          : 'animate-[manniche-toast-in_300ms_cubic-bezier(0.23,1,0.32,1)]',\n        'motion-reduce:animate-none',\n      )}\n    >\n      {t.tone === 'success' && <CircleCheck className=\"mt-0.5 size-5 shrink-0 text-success\" aria-hidden />}\n      {t.tone === 'error' && <CircleAlert className=\"mt-0.5 size-5 shrink-0 text-destructive\" aria-hidden />}\n      <div className=\"min-w-0 flex-1 py-0.5\">\n        <p className=\"font-medium\">{t.title}</p>\n        {t.description && <p className=\"mt-0.5 text-sm text-muted-foreground\">{t.description}</p>}\n      </div>\n      {t.action && (\n        <button\n          type=\"button\"\n          onClick={() => {\n            t.action!.onClick()\n            dismiss(t.id)\n          }}\n          className=\"min-h-9 shrink-0 rounded-lg bg-muted px-3 text-sm font-medium transition-colors duration-150 hover:bg-accent\"\n        >\n          {t.action.label}\n        </button>\n      )}\n      <button\n        type=\"button\"\n        onClick={() => dismiss(t.id)}\n        className=\"-my-1 grid size-9 shrink-0 place-items-center rounded-lg text-muted-foreground transition-colors duration-150 hover:bg-muted hover:text-foreground\"\n        aria-label=\"Close\"\n      >\n        <X className=\"size-4\" aria-hidden />\n      </button>\n    </li>\n  )\n}\n\n/** Put one Toaster near the root of the app. Toasts stack in the bottom right corner. */\nexport function Toaster({ className }: { className?: string }) {\n  const list = useSyncExternalStore(subscribe, () => items, () => items)\n\n  return (\n    <section aria-label=\"Notifications\" className={cn('pointer-events-none fixed right-4 bottom-4 left-4 z-50 sm:left-auto sm:w-96', className)}>\n      <style href=\"manniche-toast\" precedence=\"default\">\n        {'@keyframes manniche-toast-in { from { opacity: 0; transform: translateY(12px) scale(.97) } }' +\n          '@keyframes manniche-toast-out { to { opacity: 0; transform: translateY(6px) scale(.97) } }'}\n      </style>\n      <ol aria-live=\"polite\" className=\"flex flex-col gap-2\">\n        {list.map((t) => (\n          <Item key={t.id} t={t} />\n        ))}\n      </ol>\n    </section>\n  )\n}\n",
      "type": "registry:component"
    }
  ],
  "categories": [
    "overlays"
  ],
  "type": "registry:component"
}