{"$schema":"https://ui.shadcn.com/schema/registry-item.json","author":"Eduardo Calvo <edu@smoothui.dev>","css":{},"dependencies":["@base-ui/react","lucide-react","motion"],"description":"Toast notifications for SmoothUI built on Base UI Toast manager with Motion polish. Base-only.","devDependencies":[],"files":[{"content":"\"use client\";\n\nimport { Toast as ToastPrimitive } from \"@base-ui/react/toast\";\nimport { cn } from \"@/lib/utils\";\nimport {\n  CircleCheck,\n  CircleX,\n  Info,\n  LoaderCircle,\n  type LucideIcon,\n  TriangleAlert,\n} from \"lucide-react\";\nimport { motion, useReducedMotion } from \"motion/react\";\nimport type { ComponentProps, ReactNode } from \"react\";\nimport { SPRING_DEFAULT } from \"@/components/smoothui/lib/animation\";\n\nexport type ToastKind =\n  | \"default\"\n  | \"success\"\n  | \"error\"\n  | \"warning\"\n  | \"info\"\n  | \"loading\";\n\nexport interface ToastOptions {\n  /** Extra props for the action button, e.g. `{ children: \"Undo\", onClick }` */\n  actionProps?: ComponentProps<\"button\">;\n  /** Secondary text under the title */\n  description?: ReactNode;\n  /** Auto-dismiss delay in ms. `0` keeps the toast until closed. Default 5000. */\n  timeout?: number;\n  /** Primary text */\n  title?: ReactNode;\n  /** Visual / semantic kind */\n  type?: ToastKind | (string & {});\n}\n\nexport interface ToasterProps {\n  /** App content. Toasts render in a fixed viewport alongside it. */\n  children?: ReactNode;\n  /** Optional CSS class for the viewport */\n  className?: string;\n  /** Max visible toasts before older ones are limited (default 3) */\n  limit?: number;\n  /** Default auto-dismiss delay in ms (default 5000) */\n  timeout?: number;\n  /** Provide your own manager instead of the shared `toastManager` */\n  toastManager?: ToastManager;\n}\n\nexport type ToastManager = ReturnType<typeof ToastPrimitive.createToastManager>;\nexport type ToastProviderProps = ComponentProps<typeof ToastPrimitive.Provider>;\nexport type ToastViewportProps = Omit<\n  ComponentProps<typeof ToastPrimitive.Viewport>,\n  \"className\"\n> & { className?: string };\nexport type ToastRootProps = Omit<\n  ComponentProps<typeof ToastPrimitive.Root>,\n  \"className\" | \"render\"\n> & { className?: string };\nexport type ToastContentProps = Omit<\n  ComponentProps<typeof ToastPrimitive.Content>,\n  \"className\"\n> & { className?: string };\nexport type ToastTitleProps = Omit<\n  ComponentProps<typeof ToastPrimitive.Title>,\n  \"className\"\n> & { className?: string };\nexport type ToastDescriptionProps = Omit<\n  ComponentProps<typeof ToastPrimitive.Description>,\n  \"className\"\n> & { className?: string };\nexport type ToastCloseProps = Omit<\n  ComponentProps<typeof ToastPrimitive.Close>,\n  \"className\"\n> & { className?: string };\nexport type ToastActionProps = Omit<\n  ComponentProps<typeof ToastPrimitive.Action>,\n  \"className\"\n> & { className?: string };\n\n// Numeric `scale` / `y` (not a `transform` string): Motion springs froze a\n// string transform ~5% into the tween, leaving shown toasts scaled and shifted.\nconst TOAST_HIDDEN = { opacity: 0, scale: 0.97, y: 8 };\nconst TOAST_SHOWN = { opacity: 1, scale: 1, y: 0 };\n\n/** Shared manager so `toast()` works from anywhere (event handlers, utils). */\nexport const toastManager: ToastManager = ToastPrimitive.createToastManager();\n\n/** Base UI hook: `{ toasts, add, close, update, promise }` for the nearest provider. */\nexport const { useToastManager } = ToastPrimitive;\n\nconst add = (input: string | ToastOptions, extra?: ToastOptions) =>\n  toastManager.add(\n    typeof input === \"string\" ? { title: input, ...extra } : input\n  );\n\n/**\n * Imperative helper. `toast(\"Saved\")`, `toast({ title, description })`,\n * `toast.success()`, `toast.error()`, `toast.promise()`, `toast.close()`.\n */\nexport const toast = Object.assign(add, {\n  close: (id?: string) => toastManager.close(id),\n  error: (title: string, options?: ToastOptions) =>\n    add({ ...options, title, type: \"error\" }),\n  info: (title: string, options?: ToastOptions) =>\n    add({ ...options, title, type: \"info\" }),\n  promise: toastManager.promise,\n  success: (title: string, options?: ToastOptions) =>\n    add({ ...options, title, type: \"success\" }),\n  update: toastManager.update,\n  warning: (title: string, options?: ToastOptions) =>\n    add({ ...options, title, type: \"warning\" }),\n});\n\n/* Status icon per kind. The colour is a detail, not the message: the title\n   carries the meaning, so the icon is decorative. */\nconst KIND_ICON: Record<string, { className: string; icon: LucideIcon }> = {\n  error: { className: \"text-destructive-ink\", icon: CircleX },\n  info: { className: \"text-blue-ink\", icon: Info },\n  loading: {\n    className: \"animate-spin text-muted-foreground motion-reduce:animate-none\",\n    icon: LoaderCircle,\n  },\n  success: { className: \"text-green-ink\", icon: CircleCheck },\n  warning: { className: \"text-amber-ink\", icon: TriangleAlert },\n};\n\nexport type ToastIconProps = {\n  /** Toast kind; `default` and unknown kinds render nothing. */\n  type?: string;\n  className?: string;\n};\n\nexport const ToastIcon = ({ className, type }: ToastIconProps) => {\n  const entry = type ? KIND_ICON[type] : undefined;\n  if (!entry) {\n    return null;\n  }\n  const Icon = entry.icon;\n  return (\n    <Icon\n      aria-hidden=\"true\"\n      className={cn(\"mt-0.5 size-4 shrink-0\", entry.className, className)}\n      data-slot=\"toast-icon\"\n    />\n  );\n};\n\nexport const ToastProvider = (props: ToastProviderProps) => (\n  <ToastPrimitive.Provider toastManager={toastManager} {...props} />\n);\n\n/** Fixed bottom-right stack. Newest toast sits closest to the corner. */\nexport const ToastViewport = ({ className, ...props }: ToastViewportProps) => (\n  <ToastPrimitive.Viewport\n    className={cn(\n      \"pointer-events-none fixed right-0 bottom-0 z-[100] flex w-full max-w-sm flex-col-reverse gap-2 p-4 outline-none\",\n      className\n    )}\n    data-slot=\"toast-viewport\"\n    {...props}\n  />\n);\n\n/**\n * Toast surface. Enter/exit use Motion (opacity + transform); swipe movement is\n * applied through the independent CSS `translate` property so both compose.\n */\nexport const ToastRoot = ({ className, ...props }: ToastRootProps) => {\n  const shouldReduceMotion = useReducedMotion();\n  const isEnding = props.toast.transitionStatus === \"ending\";\n\n  return (\n    <ToastPrimitive.Root\n      className={cn(\n        \"overlay-surface pointer-events-auto relative w-full min-w-0 touch-none select-none rounded-lg border bg-popover p-4 text-popover-foreground outline-none [translate:var(--toast-swipe-movement-x,0px)_var(--toast-swipe-movement-y,0px)]\",\n        className\n      )}\n      data-slot=\"toast\"\n      render={\n        <motion.div\n          animate={isEnding ? TOAST_HIDDEN : TOAST_SHOWN}\n          initial={shouldReduceMotion ? TOAST_SHOWN : TOAST_HIDDEN}\n          transition={shouldReduceMotion ? { duration: 0 } : SPRING_DEFAULT}\n        />\n      }\n      {...props}\n    />\n  );\n};\n\nexport const ToastContent = ({ className, ...props }: ToastContentProps) => (\n  <ToastPrimitive.Content\n    className={cn(\"flex min-w-0 items-start gap-3\", className)}\n    data-slot=\"toast-content\"\n    {...props}\n  />\n);\n\nexport const ToastTitle = ({ className, ...props }: ToastTitleProps) => (\n  <ToastPrimitive.Title\n    className={cn(\"font-medium text-sm leading-5\", className)}\n    data-slot=\"toast-title\"\n    {...props}\n  />\n);\n\nexport const ToastDescription = ({\n  className,\n  ...props\n}: ToastDescriptionProps) => (\n  <ToastPrimitive.Description\n    className={cn(\"text-muted-foreground text-sm leading-5\", className)}\n    data-slot=\"toast-description\"\n    {...props}\n  />\n);\n\nexport const ToastAction = ({ className, ...props }: ToastActionProps) => (\n  <ToastPrimitive.Action\n    className={cn(\n      \"focus-ring mt-2 inline-flex h-7 items-center rounded-md border border-input bg-background px-2.5 font-medium text-xs outline-none transition-colors hover:bg-accent focus-visible:border-ring\",\n      className\n    )}\n    data-slot=\"toast-action\"\n    {...props}\n  />\n);\n\nexport const ToastClose = ({\n  className,\n  children,\n  ...props\n}: ToastCloseProps) => (\n  <ToastPrimitive.Close\n    aria-label=\"Close notification\"\n    className={cn(\n      \"focus-ring -mt-1 -mr-1 ml-auto inline-flex size-6 shrink-0 items-center justify-center rounded-md text-muted-foreground outline-none transition-colors hover:bg-accent hover:text-foreground\",\n      className\n    )}\n    data-slot=\"toast-close\"\n    {...props}\n  >\n    {children ?? (\n      <svg\n        aria-hidden=\"true\"\n        fill=\"none\"\n        height=\"14\"\n        stroke=\"currentColor\"\n        strokeLinecap=\"round\"\n        strokeWidth=\"2\"\n        viewBox=\"0 0 24 24\"\n        width=\"14\"\n      >\n        <path d=\"M18 6 6 18M6 6l12 12\" />\n      </svg>\n    )}\n  </ToastPrimitive.Close>\n);\n\n/** Renders every active toast from the nearest provider. */\nexport const ToastList = () => {\n  const { toasts } = useToastManager();\n\n  return toasts.map((item) => (\n    <ToastRoot key={item.id} toast={item}>\n      <ToastContent>\n        <ToastIcon type={item.type} />\n        <div className=\"min-w-0 flex-1\">\n          <ToastTitle />\n          <ToastDescription />\n          <ToastAction />\n        </div>\n        <ToastClose />\n      </ToastContent>\n    </ToastRoot>\n  ));\n};\n\n/**\n * SmoothUI Toast — Base UI only (Radix Toast has a different, non-manager API).\n * Convenience: Provider + children + Viewport + ToastList. Mount once near the root.\n */\nexport default function Toaster({\n  children,\n  className,\n  limit = 3,\n  timeout,\n  toastManager: manager,\n}: ToasterProps) {\n  return (\n    <ToastPrimitive.Provider\n      limit={limit}\n      timeout={timeout}\n      toastManager={manager ?? toastManager}\n    >\n      {children}\n      <ToastPrimitive.Portal>\n        <ToastViewport className={className}>\n          <ToastList />\n        </ToastViewport>\n      </ToastPrimitive.Portal>\n    </ToastPrimitive.Provider>\n  );\n}\n","path":"index.tsx","target":"components/smoothui/toast/index.tsx","type":"registry:ui"}],"name":"toast","registryDependencies":["https://smoothui.dev/r/lib.json","https://smoothui.dev/r/tokens.json"],"title":"Toast","type":"registry:ui"}