{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "dialog",
  "type": "registry:ui",
  "title": "Dialog",
  "description": "Fluent 2-styled modal dialog with shadcn APIs. Built on @base-ui/react's Dialog for focus trapping, portalling, scroll locking and Escape/outside-press dismissal; the popup is a centered bg-background surface with Fluent shadow-64 elevation and rounded-xl radius over a theme-invariant black/40 smoke backdrop, with a built-in DismissRegular close button.",
  "author": "graundtech <https://github.com/graundtech/fluent2-react-kit>",
  "dependencies": [
    "@base-ui/react",
    "@fluentui/react-icons"
  ],
  "registryDependencies": [
    "https://fluent2-react-kit.graund.io/r/utils.json"
  ],
  "files": [
    {
      "path": "components/ui/dialog.tsx",
      "type": "registry:ui",
      "target": "components/ui/dialog.tsx",
      "content": "\"use client\";\n\nimport { Dialog as DialogPrimitive } from \"@base-ui/react/dialog\";\nimport { DismissRegular } from \"@fluentui/react-icons\";\nimport type { ComponentProps } from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\n/**\n * Dialog — Fluent 2-styled, shadcn-API modal dialog.\n *\n * Follows the overlay patterns set by `select.tsx` (portal → popup surface,\n * `data-starting-style`/`data-ending-style` motion with token durations and\n * easings) with dialog-specific elevation: `shadow-64` + `rounded-xl`\n * (conventions §3.4/§3.6 — dialogs are the kit's highest floating surface).\n *\n * ## Base UI mapping (conventions §9)\n * Behavior — focus trapping, portalling, open/close state, scroll locking,\n * outside-press and Escape dismissal — genuinely needs a primitive, so the\n * parts wrap `@base-ui/react/dialog` (namespace import, matching the actual\n * export shape in node_modules, exactly like `select.tsx`). shadcn part names\n * are mapped onto Base UI's model:\n *\n * | Exported (shadcn name) | Base UI primitive                                   |\n * | ---------------------- | --------------------------------------------------- |\n * | `Dialog`               | `Dialog.Root`                                       |\n * | `DialogTrigger`        | `Dialog.Trigger`                                    |\n * | `DialogPortal`         | `Dialog.Portal`                                     |\n * | `DialogOverlay`        | `Dialog.Backdrop`  ← see divergence 1               |\n * | `DialogContent`        | `Dialog.Portal` + `Dialog.Backdrop` + `Dialog.Popup` (+ built-in ✕ `Dialog.Close`) |\n * | `DialogHeader`         | plain `<div>` (shadcn parity — no Base UI part)     |\n * | `DialogFooter`         | plain `<div>` (shadcn parity — no Base UI part)     |\n * | `DialogTitle`          | `Dialog.Title` (renders `<h2>`)                     |\n * | `DialogDescription`    | `Dialog.Description` (renders `<p>`)                |\n * | `DialogClose`          | `Dialog.Close`                                      |\n *\n * Base UI wires the popup's `aria-labelledby` to `DialogTitle` and\n * `aria-describedby` to `DialogDescription` automatically — no manual ids.\n *\n * ## Divergences from the shadcn/Radix Dialog API (all deliberate)\n * 1. **`DialogOverlay` wraps Base UI `Dialog.Backdrop`, not a Radix `Overlay`.**\n *    Same role (the dimming layer under the popup), different primitive name.\n *    Note Base UI skips rendering the backdrop for *nested* dialogs unless\n *    `forceRender` is set — parent smoke already covers the viewport.\n * 2. **`modal` defaults to `true`** (focus trap + page scroll lock + outside\n *    pointer interactions disabled), matching Radix. Base UI adds a third mode,\n *    `modal=\"trap-focus\"` (trap focus without scroll lock), which passes\n *    straight through.\n * 3. **`DialogContent` composes `Portal` + `Backdrop` + `Popup`** and appends a\n *    top-right ✕ close button (shadcn parity — shadcn composes the same three\n *    Radix parts). Pass `showCloseButton={false}` to omit the ✕ (matching\n *    current shadcn's `DialogContent` prop).\n * 4. **No `asChild`.** Base UI composes via the `render` prop instead — e.g.\n *    `<DialogTrigger render={<Button variant=\"secondary\" />}>Open</DialogTrigger>`.\n *    Every part forwards it.\n * 5. **`onOpenChange(open, eventDetails)`** — Base UI passes a second\n *    `eventDetails` argument (reason + native event). The first argument is the\n *    boolean Radix passes, so existing shadcn call sites work unchanged.\n * 6. **`Dialog.Viewport` is not re-exported.** It's a Base UI layout helper for\n *    scrollable full-viewport dialogs with no shadcn equivalent; the kit centers\n *    the popup with fixed positioning like shadcn. Compose it manually from\n *    `@base-ui/react/dialog` if you need it.\n *\n * ## Smoke layer (`bg-black/40`) — sanctioned hardcoded color\n * The backdrop is black at 40% opacity in *both* themes, per Fluent's smoke\n * layer (it dims light and dark surfaces alike). Conventions §3.7 bans\n * hardcoded colors so a shade can't drift from the token set, but the smoke\n * layer is intentionally theme-invariant — there is no token for it and\n * inventing one would imply it re-points in dark mode, which it must not.\n * `bg-black/40` is the sanctioned exception here.\n *\n * ## Motion note — the popup zoom animates `scale`, not `transform`\n * Tailwind v4 emits `translate-*` and `scale-*` as the independent CSS\n * `translate:`/`scale:` properties (not a shared `transform:`), so the\n * -50%/-50% centering translate and the open/close zoom coexist without\n * clobbering each other — but it also means the popup's transition must target\n * `transition-[opacity,scale]` (a `transition-[transform,opacity]` list would\n * leave the zoom snapping — every popup in the kit uses `[opacity,scale]` for\n * this reason).\n *\n * ## `\"use client\"` — required\n * Two reasons. The Base UI Dialog parts manage client state (each part module\n * carries 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()`\n * at module scope without its own directive, so importing an icon from a\n * Server Component breaks `next build`. See conventions §9; `select.tsx` and\n * `checkbox.tsx` are the precedents.\n *\n * ## data-slot note\n * `Dialog` (Root) renders no DOM element of its own, so it carries no\n * `data-slot`. Every part that renders an element does — `dialog-trigger`,\n * `dialog-overlay`, `dialog-content`, etc.\n */\n\nfunction Dialog<Payload>(props: DialogPrimitive.Root.Props<Payload>) {\n  return <DialogPrimitive.Root {...props} />;\n}\n\nfunction DialogTrigger({\n  className,\n  ...props\n}: ComponentProps<typeof DialogPrimitive.Trigger>) {\n  return (\n    <DialogPrimitive.Trigger\n      data-slot=\"dialog-trigger\"\n      className={cn(className)}\n      {...props}\n    />\n  );\n}\n\nfunction DialogPortal(props: ComponentProps<typeof DialogPrimitive.Portal>) {\n  return <DialogPrimitive.Portal data-slot=\"dialog-portal\" {...props} />;\n}\n\n/**\n * Overlay — Fluent's smoke layer: black at 40% opacity over the full viewport\n * in both themes (see the smoke-layer note above), fading in/out via Base UI's\n * `data-starting-style`/`data-ending-style` hooks with token durations/easings.\n */\nfunction DialogOverlay({\n  className,\n  ...props\n}: ComponentProps<typeof DialogPrimitive.Backdrop>) {\n  return (\n    <DialogPrimitive.Backdrop\n      data-slot=\"dialog-overlay\"\n      className={cn(\n        \"fixed inset-0 z-50 bg-black/40\",\n        // motion — fade on open (enter) / close (exit)\n        \"transition-opacity duration-normal ease-decelerate-mid\",\n        \"data-starting-style:opacity-0\",\n        \"data-ending-style:opacity-0 data-ending-style:ease-accelerate-mid\",\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\n/**\n * Content — the modal surface: `Portal` → `Backdrop` → `Popup`, centered with\n * fixed positioning, on `bg-background` with `rounded-xl` + `shadow-64`\n * (dialog elevation, conventions §3.6). A fade + slight zoom rides the\n * `data-starting-style`/`data-ending-style` hooks; the zoom animates the CSS\n * `scale` property (see the motion note above). The built-in ✕ close button\n * sits top-right (`showCloseButton={false}` omits it).\n */\nfunction DialogContent({\n  className,\n  children,\n  showCloseButton = true,\n  ...props\n}: ComponentProps<typeof DialogPrimitive.Popup> & {\n  showCloseButton?: boolean;\n}) {\n  return (\n    <DialogPortal>\n      <DialogOverlay />\n      <DialogPrimitive.Popup\n        data-slot=\"dialog-content\"\n        className={cn(\n          // layout — centered fixed surface, dialog radius + elevation\n          \"fixed top-[50%] left-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 rounded-xl border bg-background p-6 text-foreground shadow-64 outline-none\",\n          // motion — fade + slight zoom; `scale`, not `transform` (see doc note)\n          \"transition-[opacity,scale] duration-normal ease-decelerate-mid\",\n          \"data-starting-style:scale-95 data-starting-style:opacity-0\",\n          \"data-ending-style:scale-95 data-ending-style:opacity-0 data-ending-style:ease-accelerate-mid\",\n          className\n        )}\n        {...props}\n      >\n        {children}\n        {showCloseButton && (\n          <DialogPrimitive.Close\n            data-slot=\"dialog-close\"\n            className={cn(\n              \"absolute top-4 right-4 flex size-8 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            )}\n          >\n            <DismissRegular className=\"size-4\" />\n            <span className=\"sr-only\">Close</span>\n          </DialogPrimitive.Close>\n        )}\n      </DialogPrimitive.Popup>\n    </DialogPortal>\n  );\n}\n\nfunction DialogHeader({ className, ...props }: ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"dialog-header\"\n      className={cn(\"flex flex-col gap-1.5 text-center sm:text-left\", className)}\n      {...props}\n    />\n  );\n}\n\nfunction DialogFooter({ className, ...props }: ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"dialog-footer\"\n      className={cn(\n        \"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end\",\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction DialogTitle({\n  className,\n  ...props\n}: ComponentProps<typeof DialogPrimitive.Title>) {\n  return (\n    <DialogPrimitive.Title\n      data-slot=\"dialog-title\"\n      className={cn(\"text-xl font-semibold\", className)}\n      {...props}\n    />\n  );\n}\n\nfunction DialogDescription({\n  className,\n  ...props\n}: ComponentProps<typeof DialogPrimitive.Description>) {\n  return (\n    <DialogPrimitive.Description\n      data-slot=\"dialog-description\"\n      className={cn(\"text-sm text-muted-foreground\", className)}\n      {...props}\n    />\n  );\n}\n\nfunction DialogClose({\n  className,\n  ...props\n}: ComponentProps<typeof DialogPrimitive.Close>) {\n  return (\n    <DialogPrimitive.Close\n      data-slot=\"dialog-close\"\n      className={cn(className)}\n      {...props}\n    />\n  );\n}\n\nexport {\n  Dialog,\n  DialogTrigger,\n  DialogPortal,\n  DialogOverlay,\n  DialogContent,\n  DialogHeader,\n  DialogFooter,\n  DialogTitle,\n  DialogDescription,\n  DialogClose,\n};\n"
    }
  ],
  "docs": "`DialogContent` composes Base UI's `Portal` + `Backdrop` + `Popup` and appends a top-right close button (pass `showCloseButton={false}` to omit it). There is no `asChild` — Base UI composes via the `render` prop instead, e.g. `<DialogTrigger render={<Button variant=\"secondary\" />}>Open</DialogTrigger>`. `DialogTitle`/`DialogDescription` are wired to the popup's `aria-labelledby`/`aria-describedby` automatically; always render a `DialogTitle` so the dialog has an accessible name. See `dialog.tsx`'s doc comment for the full Base UI mapping and divergence notes."
}
