{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "toast",
  "type": "registry:ui",
  "title": "Toast",
  "description": "Fluent 2-styled, shadcn-API toast notifications on Base UI's toast manager. A Provider + portalled Viewport with a stacked, swipeable, auto-dismissing toast surface. Five variants (default, destructive, success, warning, info) reuse the status-extension tokens, with a useToast() hook (Base UI's manager) to fire toasts.",
  "author": "graundtech <https://github.com/graundtech/fluent2-react-kit>",
  "dependencies": [
    "@base-ui/react",
    "@fluentui/react-icons",
    "class-variance-authority"
  ],
  "registryDependencies": [
    "https://fluent2-react-kit.graund.io/r/utils.json"
  ],
  "files": [
    {
      "path": "components/ui/toast.tsx",
      "type": "registry:ui",
      "target": "components/ui/toast.tsx",
      "content": "\"use client\";\n\nimport { Toast as ToastPrimitive } from \"@base-ui/react/toast\";\nimport { DismissRegular } from \"@fluentui/react-icons\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport type { ComponentProps } from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\n/**\n * Toast — Fluent 2-styled, shadcn-API toast notifications.\n *\n * Follows the overlay patterns set by `select.tsx`/`dialog.tsx` (portal + a\n * floating surface, Base UI `data-starting-style`/`data-ending-style` transition\n * hooks) and is the designated next consumer of the status-extension tokens the\n * `alert.tsx` variants introduced: the `variant` cva mirrors Alert's treatment\n * (`*-subtle` fills, `*-border` borders, `*-text`/status accents on the title),\n * so a toast reads as the transient sibling of the inline Alert.\n *\n * ## Base UI mapping (conventions §9)\n * Behavior — the toast queue/manager, auto-dismiss timers, priority-based\n * live-region announcement, F6 viewport landmark, portalling, swipe-to-dismiss,\n * and the stacking/offset CSS variables — genuinely needs a primitive, so the\n * parts wrap `@base-ui/react/toast` (namespace import, matching the actual\n * export shape in node_modules, exactly like `select.tsx`/`dialog.tsx`). shadcn\n * part names are mapped onto Base UI's model:\n *\n * | Exported (shadcn name) | Base UI primitive                                    |\n * | ---------------------- | ---------------------------------------------------- |\n * | `ToastProvider`        | `Toast.Provider`                                     |\n * | `ToastViewport`        | `Toast.Portal` + `Toast.Viewport`  ← see divergence 1 |\n * | `Toast`                | `Toast.Root` + `Toast.Content`     ← see divergence 2 |\n * | `ToastTitle`           | `Toast.Title` (renders `<h2>`)                       |\n * | `ToastDescription`     | `Toast.Description` (renders `<p>`)                  |\n * | `ToastAction`          | `Toast.Action` (renders `<button>`)                 |\n * | `ToastClose`           | `Toast.Close` (renders `<button>`, ✕ via `DismissRegular`) |\n * | `useToast`             | `Toast.useToastManager`            ← see divergence 3 |\n *\n * ## Firing a toast — how the wrapper maps to Base UI's manager (the API)\n * Unlike shadcn's sonner-based `toast()` global, Base UI has no\n * render-anywhere singleton: toasts live in a manager scoped to the nearest\n * `ToastProvider`, and each visible toast is rendered by a caller-owned map over\n * `useToast().toasts`. The kit keeps that model honest (same call as `select.tsx`\n * did with `SelectValue`) rather than papering a fake global over it:\n *\n * ```tsx\n * function ToastList() {\n *   const { toasts } = useToast();\n *   return toasts.map((toast) => (\n *     <Toast key={toast.id} toast={toast} variant={toast.data?.variant}>\n *       <ToastTitle />\n *       <ToastDescription />\n *       <ToastClose />\n *     </Toast>\n *   ));\n * }\n *\n * function Fire() {\n *   const { add } = useToast();\n *   return (\n *     <button onClick={() => add({\n *       title: \"Saved\",\n *       description: \"Your changes were saved.\",\n *       data: { variant: \"success\" },\n *     })}>Save</button>\n *   );\n * }\n * ```\n *\n * `useToast()` returns Base UI's `{ toasts, add, close, update, promise }`.\n * `add(options)` enqueues a toast and returns its id; `options.timeout` (ms,\n * default 5000 via the Provider, `0` disables) drives auto-dismiss;\n * `options.priority` (`'low'` polite / `'high'` assertive) drives the live-region\n * urgency; `options.data` carries arbitrary payload — the kit's convention is to\n * stash the visual `variant` there and read it back in the map (`toast.data?.\n * variant`). Empty `<ToastTitle />`/`<ToastDescription />` fall back to the\n * toast's `title`/`description` strings automatically (Base UI behavior), so the\n * list rarely needs children. `Toast.createToastManager()` (re-exported as\n * `createToastManager`) builds a manager usable outside React — pass it to\n * `<ToastProvider toastManager={…}>` to fire toasts from non-component code.\n *\n * ## Divergences from the shadcn/Radix Toast API (all deliberate)\n * 1. **`ToastViewport` folds `Toast.Portal` + `Toast.Viewport`.** shadcn's\n *    viewport is a bare positioned list; Base UI portals the viewport to\n *    `document.body` (so it escapes overflow/transform ancestors) and exposes it\n *    as a separate `Portal` part. The kit composes both, matching how\n *    `SelectContent` folds `Portal` + `Positioner` + `Popup`. Render it once,\n *    inside `ToastProvider`, with your `ToastList` map as its children.\n * 2. **`Toast` composes `Toast.Root` + `Toast.Content`; children render into the\n *    Content.** Base UI splits an individual toast into `Root` (the stacked,\n *    swipeable, height-animated box that carries the `toast` prop) and `Content`\n *    (the inner layout whose `data-behind`/`data-expanded` opacity fades the\n *    stacked-behind toasts back). Folding them keeps the shadcn-flat inner API —\n *    `<Toast><ToastTitle/><ToastDescription/><ToastClose/></Toast>` — while\n *    preserving the fade. `ToastClose` is absolutely positioned (shadcn parity),\n *    so it can be a direct child. The **required** `toast` prop is Base UI's per-\n *    toast object from the `useToast().toasts` map.\n * 3. **`useToast` is Base UI's `useToastManager`, not shadcn's `useToast()`.**\n *    shadcn's hook returns `{ toast, dismiss, toasts }` where `toast()` is a\n *    global fire-and-forget; Base UI's returns `{ toasts, add, close, update,\n *    promise }` scoped to the Provider. The kit re-exports it under the familiar\n *    `useToast` name but keeps Base UI's shape — call `add()` (not `toast()`) to\n *    fire, and render the `toasts` array yourself (divergence 2). See the firing\n *    recipe above.\n *\n * ## Motion note — the toast stack animates `transform`, NOT the popup `scale`\n * The kit's other overlays (`select`/`dialog`) forbid `transition-[transform,\n * opacity]` and require `transition-[opacity,scale]` (conventions §3.5): their\n * zoom rides Tailwind's `scale-*`/`translate-*` utilities, which v4 compiles to\n * the independent `scale:`/`translate:` CSS properties, so a `transform`-based\n * transition list would leave the zoom snapping. Toast is the **sanctioned\n * exception**: Base UI drives the entire stack — index offset, peek, scale-back,\n * swipe displacement, and enter/exit slide — through the `transform` *shorthand*\n * built from its `--toast-*` CSS variables (`--toast-index`, `--toast-offset-y`,\n * `--toast-height`, `--toast-swipe-movement-x/y`), so the recipe here is an\n * explicit `[transform:…]` arbitrary value with a matching `[transition:transform\n * …]` — Base UI's documented styling recipe, adapted to kit tokens. This is not\n * a §3.5 violation: no Tailwind `scale-*`/`translate-*` utility is used, so there\n * is no independent-property split to break. Opacity/height animate alongside in\n * the same explicit transition list because Base UI needs three coordinated\n * durations the token utilities can't express.\n *\n * ## `\"use client\"` — required\n * Two reasons, same as `dialog.tsx`. The Base UI Toast parts manage client\n * state (the manager context, timers, swipe listeners — each part module carries\n * its own `'use client'`), and — decisively — `DismissRegular` from\n * `@fluentui/react-icons` must stay inside a client boundary: the package's\n * shared icon-sizing module calls `@griffel/react`'s client-only `__styles()` at\n * module scope without its own directive, so importing an icon from a Server\n * Component breaks `next build`. See conventions §9; `select.tsx`/`checkbox.tsx`\n * are the precedents.\n *\n * ## data-slot note\n * `ToastProvider` renders no DOM element of its own (context only), so it\n * carries no `data-slot`. Every part that renders an element does —\n * `toast-viewport`, `toast` (Root), `toast-content`, `toast-title`, etc.\n */\n\nfunction ToastProvider(props: ComponentProps<typeof ToastPrimitive.Provider>) {\n  return <ToastPrimitive.Provider {...props} />;\n}\n\n/**\n * Viewport — the fixed bottom-right stack region, portalled to `document.body`.\n * `w-[22.5rem]` (matching Base UI's demo) on a comfortable viewport, collapsing\n * to `calc(100vw-2rem)` on narrow screens; `z-50` over app content. The stacked\n * toasts inside position themselves absolutely against this box (see `Toast`).\n */\nfunction ToastViewport({\n  className,\n  ...props\n}: ComponentProps<typeof ToastPrimitive.Viewport>) {\n  return (\n    <ToastPrimitive.Portal>\n      <ToastPrimitive.Viewport\n        data-slot=\"toast-viewport\"\n        className={cn(\n          \"fixed right-4 bottom-4 z-50 mx-auto flex w-[calc(100vw-2rem)] sm:right-6 sm:bottom-6 sm:w-[22.5rem]\",\n          className\n        )}\n        {...props}\n      />\n    </ToastPrimitive.Portal>\n  );\n}\n\n/**\n * Toast surface variants — mirror `alert.tsx`'s token usage exactly. `default`\n * is the neutral floating popover surface (`bg-popover`); the four status\n * variants reuse the status-extension tokens (`*-subtle` fill, `*-border`\n * border) with the intent color carried by the *title* (`ToastTitle`) and the\n * body left as `text-muted-foreground`, so status is never conveyed by\n * body-text color alone (conventions §5). See `alert.tsx`'s doc comment for the\n * per-variant contrast math against these same token pairs. The title accent is\n * targeted with a descendant selector (`[&_[data-slot=toast-title]]`) because\n * the title sits inside the folded `Content`, not as a direct Root child.\n */\nconst toastVariants = cva(\n  [\n    // --- Base UI stacking / swipe / enter-exit recipe (adapted from the Base UI\n    // Toast docs' bottom-right demo; the --toast-* vars are Base UI's contract).\n    // See the \"Motion note\" above for why this uses the `transform` shorthand.\n    \"[--gap:0.75rem] [--peek:0.75rem] [--scale:calc(max(0,1-(var(--toast-index)*0.1)))] [--shrink:calc(1-var(--scale))] [--height:var(--toast-frontmost-height,var(--toast-height))] [--offset-y:calc(var(--toast-offset-y)*-1+calc(var(--toast-index)*var(--gap)*-1)+var(--toast-swipe-movement-y))]\",\n    \"absolute right-0 bottom-0 left-auto z-[calc(1000-var(--toast-index))] mr-0 w-full origin-bottom\",\n    \"[transform:translateX(var(--toast-swipe-movement-x))_translateY(calc(var(--toast-swipe-movement-y)-(var(--toast-index)*var(--peek))-(var(--shrink)*var(--height))))_scale(var(--scale))]\",\n    // an invisible bridge below the toast so the pointer can move between stacked\n    // toasts without leaving the hover (Base UI recipe)\n    \"after:absolute after:top-full after:left-0 after:h-[calc(var(--gap)+1px)] after:w-full after:content-['']\",\n    \"h-[var(--height)] data-expanded:h-[var(--toast-height)]\",\n    \"data-expanded:[transform:translateX(var(--toast-swipe-movement-x))_translateY(calc(var(--offset-y)))]\",\n    // enter (starting) / exit (ending) slide + fade, plus the swipe-out offsets\n    \"data-starting-style:[transform:translateY(150%)]\",\n    \"data-ending-style:opacity-0 data-limited:opacity-0\",\n    \"[&[data-ending-style]:not([data-limited]):not([data-swipe-direction])]:[transform:translateY(150%)]\",\n    \"data-ending-style:data-[swipe-direction=down]:[transform:translateY(calc(var(--toast-swipe-movement-y)+150%))] data-expanded:data-ending-style:data-[swipe-direction=down]:[transform:translateY(calc(var(--toast-swipe-movement-y)+150%))]\",\n    \"data-ending-style:data-[swipe-direction=up]:[transform:translateY(calc(var(--toast-swipe-movement-y)-150%))] data-expanded:data-ending-style:data-[swipe-direction=up]:[transform:translateY(calc(var(--toast-swipe-movement-y)-150%))]\",\n    \"data-ending-style:data-[swipe-direction=left]:[transform:translateX(calc(var(--toast-swipe-movement-x)-150%))_translateY(var(--offset-y))] data-expanded:data-ending-style:data-[swipe-direction=left]:[transform:translateX(calc(var(--toast-swipe-movement-x)-150%))_translateY(var(--offset-y))]\",\n    \"data-ending-style:data-[swipe-direction=right]:[transform:translateX(calc(var(--toast-swipe-movement-x)+150%))_translateY(var(--offset-y))] data-expanded:data-ending-style:data-[swipe-direction=right]:[transform:translateX(calc(var(--toast-swipe-movement-x)+150%))_translateY(var(--offset-y))]\",\n    \"[transition:transform_0.5s_cubic-bezier(0.22,1,0.36,1),opacity_0.5s,height_0.15s]\",\n    // --- Fluent surface: flat floating card, medium radius, flyout elevation.\n    \"rounded-md border shadow-16 select-none\",\n    // F6-navigable; keyboard focus ring (conventions §4)\n    \"outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background\",\n  ],\n  {\n    variants: {\n      variant: {\n        default: \"border-border bg-popover text-popover-foreground\",\n        destructive:\n          \"border-destructive-border bg-destructive-subtle text-foreground [&_[data-slot=toast-title]]:text-destructive-text\",\n        success:\n          \"border-success-border bg-success-subtle text-foreground [&_[data-slot=toast-title]]:text-success\",\n        warning:\n          \"border-warning-border bg-warning-subtle text-foreground [&_[data-slot=toast-title]]:text-warning-text\",\n        info: \"border-brand-140 bg-brand-160 text-foreground [&_[data-slot=toast-title]]:text-brand-80 dark:border-brand-70 dark:bg-brand-30 dark:[&_[data-slot=toast-title]]:text-brand-100\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n    },\n  }\n);\n\n/**\n * Toast — one notification. Wraps `Toast.Root` (the stacked/swipeable/height-\n * animated box carrying the required `toast` prop and the `variant` surface) and\n * folds a `Toast.Content` inside (the inner flex layout whose\n * `data-behind`/`data-expanded` opacity fades stacked-behind toasts). `children`\n * (ToastTitle/Description/Action/Close) render into the Content; `ToastClose` is\n * absolutely positioned, so `pr-10` reserves room for it.\n */\nfunction Toast({\n  className,\n  variant = \"default\",\n  children,\n  ...props\n}: ComponentProps<typeof ToastPrimitive.Root> &\n  VariantProps<typeof toastVariants>) {\n  return (\n    <ToastPrimitive.Root\n      data-slot=\"toast\"\n      data-variant={variant}\n      className={cn(toastVariants({ variant, className }))}\n      {...props}\n    >\n      <ToastPrimitive.Content\n        data-slot=\"toast-content\"\n        className={cn(\n          \"relative flex h-full flex-col gap-1 overflow-hidden p-4 pr-10\",\n          // fade the stacked-behind content back in only when the viewport expands\n          \"transition-opacity duration-normal ease-decelerate-mid data-behind:opacity-0 data-expanded:opacity-100\"\n        )}\n      >\n        {children}\n      </ToastPrimitive.Content>\n    </ToastPrimitive.Root>\n  );\n}\n\nfunction ToastTitle({\n  className,\n  ...props\n}: ComponentProps<typeof ToastPrimitive.Title>) {\n  return (\n    <ToastPrimitive.Title\n      data-slot=\"toast-title\"\n      className={cn(\"text-sm font-semibold\", className)}\n      {...props}\n    />\n  );\n}\n\nfunction ToastDescription({\n  className,\n  ...props\n}: ComponentProps<typeof ToastPrimitive.Description>) {\n  return (\n    <ToastPrimitive.Description\n      data-slot=\"toast-description\"\n      className={cn(\"text-sm text-muted-foreground\", className)}\n      {...props}\n    />\n  );\n}\n\n/**\n * Action — an optional in-toast button (e.g. \"Undo\"). Styled as the kit's\n * neutral outline button in miniature (conventions §4 neutral ramp), sitting\n * left-aligned under the message. Base UI closes the toast after the action\n * fires unless the handler prevents it.\n */\nfunction ToastAction({\n  className,\n  ...props\n}: ComponentProps<typeof ToastPrimitive.Action>) {\n  return (\n    <ToastPrimitive.Action\n      data-slot=\"toast-action\"\n      className={cn(\n        \"mt-1 inline-flex h-8 w-fit shrink-0 items-center justify-center rounded-md border border-input bg-transparent px-3 text-sm font-medium\",\n        \"cursor-pointer outline-none transition-colors duration-fast ease-ease\",\n        \"hover:bg-accent hover:text-accent-foreground active:bg-accent/80 dark:bg-input/30 dark:hover:bg-input/50 dark:active:bg-input/70\",\n        \"focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background\",\n        \"disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50\",\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\n/**\n * Close — the icon-only ✕ dismiss button, absolutely positioned top-right (like\n * `dialog.tsx`'s built-in close). Accessible name comes from the `sr-only` span;\n * ✕ glyph is `DismissRegular` (which is what forces `\"use client\"`, §9). Base UI\n * marks it non-swipe-triggering automatically.\n */\nfunction ToastClose({\n  className,\n  ...props\n}: ComponentProps<typeof ToastPrimitive.Close>) {\n  return (\n    <ToastPrimitive.Close\n      data-slot=\"toast-close\"\n      className={cn(\n        \"absolute top-3 right-3 flex size-7 items-center justify-center rounded-md text-muted-foreground\",\n        \"cursor-pointer outline-none transition-colors duration-fast ease-ease\",\n        \"hover:bg-accent hover:text-accent-foreground active:bg-accent/80 dark:hover:bg-accent/50 dark:active:bg-accent/70\",\n        \"focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background\",\n        \"disabled:pointer-events-none disabled:opacity-50\",\n        \"[&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      {...props}\n    >\n      <DismissRegular />\n      <span className=\"sr-only\">Close</span>\n    </ToastPrimitive.Close>\n  );\n}\n\nconst useToast = ToastPrimitive.useToastManager;\nconst createToastManager = ToastPrimitive.createToastManager;\n\nexport {\n  Toast,\n  ToastProvider,\n  ToastViewport,\n  ToastTitle,\n  ToastDescription,\n  ToastAction,\n  ToastClose,\n  useToast,\n  createToastManager,\n  toastVariants,\n};\n"
    }
  ],
  "docs": "Toast follows Base UI's manager model, not shadcn's global `toast()`. Wrap your app (or a subtree) in `<ToastProvider>`, render `<ToastViewport>` once with a map over `useToast().toasts` inside it, and fire toasts imperatively with `useToast().add({ title, description, data: { variant } })`. The `variant` (default | destructive | success | warning | info) is your own payload on `data`, read back in the map as `<Toast variant={toast.data?.variant}>`. `useToast` is Base UI's `useToastManager` re-exported ({ toasts, add, close, update, promise }); `createToastManager` builds a manager for firing outside React (pass it as `<ToastProvider toastManager={…}>`). Empty `<ToastTitle />`/`<ToastDescription />` fall back to the toast's title/description strings. See `toast.tsx`'s doc comment for the full firing recipe and Base UI mapping."
}
