{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "toast",
  "title": "Toast",
  "description": "First-party brutalist toast stacks with an imperative toast() API, promise toasts, swipe and stacking",
  "dependencies": [
    "radix-ui",
    "lucide-react"
  ],
  "registryDependencies": [
    "@boldkit/utils"
  ],
  "files": [
    {
      "path": "registry/default/ui/toast.tsx",
      "content": "/* eslint-disable react-refresh/only-export-components */\nimport * as React from 'react'\nimport { Toast as ToastPrimitive } from 'radix-ui'\nimport { CircleCheck, CircleX, Info, LoaderCircle, TriangleAlert, X } from 'lucide-react'\nimport { cn } from '@/lib/utils'\n\n/* -------------------------------------------------------------------------------------------------\n * Store — a tiny module-level store so `toast()` can be called from anywhere (event handlers,\n * effects, plain modules). `<Toaster />` subscribes with useSyncExternalStore. Mount ONE\n * `<Toaster />`, at the app root: every mounted Toaster renders every toast.\n *\n * Updating a toast (re-adding its `id`, or a settled `toast.promise`) REPLACES its content —\n * fields you leave out are cleared — and re-adding an open `id` restarts its dismiss timer.\n * -----------------------------------------------------------------------------------------------*/\n\ntype ToastVariant = 'default' | 'success' | 'error' | 'warning' | 'info' | 'loading'\n\ntype ToastPosition =\n  | 'top-left'\n  | 'top-right'\n  | 'top-center'\n  | 'bottom-left'\n  | 'bottom-right'\n  | 'bottom-center'\n\ninterface ToastActionOptions {\n  label: string\n  onClick: () => void\n  /** Screen-reader description of an alternate way to do the action. Defaults to `label`. */\n  altText?: string\n}\n\ninterface ToastOptions {\n  /** Reuse an id to update an open toast in place. */\n  id?: string\n  title?: React.ReactNode\n  description?: React.ReactNode\n  variant?: ToastVariant\n  action?: ToastActionOptions\n  /** Milliseconds before auto-dismiss. `Infinity` keeps it open. Defaults to the Toaster's duration. */\n  duration?: number\n}\n\ninterface ToastRecord extends Omit<ToastOptions, 'id'> {\n  id: string\n  open: boolean\n  /** Increments on every in-place update (re-measures height, re-announces). */\n  updateKey: number\n  /** Increments when an open toast is added again with the same id (restarts its timer). */\n  restartKey: number\n}\n\ntype ToastPromiseState<T> = string | ToastOptions | ((value: T) => string | ToastOptions)\n\ninterface ToastPromiseOptions<T> {\n  loading: string | ToastOptions\n  success: ToastPromiseState<T>\n  error: ToastPromiseState<unknown>\n}\n\nconst EMPTY: ToastRecord[] = []\nlet toasts: ToastRecord[] = EMPTY\nlet count = 0\nconst listeners = new Set<() => void>()\n/** Number of mounted <Toaster />s. Without one, nothing can animate a toast out. */\nlet toasterCount = 0\n/** Toasts kept while no <Toaster /> is mounted (oldest dropped first). */\nconst UNMOUNTED_LIMIT = 20\n\nfunction setToasts(next: ToastRecord[]) {\n  toasts = next\n  listeners.forEach((listener) => listener())\n}\n\nconst toastStore = {\n  subscribe(listener: () => void) {\n    listeners.add(listener)\n    return () => {\n      listeners.delete(listener)\n    }\n  },\n  getSnapshot: () => toasts,\n  getServerSnapshot: () => EMPTY,\n  add({ id: idOption, ...options }: ToastOptions): string {\n    const id = idOption ?? `bk-toast-${++count}`\n    const existing = toasts.find((t) => t.id === id)\n    if (existing?.open) {\n      toastStore.update(id, options, true)\n      return id\n    }\n    const next = [{ ...options, id, open: true, updateKey: 0, restartKey: 0 }, ...toasts.filter((t) => t.id !== id)]\n    setToasts(toasterCount === 0 ? next.slice(0, UNMOUNTED_LIMIT) : next)\n    return id\n  },\n  /** Replaces an open toast's content in place (fields not given are cleared). */\n  update(id: string, content: Omit<ToastOptions, 'id'>, restart = false) {\n    setToasts(\n      toasts.map((t) =>\n        t.id === id && t.open\n          ? {\n              ...content,\n              id,\n              open: true,\n              updateKey: t.updateKey + 1,\n              restartKey: t.restartKey + (restart ? 1 : 0),\n            }\n          : t\n      )\n    )\n  },\n  dismiss(id?: string) {\n    if (!toasts.some((t) => t.open && (id === undefined || t.id === id))) return\n    const matches = (t: ToastRecord) => id === undefined || t.id === id\n    // No Toaster → no exit animation will ever finish, so drop the records outright.\n    if (toasterCount === 0) setToasts(toasts.filter((t) => !matches(t)))\n    else setToasts(toasts.map((t) => (matches(t) ? { ...t, open: false } : t)))\n  },\n  /** Drops a closed toast once its exit animation has finished. */\n  remove(id: string) {\n    if (toasts.some((t) => t.id === id && !t.open)) setToasts(toasts.filter((t) => t.id !== id))\n  },\n}\n\nfunction resolveState<T>(state: ToastPromiseState<T>, value: T): ToastOptions {\n  const resolved = typeof state === 'function' ? state(value) : state\n  return typeof resolved === 'string' ? { title: resolved } : resolved\n}\n\ntype VariantHelper = (title: React.ReactNode, options?: Omit<ToastOptions, 'title' | 'variant'>) => string\n\nconst variantHelper =\n  (variant: ToastVariant): VariantHelper =>\n  (title, options) =>\n    toastStore.add({ ...options, title, variant })\n\n/**\n * Show a toast. Returns its id.\n *\n * @example toast({ title: 'Saved', description: 'Your changes are live.', variant: 'success' })\n */\nconst toast = Object.assign((options: ToastOptions) => toastStore.add(options), {\n  success: variantHelper('success'),\n  error: variantHelper('error'),\n  warning: variantHelper('warning'),\n  info: variantHelper('info'),\n  /** Dismiss one toast, or every toast when called without an id. */\n  dismiss: (id?: string) => toastStore.dismiss(id),\n  /** Shows a loading toast that turns into a success or error toast, in place, when the promise settles. */\n  promise<T>(promise: Promise<T>, options: ToastPromiseOptions<T>): string {\n    const loading = typeof options.loading === 'string' ? { title: options.loading } : options.loading\n    const id = toastStore.add({ ...loading, variant: 'loading', duration: Infinity })\n    promise.then(\n      (value) => toastStore.update(id, { ...resolveState(options.success, value), variant: 'success' }),\n      (error: unknown) => toastStore.update(id, { ...resolveState(options.error, error), variant: 'error' })\n    )\n    return id\n  },\n})\n\n/** The current toasts (newest first), including ones still animating out. */\nfunction useToasts() {\n  return React.useSyncExternalStore(toastStore.subscribe, toastStore.getSnapshot, toastStore.getServerSnapshot)\n}\n\n/* -------------------------------------------------------------------------------------------------\n * Parts — styled Radix Toast primitives. Use these directly for fully controlled toasts, or use\n * `<Toaster />` + `toast()` for the imperative API.\n * -----------------------------------------------------------------------------------------------*/\n\nconst ToastProvider = ToastPrimitive.Provider\n\nconst ToastViewport = React.forwardRef<\n  React.ElementRef<typeof ToastPrimitive.Viewport>,\n  React.ComponentPropsWithoutRef<typeof ToastPrimitive.Viewport>\n>(({ className, ...props }, ref) => (\n  <ToastPrimitive.Viewport\n    ref={ref}\n    data-slot=\"toast-viewport\"\n    className={cn(\n      'fixed z-[100] m-0 w-[calc(100%-2rem)] list-none p-0 outline-none sm:w-[380px]',\n      className\n    )}\n    {...props}\n  />\n))\nToastViewport.displayName = ToastPrimitive.Viewport.displayName\n\nconst Toast = React.forwardRef<\n  React.ElementRef<typeof ToastPrimitive.Root>,\n  React.ComponentPropsWithoutRef<typeof ToastPrimitive.Root>\n>(({ className, ...props }, ref) => (\n  <ToastPrimitive.Root\n    ref={ref}\n    data-slot=\"toast\"\n    className={cn(\n      'group/toast pointer-events-auto relative flex w-full items-stretch border-3 border-foreground bg-background text-foreground shadow-[4px_4px_0px_hsl(var(--shadow-color))] outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',\n      // enter / exit + swipe (Radix exposes the drag offset as CSS vars)\n      'duration-200 ease-out data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=closed]:animate-out data-[state=closed]:fade-out-0',\n      'data-[swipe=move]:[transform:translateX(var(--radix-toast-swipe-move-x))] data-[swipe=cancel]:[transform:translateX(0)] data-[swipe=cancel]:transition-[transform] data-[swipe=end]:[transform:translateX(var(--radix-toast-swipe-end-x))]',\n      'motion-reduce:transition-none',\n      className\n    )}\n    {...props}\n  />\n))\nToast.displayName = ToastPrimitive.Root.displayName\n\nconst ToastTitle = React.forwardRef<\n  React.ElementRef<typeof ToastPrimitive.Title>,\n  React.ComponentPropsWithoutRef<typeof ToastPrimitive.Title>\n>(({ className, ...props }, ref) => (\n  <ToastPrimitive.Title\n    ref={ref}\n    data-slot=\"toast-title\"\n    className={cn('text-sm font-black uppercase leading-tight tracking-wide', className)}\n    {...props}\n  />\n))\nToastTitle.displayName = ToastPrimitive.Title.displayName\n\nconst ToastDescription = React.forwardRef<\n  React.ElementRef<typeof ToastPrimitive.Description>,\n  React.ComponentPropsWithoutRef<typeof ToastPrimitive.Description>\n>(({ className, ...props }, ref) => (\n  <ToastPrimitive.Description\n    ref={ref}\n    data-slot=\"toast-description\"\n    className={cn('text-sm font-medium text-muted-foreground', className)}\n    {...props}\n  />\n))\nToastDescription.displayName = ToastPrimitive.Description.displayName\n\nconst ToastAction = React.forwardRef<\n  React.ElementRef<typeof ToastPrimitive.Action>,\n  React.ComponentPropsWithoutRef<typeof ToastPrimitive.Action>\n>(({ className, ...props }, ref) => (\n  <ToastPrimitive.Action\n    ref={ref}\n    data-slot=\"toast-action\"\n    className={cn(\n      'inline-flex shrink-0 items-center self-center border-2 border-foreground bg-primary px-3 py-1 text-xs font-bold uppercase tracking-wide text-primary-foreground shadow-[2px_2px_0px_hsl(var(--shadow-color))] transition duration-150 ease-out hover:translate-x-[2px] hover:translate-y-[2px] hover:shadow-none active:translate-x-[2px] active:translate-y-[2px] active:shadow-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring motion-reduce:transition-none',\n      className\n    )}\n    {...props}\n  />\n))\nToastAction.displayName = ToastPrimitive.Action.displayName\n\nconst ToastClose = React.forwardRef<\n  React.ElementRef<typeof ToastPrimitive.Close>,\n  React.ComponentPropsWithoutRef<typeof ToastPrimitive.Close>\n>(({ className, ...props }, ref) => (\n  <ToastPrimitive.Close\n    ref={ref}\n    data-slot=\"toast-close\"\n    aria-label=\"Close\"\n    className={cn(\n      'absolute right-2 top-2 border-2 border-foreground bg-background p-0.5 text-foreground transition duration-150 ease-out hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring motion-reduce:transition-none',\n      className\n    )}\n    {...props}\n  >\n    <X className=\"h-3.5 w-3.5 stroke-[3]\" aria-hidden=\"true\" />\n  </ToastPrimitive.Close>\n))\nToastClose.displayName = ToastPrimitive.Close.displayName\n\n/* -------------------------------------------------------------------------------------------------\n * Toaster — renders the store as a brutalist pile: collapsed, older toasts peek out behind the\n * newest as offset hard-edged cards; on hover/focus the pile fans out into a list.\n * -----------------------------------------------------------------------------------------------*/\n\nconst variantBlock: Record<Exclude<ToastVariant, 'default'>, { className: string; Icon: typeof Info }> = {\n  success: { className: 'bg-success text-success-foreground', Icon: CircleCheck },\n  error: { className: 'bg-destructive text-destructive-foreground', Icon: CircleX },\n  warning: { className: 'bg-warning text-warning-foreground', Icon: TriangleAlert },\n  info: { className: 'bg-info text-info-foreground', Icon: Info },\n  loading: { className: 'bg-muted text-foreground', Icon: LoaderCircle },\n}\n\nconst positionClasses: Record<ToastPosition, { viewport: string; toast: string }> = {\n  'top-left': {\n    viewport: 'left-4 top-4',\n    toast: 'top-0 after:top-full data-[state=open]:slide-in-from-top-full data-[state=closed]:slide-out-to-left-full',\n  },\n  'top-right': {\n    viewport: 'right-4 top-4',\n    toast: 'top-0 after:top-full data-[state=open]:slide-in-from-top-full data-[state=closed]:slide-out-to-right-full',\n  },\n  'top-center': {\n    viewport: 'left-1/2 top-4 -translate-x-1/2',\n    toast: 'top-0 after:top-full data-[state=open]:slide-in-from-top-full data-[state=closed]:slide-out-to-right-full',\n  },\n  'bottom-left': {\n    viewport: 'bottom-4 left-4',\n    toast: 'bottom-0 after:bottom-full data-[state=open]:slide-in-from-bottom-full data-[state=closed]:slide-out-to-left-full',\n  },\n  'bottom-right': {\n    viewport: 'bottom-4 right-4',\n    toast: 'bottom-0 after:bottom-full data-[state=open]:slide-in-from-bottom-full data-[state=closed]:slide-out-to-right-full',\n  },\n  'bottom-center': {\n    viewport: 'bottom-4 left-1/2 -translate-x-1/2',\n    toast: 'bottom-0 after:bottom-full data-[state=open]:slide-in-from-bottom-full data-[state=closed]:slide-out-to-right-full',\n  },\n}\n\n/** Pixel offsets of the collapsed pile and the gap of the expanded list. */\nconst PILE_X = 6\nconst PILE_Y = 10\nconst LIST_GAP = 12\n\n/**\n * Rendered inside the Radix toast: marks it as mounted, and removes the record once Radix unmounts\n * it (after its exit animation).\n */\nfunction ExitTracker({ id, onMount }: { id: string; onMount: () => void }) {\n  React.useEffect(() => {\n    onMount()\n    return () => toastStore.remove(id)\n  }, [id, onMount])\n  return null\n}\n\n/**\n * Radix computes a toast's announcement once, on mount, so in-place updates (toast.promise,\n * re-adding an id) would be silent. This live region announces each update: it mounts empty and\n * fills a frame later (so screen readers notice the change), then clears itself.\n */\nfunction UpdateAnnouncer({ label, assertive, children }: { label: string; assertive: boolean; children: React.ReactNode }) {\n  const [phase, setPhase] = React.useState<'empty' | 'announce' | 'done'>('empty')\n  React.useEffect(() => {\n    const frame = requestAnimationFrame(() => setPhase('announce'))\n    const timer = window.setTimeout(() => setPhase('done'), 1000)\n    return () => {\n      cancelAnimationFrame(frame)\n      window.clearTimeout(timer)\n    }\n  }, [])\n  if (phase === 'done') return null\n  return (\n    <div\n      data-slot=\"toast-announcer\"\n      role=\"status\"\n      aria-live={assertive ? 'assertive' : 'polite'}\n      aria-atomic=\"true\"\n      className=\"sr-only\"\n    >\n      {phase === 'announce' && (\n        <>\n          {label} {children}\n        </>\n      )}\n    </div>\n  )\n}\n\ninterface ToasterItemProps {\n  toast: ToastRecord\n  index: number\n  offsetY: number\n  expanded: boolean\n  position: ToastPosition\n  /** The Toaster's default duration (Radix falls back to it for `0`/`undefined`). */\n  defaultDuration: number\n  label: string\n  /** Reports the toast's natural height; `null` when it unmounts. */\n  onHeight: (id: string, height: number | null) => void\n}\n\nfunction ToasterItem({\n  toast: t,\n  index,\n  offsetY,\n  expanded,\n  position,\n  defaultDuration,\n  label,\n  onHeight,\n}: ToasterItemProps) {\n  // Callback-ref state: Radix renders the toast only once the viewport has mounted.\n  const [content, setContent] = React.useState<HTMLDivElement | null>(null)\n  const variant = t.variant ?? 'default'\n  const block = variant === 'default' ? null : variantBlock[variant]\n  const yDir = position.startsWith('top') ? 1 : -1\n  const xDir = position.endsWith('left') ? 1 : -1\n\n  // Natural height = content (never stretched: `self-start`) + the card's borders. Measured on the\n  // content because behind-cards are clamped to the front card's height.\n  const measure = React.useCallback(() => {\n    const card = content?.parentElement\n    if (!content || !card) return\n    onHeight(t.id, content.offsetHeight + card.offsetHeight - card.clientHeight)\n  }, [content, onHeight, t.id])\n\n  React.useLayoutEffect(measure, [measure, t.updateKey])\n  React.useEffect(() => {\n    if (!content || typeof ResizeObserver === 'undefined') return\n    const observer = new ResizeObserver(measure)\n    observer.observe(content)\n    return () => observer.disconnect()\n  }, [content, measure])\n  React.useEffect(() => () => onHeight(t.id, null), [onHeight, t.id])\n\n  // Closed before Radix ever mounted it (e.g. toast() + dismiss() in one tick): there is no exit\n  // animation to wait for, so drop the record now.\n  const mounted = React.useRef(false)\n  const markMounted = React.useCallback(() => {\n    mounted.current = true\n  }, [])\n  React.useEffect(() => {\n    if (!t.open && !mounted.current) toastStore.remove(t.id)\n  }, [t.open, t.id])\n\n  // Radix restarts the dismiss timer only when `open` or `duration` changes, so a re-add with the\n  // same id nudges the duration by an imperceptible 1µs to restart it.\n  const duration = (t.duration || defaultDuration) + (t.restartKey % 2 ? 0.001 : 0)\n\n  return (\n    <>\n      <Toast\n        open={t.open}\n        onOpenChange={(open) => {\n          if (!open) toastStore.dismiss(t.id)\n        }}\n        duration={duration}\n        type={variant === 'error' ? 'foreground' : 'background'}\n        data-variant={variant}\n        data-index={index}\n        data-behind={index > 0 ? '' : undefined}\n        data-expanded={expanded ? '' : undefined}\n        className={cn(\n          'absolute inset-x-0',\n          // stack geometry: collapsed pile ⇄ expanded list\n          'transition-[translate,height] [translate:var(--toast-pile-x)_var(--toast-pile-y)] data-[expanded]:[translate:0_var(--toast-list-y)]',\n          '[&[data-behind]:not([data-expanded])]:h-[var(--toast-front-height)] [&[data-behind]:not([data-expanded])]:overflow-hidden',\n          // bridges the gap between expanded toasts so hovering it doesn't collapse the list\n          'after:absolute after:inset-x-0 after:h-3',\n          positionClasses[position].toast\n        )}\n        style={\n          {\n            zIndex: 100 - index,\n            '--toast-pile-x': `${index * PILE_X * xDir}px`,\n            '--toast-pile-y': `${index * PILE_Y * yDir}px`,\n            '--toast-list-y': `${(offsetY + index * LIST_GAP) * yDir}px`,\n          } as React.CSSProperties\n        }\n      >\n        {block && (\n          <div\n            data-slot=\"toast-icon\"\n            className={cn('flex w-11 shrink-0 items-center justify-center border-r-3 border-foreground', block.className)}\n          >\n            <block.Icon\n              aria-hidden=\"true\"\n              className={cn('h-5 w-5 stroke-[2.5]', variant === 'loading' && 'animate-spin motion-reduce:animate-none')}\n            />\n          </div>\n        )}\n        <div\n          ref={setContent}\n          className=\"flex min-w-0 flex-1 items-start gap-3 self-start p-3 pr-10 transition-opacity duration-200 [[data-behind]:not([data-expanded])>&]:pointer-events-none [[data-behind]:not([data-expanded])>&]:opacity-0 motion-reduce:transition-none\">\n          <div className=\"grid min-w-0 flex-1 gap-1\">\n            {t.title && <ToastTitle>{t.title}</ToastTitle>}\n            {t.description && <ToastDescription>{t.description}</ToastDescription>}\n          </div>\n          {t.action && (\n            <ToastAction altText={t.action.altText ?? t.action.label} onClick={t.action.onClick}>\n              {t.action.label}\n            </ToastAction>\n          )}\n          <ToastClose />\n        </div>\n        <ExitTracker id={t.id} onMount={markMounted} />\n      </Toast>\n      {t.open && t.updateKey > 0 && (\n        <UpdateAnnouncer key={t.updateKey} label={label} assertive={variant === 'error'}>\n          {t.title} {t.description}\n        </UpdateAnnouncer>\n      )}\n    </>\n  )\n}\n\ninterface ToasterProps\n  extends Omit<React.ComponentPropsWithoutRef<typeof ToastPrimitive.Viewport>, 'children'> {\n  /** Where the stack sits on screen. */\n  position?: ToastPosition\n  /** Maximum number of toasts kept open; older ones are dismissed. */\n  max?: number\n  /** Default auto-dismiss time in ms. */\n  duration?: number\n  /** Screen-reader label prefixed to every announcement. */\n  label?: string\n}\n\n/** Mount once, near the root of your app. */\nfunction Toaster({\n  position = 'bottom-right',\n  max = 3,\n  duration = 5000,\n  label = 'Notification',\n  className,\n  style,\n  ...props\n}: ToasterProps) {\n  const toasts = useToasts()\n  const [heights, setHeights] = React.useState<Record<string, number>>({})\n  const [hovering, setHovering] = React.useState(false)\n  const [focused, setFocused] = React.useState(false)\n  const hasOpen = toasts.some((t) => t.open)\n  const expanded = hasOpen && (hovering || focused)\n\n  // Radix portals each toast into the viewport, so React's synthetic events\n  // from a toast never reach the viewport's handlers (they follow the component\n  // tree, where toasts aren't the viewport's children). Native events follow\n  // the DOM, where they are.\n  const viewportRef = React.useRef<HTMLOListElement>(null)\n  React.useEffect(() => {\n    toasterCount++\n    return () => {\n      toasterCount--\n    }\n  }, [])\n  React.useEffect(() => {\n    const el = viewportRef.current\n    if (!el) return\n    const enter = () => setHovering(true)\n    const leave = () => setHovering(false)\n    const focusIn = () => setFocused(true)\n    const focusOut = (e: FocusEvent) => {\n      if (!el.contains(e.relatedTarget as Node | null)) setFocused(false)\n    }\n    el.addEventListener('pointermove', enter)\n    el.addEventListener('pointerleave', leave)\n    el.addEventListener('focusin', focusIn)\n    el.addEventListener('focusout', focusOut)\n    return () => {\n      el.removeEventListener('pointermove', enter)\n      el.removeEventListener('pointerleave', leave)\n      el.removeEventListener('focusin', focusIn)\n      el.removeEventListener('focusout', focusOut)\n    }\n  }, [])\n\n  const onHeight = React.useCallback((id: string, height: number | null) => {\n    setHeights((prev) => {\n      if (height === null) {\n        if (!(id in prev)) return prev\n        const { [id]: _removed, ...rest } = prev\n        return rest\n      }\n      return prev[id] === height ? prev : { ...prev, [id]: height }\n    })\n  }, [])\n\n  // Keep at most `max` toasts open — older ones are dismissed (and animate out).\n  React.useEffect(() => {\n    toasts\n      .filter((t) => t.open)\n      .slice(max)\n      .forEach((t) => toastStore.dismiss(t.id))\n  }, [toasts, max])\n\n  // Collapse again once the stack empties (adjusting state during render, not in an effect).\n  const [hadOpen, setHadOpen] = React.useState(hasOpen)\n  if (hadOpen !== hasOpen) {\n    setHadOpen(hasOpen)\n    if (!hasOpen) {\n      setHovering(false)\n      setFocused(false)\n    }\n  }\n\n  // Stack layout (newest first): open toasts get consecutive indexes and stacked offsets;\n  // closing toasts keep their place so they animate out where they were.\n  const layout: { toast: ToastRecord; index: number; offsetY: number }[] = []\n  let visibleIndex = 0\n  let offsetY = 0\n  for (const [i, t] of toasts.entries()) {\n    layout.push({ toast: t, index: t.open ? visibleIndex : i, offsetY })\n    if (t.open) {\n      offsetY += heights[t.id] ?? 0\n      visibleIndex++\n    }\n  }\n  const front = toasts.find((t) => t.open)\n  const frontHeight = front ? heights[front.id] ?? 0 : 0\n\n  return (\n    <ToastProvider\n      duration={duration}\n      label={label}\n      swipeDirection={position.endsWith('left') ? 'left' : 'right'}\n    >\n      {layout.map(({ toast: t, index, offsetY: y }) => (\n        <ToasterItem\n          key={t.id}\n          toast={t}\n          index={index}\n          offsetY={y}\n          expanded={expanded}\n          position={position}\n          defaultDuration={duration}\n          label={label}\n          onHeight={onHeight}\n        />\n      ))}\n      <ToastViewport\n        data-position={position}\n        data-expanded={expanded ? '' : undefined}\n        className={cn('h-[var(--toast-front-height)]', positionClasses[position].viewport, className)}\n        style={{ '--toast-front-height': `${frontHeight}px`, ...style } as React.CSSProperties}\n        {...props}\n        ref={viewportRef}\n      />\n    </ToastProvider>\n  )\n}\nToaster.displayName = 'Toaster'\n\nexport {\n  Toaster,\n  toast,\n  useToasts,\n  ToastProvider,\n  ToastViewport,\n  Toast,\n  ToastTitle,\n  ToastDescription,\n  ToastAction,\n  ToastClose,\n  type ToasterProps,\n  type ToastOptions,\n  type ToastVariant,\n  type ToastPosition,\n  type ToastActionOptions,\n  type ToastPromiseOptions,\n  type ToastRecord,\n}\n",
      "type": "registry:ui",
      "target": "components/ui/toast.tsx"
    }
  ],
  "type": "registry:ui"
}