{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "toast",
  "title": "Toast",
  "description": "Brief messages that stack neatly, expand on hover, swipe away, and turn a loading state into success or error in place.",
  "dependencies": [
    "@base-ui/react@^1.8.0",
    "@tabler/icons-react",
    "cn"
  ],
  "registryDependencies": [
    "https://hextaui.com/r/theme.json",
    "https://hextaui.com/r/button.json",
    "https://hextaui.com/r/spinner.json"
  ],
  "files": [
    {
      "path": "components/ui/toast.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Toast as ToastPrimitive } from \"@base-ui/react/toast\"\nimport {\n  IconAlertTriangleFilled,\n  IconCircleCheckFilled,\n  IconCircleXFilled,\n  IconInfoCircleFilled,\n  IconX,\n} from \"@tabler/icons-react\"\nimport { cn } from \"cn\"\n\nimport { buttonVariants } from \"@/components/ui/button\"\nimport { Spinner } from \"@/components/ui/spinner\"\n\ntype ToastType =\n  \"default\" | \"success\" | \"error\" | \"warning\" | \"info\" | \"loading\"\n\ntype ToastPosition =\n  | \"top-left\"\n  | \"top-center\"\n  | \"top-right\"\n  | \"bottom-left\"\n  | \"bottom-center\"\n  | \"bottom-right\"\n\ntype ToastAction = {\n  label: React.ReactNode\n  onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void\n}\n\ntype ToastOptions = {\n  id?: string\n  description?: React.ReactNode\n  timeout?: number\n  priority?: \"low\" | \"high\"\n  action?: ToastAction\n  onClose?: () => void\n  onRemove?: () => void\n}\n\ntype ToastData = { action?: ToastAction }\n\nconst manager = ToastPrimitive.createToastManager<ToastData>()\n\nlet counter = 0\n\nfunction nextId() {\n  counter += 1\n  return `toast-${counter}`\n}\n\nfunction textOf(node: React.ReactNode): string {\n  if (typeof node === \"string\" || typeof node === \"number\") {\n    return String(node)\n  }\n  if (Array.isArray(node)) {\n    return node.map(textOf).join(\" \")\n  }\n  if (React.isValidElement<{ children?: React.ReactNode }>(node)) {\n    return textOf(node.props.children)\n  }\n  return \"\"\n}\n\nconst readingFloor = 5000\n\nfunction readingTimeout(title: React.ReactNode, description: React.ReactNode) {\n  const words = `${textOf(title)} ${textOf(description)}`\n    .split(/\\s+/)\n    .filter(Boolean).length\n  const time = Math.min(15000, 2000 + words * 240)\n  return time > readingFloor ? time : undefined\n}\n\nfunction toOptions(\n  type: ToastType,\n  title: React.ReactNode,\n  { action, ...options }: ToastOptions = {}\n) {\n  return {\n    ...options,\n    type,\n    title,\n    timeout:\n      type === \"loading\"\n        ? 0\n        : (options.timeout ?? readingTimeout(title, options.description)),\n    data: { action },\n  }\n}\n\nfunction show(type: ToastType, title: React.ReactNode, options?: ToastOptions) {\n  const id = options?.id ?? nextId()\n  manager.add({ id, ...toOptions(type, title, options) })\n  return id\n}\n\ntype PromiseState<Value> =\n  | React.ReactNode\n  | ((\n      value: Value\n    ) => React.ReactNode | (ToastOptions & { title: React.ReactNode }))\n\nfunction resolveState<Value>(\n  state: PromiseState<Value>,\n  value: Value,\n  type: ToastType\n) {\n  const result = typeof state === \"function\" ? state(value) : state\n  if (\n    result !== null &&\n    typeof result === \"object\" &&\n    !React.isValidElement(result) &&\n    \"title\" in (result as object)\n  ) {\n    const { title, ...options } = result as ToastOptions & {\n      title: React.ReactNode\n    }\n    return toOptions(type, title, options)\n  }\n  return toOptions(type, result as React.ReactNode)\n}\n\nconst toast = Object.assign(\n  (title: React.ReactNode, options?: ToastOptions) =>\n    show(\"default\", title, options),\n  {\n    success: (title: React.ReactNode, options?: ToastOptions) =>\n      show(\"success\", title, options),\n    error: (title: React.ReactNode, options?: ToastOptions) =>\n      show(\"error\", title, { priority: \"high\", ...options }),\n    warning: (title: React.ReactNode, options?: ToastOptions) =>\n      show(\"warning\", title, options),\n    info: (title: React.ReactNode, options?: ToastOptions) =>\n      show(\"info\", title, options),\n    loading: (title: React.ReactNode, options?: ToastOptions) =>\n      show(\"loading\", title, options),\n    promise: <Value,>(\n      promise: Promise<Value>,\n      states: {\n        loading: React.ReactNode\n        success: PromiseState<Value>\n        error: PromiseState<unknown>\n      }\n    ) => {\n      const tracked = manager.promise(promise, {\n        loading: toOptions(\"loading\", states.loading),\n        success: (value: Value) =>\n          resolveState(states.success, value, \"success\"),\n        error: (error: unknown) => resolveState(states.error, error, \"error\"),\n      })\n      promise.catch(() => undefined)\n      tracked.catch(() => undefined)\n      return tracked\n    },\n    update: (\n      id: string,\n      {\n        type,\n        title,\n        ...options\n      }: ToastOptions & {\n        type?: ToastType\n        title?: React.ReactNode\n      }\n    ) =>\n      manager.update(id, {\n        ...(type ? { type } : {}),\n        ...(title !== undefined ? { title } : {}),\n        ...(type && type !== \"loading\" && options.timeout === undefined\n          ? {\n              timeout:\n                readingTimeout(title, options.description) ?? readingFloor,\n            }\n          : {}),\n        ...options,\n        ...(options.action ? { data: { action: options.action } } : {}),\n      }),\n    dismiss: (id?: string) => manager.close(id),\n  }\n)\n\nconst icons: Partial<Record<ToastType, React.ReactNode>> = {\n  success: <IconCircleCheckFilled className=\"text-success\" />,\n  error: <IconCircleXFilled className=\"text-destructive\" />,\n  warning: <IconAlertTriangleFilled className=\"text-warning\" />,\n  info: <IconInfoCircleFilled className=\"text-info\" />,\n  loading: (\n    <Spinner aria-hidden size={null} className=\"text-muted-foreground\" />\n  ),\n}\n\nfunction swipeFor(\n  position: ToastPosition\n): ToastPrimitive.Root.Props[\"swipeDirection\"] {\n  const vertical = position.startsWith(\"top\") ? \"up\" : \"down\"\n  if (position.endsWith(\"left\")) {\n    return [vertical, \"left\"]\n  }\n  if (position.endsWith(\"right\")) {\n    return [vertical, \"right\"]\n  }\n  return [vertical]\n}\n\nconst rootClassName =\n  \"group/toast absolute inset-x-0 z-[calc(1000-var(--toast-index))] h-(--height) w-full rounded-xl bg-popover text-popover-foreground shadow-lg ring-(length:--hairline) forced-colors:border ring-foreground/10 select-none [--gap:0.625rem] [--height:var(--toast-frontmost-height,var(--toast-height))] [--peek:0.625rem] [--scale:calc(max(0,1-(var(--toast-index)*0.06)))] [--shrink:calc(1-var(--scale))] [transform:translateX(var(--toast-swipe-movement-x))_translateY(calc(var(--toast-swipe-movement-y)+var(--toast-dir)*(var(--toast-index)*var(--peek)+var(--shrink)*var(--height))))_scale(var(--scale))] [transition:transform_450ms_cubic-bezier(0.22,1,0.36,1),opacity_350ms,height_200ms_cubic-bezier(0.22,1,0.36,1)] group-data-[position^=bottom]/toaster:bottom-0 group-data-[position^=bottom]/toaster:origin-bottom group-data-[position^=top]/toaster:top-0 group-data-[position^=top]/toaster:origin-top after:absolute after:inset-x-0 after:h-[calc(var(--gap)+1px)] group-data-[position^=bottom]/toaster:after:top-full group-data-[position^=top]/toaster:after:bottom-full data-expanded:h-(--toast-height) data-expanded:[transform:translateX(var(--toast-swipe-movement-x))_translateY(calc(var(--toast-dir)*(var(--toast-offset-y)+var(--toast-index)*var(--gap))+var(--toast-swipe-movement-y)))] data-limited:opacity-0 data-starting-style:[transform:translateY(calc(var(--toast-dir)*-150%))] data-ending-style:opacity-0 [&[data-ending-style]:not([data-limited]):not([data-swipe-direction])]:[transform:translateY(calc(var(--toast-dir)*-150%))] data-ending-style:data-[swipe-direction=down]:[transform:translateY(calc(var(--toast-swipe-movement-y)+150%))] data-ending-style:data-[swipe-direction=up]:[transform:translateY(calc(var(--toast-swipe-movement-y)-150%))] data-ending-style:data-[swipe-direction=left]:[transform:translateX(calc(var(--toast-swipe-movement-x)-150%))] data-ending-style:data-[swipe-direction=right]:[transform:translateX(calc(var(--toast-swipe-movement-x)+150%))] data-swiping:[transition:none] motion-reduce:[transition:opacity_200ms]\"\n\nfunction ToastItem({\n  item,\n  position,\n}: {\n  item: ToastPrimitive.Root.ToastObject<ToastData>\n  position: ToastPosition\n}) {\n  const type = (item.type ?? \"default\") as ToastType\n  const icon = icons[type]\n  const action = item.data?.action\n\n  return (\n    <ToastPrimitive.Root\n      toast={item}\n      swipeDirection={swipeFor(position)}\n      data-slot=\"toast\"\n      className={rootClassName}\n    >\n      <ToastPrimitive.Content\n        data-slot=\"toast-content\"\n        className=\"flex items-start gap-3 overflow-hidden p-4 transition-opacity duration-250 data-behind:opacity-0 data-expanded:opacity-100\"\n      >\n        {icon ? (\n          <span\n            key={type}\n            data-slot=\"toast-icon\"\n            className=\"mt-px flex size-4 shrink-0 items-center justify-center motion-safe:animate-in motion-safe:animation-duration-300 motion-safe:fade-in-0 motion-safe:zoom-in-50 [&_svg]:size-4\"\n          >\n            {icon}\n          </span>\n        ) : null}\n        <div className=\"flex min-w-0 flex-1 flex-col gap-0.5\">\n          <ToastPrimitive.Title\n            data-slot=\"toast-title\"\n            className=\"text-sm leading-5 font-medium wrap-break-word\"\n          />\n          <ToastPrimitive.Description\n            data-slot=\"toast-description\"\n            className=\"text-sm leading-5 wrap-break-word text-muted-foreground\"\n          />\n        </div>\n        {action ? (\n          <ToastPrimitive.Action\n            data-slot=\"toast-action\"\n            className={cn(\n              buttonVariants({ variant: \"outline\", size: \"xs\" }),\n              \"-my-0.5 shrink-0\"\n            )}\n            onClick={(event) => {\n              action.onClick?.(event)\n              if (!event.defaultPrevented) {\n                manager.close(item.id)\n              }\n            }}\n          >\n            {action.label}\n          </ToastPrimitive.Action>\n        ) : null}\n        <ToastPrimitive.Close\n          data-slot=\"toast-close\"\n          aria-label=\"Dismiss\"\n          className=\"relative -me-1.5 -mt-1 flex size-6 shrink-0 items-center justify-center rounded-md text-muted-foreground opacity-0 transition-opacity duration-150 outline-none group-hover/toast:opacity-100 after:absolute after:-inset-1.5 hover:bg-muted hover:text-foreground focus-visible:opacity-100 focus-visible:ring-3 focus-visible:ring-focus-ring focus-visible:outline-hidden pointer-coarse:opacity-100 [&_svg]:size-3.5\"\n        >\n          <IconX />\n        </ToastPrimitive.Close>\n      </ToastPrimitive.Content>\n    </ToastPrimitive.Root>\n  )\n}\n\nfunction ToastList({ position }: { position: ToastPosition }) {\n  const { toasts } = ToastPrimitive.useToastManager<ToastData>()\n  return toasts.map((item) => (\n    <ToastItem key={item.id} item={item} position={position} />\n  ))\n}\n\nconst positionClassName: Record<ToastPosition, string> = {\n  \"top-left\": \"top-4 sm:top-6 sm:left-6\",\n  \"top-center\": \"top-4 sm:top-6 sm:left-1/2 sm:-translate-x-1/2\",\n  \"top-right\": \"top-4 sm:top-6 sm:right-6\",\n  \"bottom-left\": \"bottom-4 sm:bottom-6 sm:left-6\",\n  \"bottom-center\": \"bottom-4 sm:bottom-6 sm:left-1/2 sm:-translate-x-1/2\",\n  \"bottom-right\": \"bottom-4 sm:bottom-6 sm:right-6\",\n}\n\ntype ToasterProps = Omit<\n  ToastPrimitive.Viewport.Props,\n  \"children\" | \"className\"\n> & {\n  className?: string\n  position?: ToastPosition\n  limit?: number\n  timeout?: number\n}\n\nfunction Toaster({\n  className,\n  position = \"bottom-right\",\n  limit = 3,\n  timeout = 5000,\n  ...props\n}: ToasterProps) {\n  return (\n    <ToastPrimitive.Provider\n      toastManager={manager}\n      limit={limit}\n      timeout={timeout}\n    >\n      <ToastPrimitive.Portal>\n        <ToastPrimitive.Viewport\n          data-slot=\"toaster\"\n          data-position={position}\n          className={cn(\n            \"group/toaster fixed inset-x-4 z-100 mx-auto outline-none focus-visible:outline-hidden data-[position^=bottom]:[--toast-dir:-1] data-[position^=top]:[--toast-dir:1] sm:inset-x-auto sm:w-[22.5rem]\",\n            positionClassName[position],\n            className\n          )}\n          {...props}\n        >\n          <ToastList position={position} />\n        </ToastPrimitive.Viewport>\n      </ToastPrimitive.Portal>\n    </ToastPrimitive.Provider>\n  )\n}\n\nexport { Toaster, toast }\nexport type {\n  ToastAction,\n  ToastOptions,\n  ToastPosition,\n  ToasterProps,\n  ToastType,\n}\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "feedback"
  ],
  "type": "registry:ui"
}