{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "popover",
  "type": "registry:ui",
  "title": "Popover",
  "description": "Fluent 2-styled non-modal popover with shadcn APIs. Built on @base-ui/react's Popover for portalled positioning, focus management, and outside-press/Escape dismissal; the flyout uses Fluent shadow-16 elevation with a scale+fade open/close animation.",
  "author": "graundtech <https://github.com/graundtech/fluent2-react-kit>",
  "dependencies": [
    "@base-ui/react"
  ],
  "registryDependencies": [
    "https://fluent2-react-kit.graund.io/r/utils.json"
  ],
  "files": [
    {
      "path": "components/ui/popover.tsx",
      "type": "registry:ui",
      "target": "components/ui/popover.tsx",
      "content": "import { Popover as PopoverPrimitive } from \"@base-ui/react/popover\";\nimport type { ComponentProps } from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\n/**\n * Popover — Fluent 2-styled, shadcn-API popover (non-modal floating panel\n * anchored to a trigger, for rich content — forms, menus, previews — that\n * isn't a single-purpose listbox or tooltip).\n *\n * This is the kit's second popup/overlay component; it reuses the\n * portal → positioner → popup structure and motion recipe `select.tsx`\n * established (conventions §9) rather than inventing a new shape.\n *\n * ## Base UI mapping (conventions §9)\n * Behavior — focus management, portalling, open/close state, collision-aware\n * positioning, outside-press/Escape dismissal — genuinely needs a primitive,\n * so the parts wrap `@base-ui/react/popover` (namespace import, matching the\n * actual `export * as Popover from \"./index.parts.js\"` shape in node_modules,\n * exactly like `select.tsx`). shadcn part names are mapped onto Base UI's\n * model:\n *\n * | Exported (shadcn name) | Base UI primitive                                    |\n * | ----------------------- | ---------------------------------------------------- |\n * | `Popover`               | `Popover.Root`                                       |\n * | `PopoverTrigger`        | `Popover.Trigger`                                    |\n * | `PopoverContent`        | `Popover.Portal` + `Popover.Positioner` + `Popover.Popup` composed |\n *\n * `Popover.Arrow` / `Popover.Title` / `Popover.Description` / `Popover.Close`\n * / `Popover.Backdrop` / `Popover.Viewport` exist in Base UI but are out of\n * scope for this thin wrapper (no arrow ships by default per spec, and the\n * shadcn Popover API itself has no title/description/close parts) — reach\n * for `@base-ui/react/popover` directly if a consumer needs one of those.\n *\n * ## Divergences from the shadcn/Radix Popover API (all deliberate)\n * 1. **No `PopoverAnchor` export.** Radix ships a dedicated `Popover.Anchor`\n *    part you wrap around an arbitrary element to anchor the content\n *    somewhere other than the trigger. Base UI has no equivalent part —\n *    positioning against a different element is instead a plain `anchor`\n *    prop on `Popover.Positioner` (`Element | VirtualElement |\n *    RefObject<Element | null> | (() => Element | VirtualElement | null)`,\n *    confirmed in `positioner/PopoverPositioner.d.ts` via\n *    `UseAnchorPositioningSharedParameters`). Re-exposing that as its own\n *    \"Anchor\" component would have nothing to render (no DOM wrapper needed\n *    around the target element), so `PopoverContent` instead accepts an\n *    `anchor` prop that forwards straight to the `Positioner`: pass a ref to\n *    the element you want to anchor against instead of wrapping it.\n * 2. **Composition uses Base UI's `render` prop, not `asChild`.**\n *    `Popover.Trigger` renders its own `<button>` (Base UI's model — there is\n *    no Slot-style boolean). `PopoverPrimitive.Trigger.Props` already includes\n *    Base UI's `render` prop, so no extra plumbing is needed to point the\n *    trigger at another element (e.g. the kit's `Button`):\n *    `<PopoverTrigger render={<Button variant=\"outline\">Open</Button>} />`.\n *    This is the direct Base UI analogue of shadcn's\n *    `<PopoverTrigger asChild><Button /></PopoverTrigger>`, same reasoning\n *    `tooltip.tsx` and `dropdown-menu.tsx` document for their own triggers.\n * 3. **The popup's role is `dialog` (non-modal), not the Radix default.**\n *    Base UI's `Popover.Popup` always renders `role=\"dialog\"` (confirmed in\n *    `popup/PopoverPopup.js`) and `Popover.Trigger` sets\n *    `aria-haspopup=\"dialog\"` + `aria-expanded` + `aria-controls`\n *    accordingly. `Popover.Root`'s `modal` prop defaults to `false` (no\n *    `aria-modal`, no scroll lock, outside interaction stays live) and is\n *    left at that default here — Radix's Popover is non-modal by default\n *    too, so behavior matches even though this wrapper doesn't set the prop\n *    itself. Pass `modal` through `<Popover>` (it forwards to `Popover.Root`)\n *    if a consumer needs modal behavior.\n * 4. **`sideOffset` defaults to `4`, not Base UI's `0`.** Same shadcn-parity\n *    override `select.tsx` makes for `SelectContent`; `align=\"center\"` and\n *    `side=\"bottom\"` are already Base UI's own defaults, restated here as\n *    explicit defaults for clarity and so `PopoverContent`'s JSDoc is the one\n *    place all three live.\n * 5. **`PopoverContent` needs an accessible name from the consumer.** Because\n *    the popup is forced to `role=\"dialog\"` (divergence 3), axe-core's\n *    `aria-dialog-name` rule fails if it has neither `aria-label` nor\n *    `aria-labelledby` — unlike Radix's Popover.Content, which is a plain\n *    (unroled) div and has no such requirement. This wrapper doesn't ship a\n *    `PopoverTitle`/heading part (out of scope, matching the shadcn API\n *    surface), so give `PopoverContent` an explicit `aria-label` (e.g.\n *    `aria-label=\"Edit dimensions\"`) or point `aria-labelledby` at a heading\n *    rendered inside it. `popover.test.tsx` and the preview page both do\n *    this on every example.\n *\n * ## `\"use client\"` — intentionally omitted\n * Every Base UI Popover part this file uses (`Root`, `Trigger`, `Portal`,\n * `Positioner`, `Popup`) carries its own `'use client'` directive at the\n * source level (verified in the compiled package output, same check\n * `avatar.tsx` documents), so they're already client boundaries on their\n * own. This wrapper has no hooks/handlers and — unlike `select.tsx` /\n * `checkbox.tsx` — no `@fluentui/react-icons` import (no arrow renders by\n * default), so none of the triggers in conventions §2/§9 that would force\n * `\"use client\"` apply here. The file stays a plain, server-renderable\n * module per conventions §2: a Server Component tree can render these\n * Client Component children without the parent re-declaring the directive.\n * Confirmed via `pnpm typecheck` + the test suite; add the directive here\n * only if that stops holding true.\n *\n * ## data-slot note\n * `Popover` (Root) renders no DOM element of its own (same as `Select`) so it\n * carries no `data-slot`. Every part that renders an element does —\n * `popover-trigger`, `popover-positioner`, `popover-content` — which is what\n * tests/consumers hook onto.\n */\n\nfunction Popover(props: PopoverPrimitive.Root.Props) {\n  return <PopoverPrimitive.Root {...props} />;\n}\n\nfunction PopoverTrigger({\n  className,\n  ...props\n}: PopoverPrimitive.Trigger.Props) {\n  return (\n    <PopoverPrimitive.Trigger\n      data-slot=\"popover-trigger\"\n      className={cn(className)}\n      {...props}\n    />\n  );\n}\n\n/**\n * Content — the floating panel: `Portal` → `Positioner` → `Popup`. Surface is\n * `bg-popover` + `border` + `shadow-16` (Fluent flyout elevation, conventions\n * §3.6), `rounded-md`, `w-72 p-4` (shadcn parity default size for prose/form\n * content — override with `className` for a differently sized panel). A\n * subtle scale+fade open/close animation rides Base UI's\n * `data-starting-style`/`data-ending-style` hooks with the same token\n * durations/easings `SelectContent` uses. No arrow by default (divergence\n * table above) — `origin-[var(--transform-origin)]` keeps the scale\n * animation's pivot correct regardless of which side the popup lands on.\n */\nfunction PopoverContent({\n  className,\n  children,\n  sideOffset = 4,\n  align = \"center\",\n  side = \"bottom\",\n  anchor,\n  ...props\n}: ComponentProps<typeof PopoverPrimitive.Popup> &\n  Pick<\n    ComponentProps<typeof PopoverPrimitive.Positioner>,\n    \"side\" | \"align\" | \"sideOffset\" | \"anchor\"\n  >) {\n  return (\n    <PopoverPrimitive.Portal>\n      <PopoverPrimitive.Positioner\n        data-slot=\"popover-positioner\"\n        sideOffset={sideOffset}\n        align={align}\n        side={side}\n        anchor={anchor}\n        className=\"z-50 outline-none\"\n      >\n        <PopoverPrimitive.Popup\n          data-slot=\"popover-content\"\n          className={cn(\n            \"w-72 origin-[var(--transform-origin)] rounded-md border bg-popover p-4 text-popover-foreground shadow-16 outline-none\",\n            // motion — subtle scale + fade on open (enter) / close (exit)\n            \"transition-[opacity,scale] duration-fast 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        </PopoverPrimitive.Popup>\n      </PopoverPrimitive.Positioner>\n    </PopoverPrimitive.Portal>\n  );\n}\n\nexport { Popover, PopoverTrigger, PopoverContent };\n"
    }
  ],
  "docs": "There is no `PopoverAnchor` export — Base UI has no dedicated anchor part; pass a ref via `PopoverContent`'s `anchor` prop instead of wrapping a target element. `PopoverContent` renders `role=\"dialog\"` (a Base UI default, unlike Radix's plain-div content), so axe-core's `aria-dialog-name` rule requires it to have an accessible name: give it an explicit `aria-label` or point `aria-labelledby` at a heading you render inside. See `popover.tsx`'s doc comment for the full divergence list."
}
