{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "toast",
  "type": "registry:ui",
  "dependencies": [
    "class-variance-authority",
    "lucide-react"
  ],
  "registryDependencies": [
    "https://ui.getlyse.com/r/lyse-tokens.json"
  ],
  "files": [
    {
      "path": "registry/new-york/ui/toast/toast.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\nimport { CircleCheck, CircleAlert, Info, TriangleAlert, X } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport \"./toast.css\"\n\n/* ------------------------------------------------------------------ */\n/*  Variant icons                                                      */\n/* ------------------------------------------------------------------ */\n\nconst variantIcons = {\n  brand: Info,\n  success: CircleCheck,\n  danger: CircleAlert,\n  warning: TriangleAlert,\n} as const\n\n/* ------------------------------------------------------------------ */\n/*  CVA                                                                */\n/* ------------------------------------------------------------------ */\n\nconst toastVariants = cva(\n  \"toast-base inline-flex w-[400px] items-center gap-[var(--layout-gap-md)] overflow-hidden rounded-[var(--layout-radius-xl)] p-[var(--layout-padding-lg)] text-content-note font-accent\",\n  {\n    variants: {\n      variant: {\n        brand: \"toast-brand\",\n        success: \"toast-success\",\n        danger: \"toast-danger\",\n        warning: \"toast-warning\",\n      },\n    },\n    defaultVariants: {\n      variant: \"success\",\n    },\n  }\n)\n\n/* ------------------------------------------------------------------ */\n/*  Toast                                                              */\n/* ------------------------------------------------------------------ */\n\nfunction Toast({\n  className,\n  variant = \"success\",\n  children,\n  onClose,\n  ...props\n}: React.ComponentProps<\"div\"> &\n  VariantProps<typeof toastVariants> & {\n    onClose?: () => void\n  }) {\n  const Icon = variant ? variantIcons[variant] : variantIcons.success\n  return (\n    <div\n      data-slot=\"toast\"\n      className={cn(toastVariants({ variant, className }))}\n      role=\"status\"\n      aria-live=\"polite\"\n      {...props}\n    >\n      <Icon className=\"toast-icon shrink-0 h-[var(--layout-size-md)] w-[var(--layout-size-md)]\" />\n      <p className=\"flex-1 min-w-0\">{children}</p>\n      {onClose && (\n        <button\n          type=\"button\"\n          onClick={onClose}\n          className=\"toast-close shrink-0 cursor-pointer inline-flex items-center justify-center h-[var(--layout-size-md)] w-[var(--layout-size-md)]\"\n          aria-label=\"Dismiss\"\n        >\n          <X className=\"size-3.5\" />\n        </button>\n      )}\n    </div>\n  )\n}\n\n/* ------------------------------------------------------------------ */\n/*  State (pub/sub)                                                    */\n/* ------------------------------------------------------------------ */\n\ntype ToastVariant = NonNullable<VariantProps<typeof toastVariants>[\"variant\"]>\n\ntype ToastData = {\n  id: string\n  variant: ToastVariant\n  text: string\n  duration?: number\n}\n\ntype ToastHandler = (t: ToastData) => void\n\nlet listeners: ToastHandler[] = []\nlet count = 0\n\nfunction dispatch(data: Omit<ToastData, \"id\">) {\n  const t = { ...data, id: String(++count) }\n  listeners.forEach((l) => l(t))\n  return t.id\n}\n\nfunction toast(\n  text: string,\n  opts?: { variant?: ToastVariant; duration?: number }\n) {\n  return dispatch({ variant: \"brand\", text, ...opts })\n}\n\ntoast.success = (text: string, opts?: { duration?: number }) =>\n  dispatch({ variant: \"success\", text, ...opts })\ntoast.danger = (text: string, opts?: { duration?: number }) =>\n  dispatch({ variant: \"danger\", text, ...opts })\ntoast.brand = (text: string, opts?: { duration?: number }) =>\n  dispatch({ variant: \"brand\", text, ...opts })\ntoast.warning = (text: string, opts?: { duration?: number }) =>\n  dispatch({ variant: \"warning\", text, ...opts })\n\n/* ------------------------------------------------------------------ */\n/*  Toaster                                                            */\n/* ------------------------------------------------------------------ */\n\nconst STACK_OFFSET = 12\nconst EXPANDED_GAP = 12\nconst VISIBLE_COUNT = 3\nconst DEFAULT_DURATION = 5000\nconst TOAST_HEIGHT = 48\n\nfunction Toaster({ className }: { className?: string }) {\n  const [toasts, setToasts] = React.useState<ToastData[]>([])\n  const [expanded, setExpanded] = React.useState(false)\n  const timersRef = React.useRef(\n    new Map<string, ReturnType<typeof setTimeout>>()\n  )\n  const toastsRef = React.useRef(toasts)\n  toastsRef.current = toasts\n\n  const dismiss = React.useCallback((id: string) => {\n    setToasts((prev) => prev.filter((t) => t.id !== id))\n    const timer = timersRef.current.get(id)\n    if (timer) clearTimeout(timer)\n    timersRef.current.delete(id)\n  }, [])\n\n  React.useEffect(() => {\n    const handler: ToastHandler = (t) => {\n      setToasts((prev) => [...prev, t])\n      const timer = setTimeout(\n        () => dismiss(t.id),\n        t.duration ?? DEFAULT_DURATION\n      )\n      timersRef.current.set(t.id, timer)\n    }\n    listeners.push(handler)\n    return () => {\n      listeners = listeners.filter((l) => l !== handler)\n      timersRef.current.forEach((timer) => clearTimeout(timer))\n    }\n  }, [dismiss])\n\n  const pauseTimers = React.useCallback(() => {\n    timersRef.current.forEach((timer) => clearTimeout(timer))\n    timersRef.current.clear()\n  }, [])\n\n  const resumeTimers = React.useCallback(() => {\n    toastsRef.current.forEach((t) => {\n      const timer = setTimeout(() => dismiss(t.id), DEFAULT_DURATION)\n      timersRef.current.set(t.id, timer)\n    })\n  }, [dismiss])\n\n  if (toasts.length === 0) return null\n\n  // Reversed: newest = index 0 (front, bottom)\n  const reversed = [...toasts].reverse()\n  const visibleCount = Math.min(reversed.length, VISIBLE_COUNT)\n  const containerHeight = expanded\n    ? reversed.length * TOAST_HEIGHT + (reversed.length - 1) * EXPANDED_GAP\n    : TOAST_HEIGHT + (visibleCount - 1) * STACK_OFFSET\n\n  return (\n    <ol\n      data-slot=\"toaster\"\n      className={cn(\"fixed bottom-6 right-6 z-[9999]\", className)}\n      style={{\n        width: 400,\n        height: containerHeight,\n        transition: \"height 300ms cubic-bezier(0.2, 0, 0, 1)\",\n      }}\n      onMouseEnter={() => {\n        setExpanded(true)\n        pauseTimers()\n      }}\n      onMouseLeave={() => {\n        setExpanded(false)\n        resumeTimers()\n      }}\n    >\n      {reversed.map((t, i) => {\n        // i=0 newest (front), i=N oldest (back)\n        const isVisible = i < VISIBLE_COUNT || expanded\n        const translateY = expanded\n          ? i * (TOAST_HEIGHT + EXPANDED_GAP)\n          : i * STACK_OFFSET\n        const scale = expanded ? 1 : Math.max(1 - i * 0.05, 0.85)\n        const opacity = isVisible\n          ? expanded\n            ? 1\n            : Math.max(1 - i * 0.1, 0)\n          : 0\n\n        return (\n          <li\n            key={t.id}\n            className=\"toast-item absolute bottom-0 left-0 right-0 list-none\"\n            style={{\n              transform: `translateY(-${translateY}px) scale(${scale})`,\n              opacity,\n              zIndex: reversed.length - i,\n              transformOrigin: \"bottom center\",\n              pointerEvents: isVisible ? \"auto\" : \"none\",\n            }}\n          >\n            <div className=\"toast-enter\">\n              <Toast variant={t.variant} onClose={() => dismiss(t.id)}>\n                {t.text}\n              </Toast>\n            </div>\n          </li>\n        )\n      })}\n    </ol>\n  )\n}\n\nexport { Toast, Toaster, toast, toastVariants }\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/new-york/ui/toast/toast.css",
      "content": "/* ================================================================\n * TOAST — Styles\n * Structure (flex, gap, padding, radius) lives in toast.tsx via cva.\n * Theming (colors, borders, shadow) lives here via tokens.\n * ================================================================ */\n\n/* --------------------------------\n * Base\n * -------------------------------- */\n.toast-base {\n  background: var(--background-neutral-faint-default);\n  border: var(--layout-border-thin) solid var(--border-default);\n  color: var(--text-base-strong);\n  box-shadow: var(--shadow-elevation-md);\n}\n\n/* --------------------------------\n * Variant icon colors\n * -------------------------------- */\n.toast-brand .toast-icon {\n  color: var(--text-brand-moderate);\n}\n\n.toast-success .toast-icon {\n  color: var(--text-success-moderate);\n}\n\n.toast-danger .toast-icon {\n  color: var(--text-danger-moderate);\n}\n\n.toast-warning .toast-icon {\n  color: var(--text-warning-moderate);\n}\n\n/* --------------------------------\n * Close button\n * -------------------------------- */\n.toast-close {\n  color: var(--text-base-moderate);\n}\n\n.toast-close:hover {\n  color: var(--text-base-strong);\n}\n\n/* --------------------------------\n * Stacking transitions\n * -------------------------------- */\n.toast-item {\n  transition:\n    transform 400ms cubic-bezier(0.2, 0, 0, 1),\n    opacity 400ms cubic-bezier(0.2, 0, 0, 1);\n}\n\n/* --------------------------------\n * Enter animation\n * -------------------------------- */\n@keyframes toast-slide-in {\n  from {\n    transform: translateY(var(--layout-padding-xl));\n    opacity: 0;\n  }\n  to {\n    transform: translateY(0);\n    opacity: 1;\n  }\n}\n\n.toast-enter {\n  animation: toast-slide-in 300ms cubic-bezier(0.2, 0, 0, 1);\n}\n",
      "type": "registry:ui"
    }
  ]
}