{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "toast",
  "title": "Toast",
  "dependencies": [
    "@base-ui/react",
    "@phosphor-icons/react"
  ],
  "registryDependencies": [
    "https://ui.hotfix.jobs/r/recipes.json",
    "https://ui.hotfix.jobs/r/spinner.json",
    "https://ui.hotfix.jobs/r/tokens.json",
    "https://ui.hotfix.jobs/r/utils.json"
  ],
  "files": [
    {
      "path": "registry/components/ui/toast.tsx",
      "content": "\"use client\";\n\nimport { Toast as ToastPrimitive } from \"@base-ui/react/toast\";\nimport {\n  CheckCircle,\n  Info,\n  Warning,\n  WarningCircle,\n  X,\n} from \"@phosphor-icons/react/dist/ssr\";\nimport type * as React from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport { selectionFocus } from \"@/lib/recipes\";\nimport { Spinner } from \"@/components/ui/spinner\";\n\n/* ----------------------------- types ----------------------------- */\n\nexport type ToastType =\n  | \"default\"\n  | \"success\"\n  | \"error\"\n  | \"warning\"\n  | \"info\"\n  | \"loading\";\n\nexport type ToastPosition =\n  | \"top-left\"\n  | \"top-center\"\n  | \"top-right\"\n  | \"bottom-left\"\n  | \"bottom-center\"\n  | \"bottom-right\";\n\nexport interface ToastAction {\n  label: React.ReactNode;\n  onClick: () => void;\n}\n\nexport interface ToastOptions {\n  id?: string;\n  description?: React.ReactNode;\n  duration?: number;\n  action?: ToastAction;\n}\n\ntype ToastData = {\n  action?: ToastAction;\n};\n\n/* --------------------------- manager --------------------------- */\n\nconst DEFAULT_DURATIONS: Record<ToastType, number> = {\n  default: 5000,\n  success: 5000,\n  info: 5000,\n  warning: 6000,\n  error: 7000,\n  loading: 0,\n};\n\nconst HIGH_PRIORITY: Partial<Record<ToastType, true>> = {\n  warning: true,\n  error: true,\n};\n\nconst manager = ToastPrimitive.createToastManager<ToastData>();\n\nfunction addToast(type: ToastType, title: React.ReactNode, options?: ToastOptions): string {\n  return manager.add({\n    id: options?.id,\n    type,\n    title,\n    description: options?.description,\n    timeout: options?.duration ?? DEFAULT_DURATIONS[type],\n    priority: HIGH_PRIORITY[type] ? \"high\" : \"low\",\n    data: options?.action ? { action: options.action } : undefined,\n  });\n}\n\ntype ToastFn = (title: React.ReactNode, options?: ToastOptions) => string;\n\ninterface ToastApi extends ToastFn {\n  success: ToastFn;\n  error: ToastFn;\n  warning: ToastFn;\n  info: ToastFn;\n  loading: ToastFn;\n  dismiss: (id?: string) => void;\n}\n\nexport const toast: ToastApi = Object.assign(\n  ((title, options) => addToast(\"default\", title, options)) as ToastFn,\n  {\n    success: (title, options) => addToast(\"success\", title, options),\n    error: (title, options) => addToast(\"error\", title, options),\n    warning: (title, options) => addToast(\"warning\", title, options),\n    info: (title, options) => addToast(\"info\", title, options),\n    loading: (title, options) => addToast(\"loading\", title, options),\n    dismiss: (id?: string) => manager.close(id),\n  } satisfies Omit<ToastApi, keyof ToastFn>,\n);\n\n/* --------------------------- Toaster --------------------------- */\n\nconst POSITION_CLASSES: Record<ToastPosition, string> = {\n  \"top-left\": \"top-4 left-4 sm:top-6 sm:left-6\",\n  \"top-center\": \"top-4 left-1/2 -translate-x-1/2 sm:top-6\",\n  \"top-right\": \"top-4 right-4 sm:top-6 sm:right-6\",\n  \"bottom-left\": \"bottom-4 left-4 sm:bottom-6 sm:left-6\",\n  \"bottom-center\": \"bottom-4 left-1/2 -translate-x-1/2 sm:bottom-6\",\n  \"bottom-right\": \"bottom-4 right-4 sm:bottom-6 sm:right-6\",\n};\n\nfunction isTopPosition(p: ToastPosition): boolean {\n  return p.startsWith(\"top\");\n}\n\nexport interface ToasterProps {\n  position?: ToastPosition;\n  visibleToasts?: number;\n  toastWidth?: number;\n  className?: string;\n}\n\nexport function Toaster({\n  position = \"bottom-right\",\n  visibleToasts = 3,\n  toastWidth = 360,\n  className,\n}: ToasterProps): React.ReactElement {\n  return (\n    <ToastPrimitive.Provider toastManager={manager} limit={visibleToasts}>\n      <ToastPrimitive.Viewport\n        data-slot=\"toaster\"\n        data-position={position}\n        className={cn(\n          \"pointer-events-none fixed z-90\",\n          POSITION_CLASSES[position],\n          className,\n        )}\n        style={{ width: toastWidth }}\n      >\n        <ToastList position={position} />\n      </ToastPrimitive.Viewport>\n    </ToastPrimitive.Provider>\n  );\n}\n\n/* --------------------------- ToastList --------------------------- */\n\nconst TYPE_ICON: Record<string, React.ReactNode> = {\n  success: <CheckCircle className=\"size-4 text-success\" />,\n  error: <WarningCircle className=\"size-4 text-error\" />,\n  warning: <Warning className=\"size-4 text-warning\" />,\n  info: <Info className=\"size-4 text-ink-muted\" />,\n  loading: <Spinner size=\"sm\" className=\"text-ink-muted\" />,\n};\n\nfunction ToastList({ position }: { position: ToastPosition }): React.ReactElement {\n  const { toasts } = ToastPrimitive.useToastManager<ToastData>();\n  const top = isTopPosition(position);\n\n  return (\n    <>\n      {toasts.map((t) => {\n        const icon = t.type ? TYPE_ICON[t.type] : null;\n        const action = t.data?.action;\n        return (\n          <ToastPrimitive.Root\n            key={t.id}\n            toast={t}\n            swipeDirection={top ? [\"up\", \"right\"] : [\"down\", \"right\"]}\n            data-slot=\"toast\"\n            data-type={t.type ?? \"default\"}\n            className={cn(\n              \"pointer-events-auto absolute w-full select-none\",\n              \"rounded-[var(--radius-12)]\",\n              \"bg-layer-1 text-ink\",\n              \"border border-hairline shadow-menu\",\n              \"px-3 py-3 pe-10\",\n              \"flex items-start gap-2.5\",\n              // Stacking + transitions via Base UI CSS variables\n              top\n                ? \"top-0 [transform:translateY(calc(var(--toast-index)*var(--toast-swipe-movement-y,0px)+var(--toast-index)*8px))_scale(calc(1-var(--toast-index)*0.05))]\"\n                : \"bottom-0 [transform:translateY(calc(var(--toast-index)*-8px+var(--toast-swipe-movement-y,0px)))_scale(calc(1-var(--toast-index)*0.05))]\",\n              \"transition-[transform,opacity] duration-[var(--duration-overlay)] ease-[var(--ease-standard)]\",\n              \"data-[starting-style]:opacity-0\",\n              top\n                ? \"data-[starting-style]:-translate-y-4 data-[ending-style]:-translate-y-4\"\n                : \"data-[starting-style]:translate-y-4 data-[ending-style]:translate-y-4\",\n              \"data-[ending-style]:opacity-0\",\n              \"[[data-expanded]_&]:!translate-y-[var(--toast-offset-y)] [[data-expanded]_&]:!scale-100\",\n            )}\n          >\n            {icon && (\n              <span className=\"mt-px shrink-0\" data-slot=\"toast-icon\">\n                {icon}\n              </span>\n            )}\n            <div className=\"min-w-0 flex-1\">\n              <ToastPrimitive.Title\n                className=\"text-small font-medium leading-tight text-ink\"\n                data-slot=\"toast-title\"\n              />\n              <ToastPrimitive.Description\n                className=\"mt-0.5 text-mini leading-snug text-ink-muted data-[empty]:hidden\"\n                data-slot=\"toast-description\"\n              />\n              {action && (\n                <ToastPrimitive.Action\n                  onClick={action.onClick}\n                  className={cn(\n                    \"mt-2 inline-flex items-center justify-center\",\n                    \"rounded-[var(--radius-8)] bg-fill-1 px-2.5 py-1\",\n                    \"text-small font-medium text-ink\",\n                    \"transition-colors duration-[var(--duration-state)] ease-[var(--ease-standard)]\",\n                    \"hover:bg-fill-2 active:bg-layer-hover\",\n                    selectionFocus,\n                  )}\n                  data-slot=\"toast-action\"\n                >\n                  {action.label}\n                </ToastPrimitive.Action>\n              )}\n            </div>\n            <ToastPrimitive.Close\n              aria-label=\"Dismiss\"\n              data-slot=\"toast-close\"\n              className={cn(\n                \"absolute top-1/2 -translate-y-1/2 end-2\",\n                \"flex size-7 items-center justify-center\",\n                \"rounded-[var(--radius-8)] text-ink-muted\",\n                \"transition-colors duration-[var(--duration-state)] ease-[var(--ease-standard)]\",\n                \"hover:bg-layer-hover hover:text-ink active:bg-layer-hover\",\n                selectionFocus,\n              )}\n            >\n              <X className=\"size-3.5\" aria-hidden />\n            </ToastPrimitive.Close>\n          </ToastPrimitive.Root>\n        );\n      })}\n    </>\n  );\n}\n\nconst ToastPrimitiveExport = ToastPrimitive;\nexport { ToastPrimitiveExport as ToastPrimitive };\n",
      "type": "registry:ui",
      "target": "components/ui/toast.tsx"
    }
  ],
  "type": "registry:ui"
}