{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "toolbar",
  "type": "registry:ui",
  "title": "Toolbar",
  "description": "Fluent 2-styled toolbar part family built on @base-ui/react's Toolbar for a single tab stop, roving-tabindex arrow-key navigation, and composite focus. Six parts (Toolbar, ToolbarButton, ToolbarGroup, ToolbarSeparator, ToolbarLink, ToolbarInput); ToolbarButton reuses Button's buttonVariants and defaults to Fluent's subtle (ghost) look. The foundation primitive for an Office-style Ribbon.",
  "author": "graundtech <https://github.com/graundtech/fluent2-react-kit>",
  "dependencies": [
    "@base-ui/react",
    "class-variance-authority"
  ],
  "registryDependencies": [
    "https://fluent2-react-kit.graund.io/r/utils.json",
    "https://fluent2-react-kit.graund.io/r/button.json"
  ],
  "files": [
    {
      "path": "components/ui/toolbar.tsx",
      "type": "registry:ui",
      "target": "components/ui/toolbar.tsx",
      "content": "import { Toolbar as ToolbarPrimitive } from \"@base-ui/react/toolbar\";\nimport type { VariantProps } from \"class-variance-authority\";\nimport type { ComponentProps } from \"react\";\n\nimport { cn } from \"@/lib/utils\";\nimport { buttonVariants } from \"./button\";\n\n/**\n * Toolbar — Fluent 2-styled, Base-UI-modeled toolbar part family.\n *\n * Maps to Fluent 2's **Toolbar**: a transparent horizontal (or vertical) bar of\n * related controls — buttons, groups, separators, links, inline inputs. It is\n * the foundation primitive for an Office-style Ribbon, but stands alone as a\n * general-purpose component.\n *\n * ## Base UI mapping (conventions §9)\n * A toolbar's *behavior* — a single tab stop (roving tabindex), arrow-key\n * navigation among its items, composite focus, and wrap-around at the ends — is\n * real focus management that plain markup can't express, so the parts wrap\n * `@base-ui/react/toolbar` (namespace import as `ToolbarPrimitive`, matching\n * the actual `export * as Toolbar from \"./index.parts.js\"` shape in\n * node_modules — the same pattern `tabs.tsx`/`popover.tsx` use). Base UI owns\n * all of the keyboard behavior; this wrapper never hand-rolls a roving\n * tabindex.\n *\n * | Exported (kit name) | Base UI primitive     | Element |\n * | ------------------- | --------------------- | ------- |\n * | `Toolbar`           | `Toolbar.Root`        | `<div role=\"toolbar\">` |\n * | `ToolbarButton`     | `Toolbar.Button`      | `<button>` |\n * | `ToolbarGroup`      | `Toolbar.Group`       | `<div role=\"group\">` |\n * | `ToolbarSeparator`  | `Toolbar.Separator`   | `<div role=\"separator\">` |\n * | `ToolbarLink`       | `Toolbar.Link`        | `<a>` |\n * | `ToolbarInput`      | `Toolbar.Input`       | `<input>` |\n *\n * ## Divergences from shadcn\n * shadcn/ui has **no** Toolbar component, so there is no shadcn API to match —\n * the part-family surface here is modeled on Base UI's own parts (renamed with\n * the kit's `Toolbar*` PascalCase convention, §11) rather than on Radix. Two\n * things follow from that:\n * 1. **Composition uses Base UI's `render` prop, not Radix `asChild`.** Every\n *    part forwards Base UI's `render` prop (part of `BaseUIComponentProps`), the\n *    same idiom `popover.tsx`/`tooltip.tsx`/`dropdown-menu.tsx` document for\n *    their triggers. To turn a toolbar item into a different element/component —\n *    e.g. render `ToolbarButton` as the kit `Button`, or as a\n *    `DropdownMenuTrigger`/`PopoverTrigger` — pass\n *    `render={<Button variant=\"outline\">…</Button>}`. That is the direct\n *    analogue of shadcn's `<…Trigger asChild><Button/></…Trigger>`. Because\n *    `ToolbarButton` already carries the `buttonVariants` look (below), for pure\n *    styling prefer its own `variant`/`size` props; reach for `render` when you\n *    need to change the underlying element or delegate to another component.\n * 2. **`ToolbarButton` reuses `Button`'s `buttonVariants`.** Its default\n *    appearance is Fluent's *subtle* button (`variant=\"ghost\"` — transparent at\n *    rest, `bg-accent` hover/press via kit tokens), so a bare `<ToolbarButton>`\n *    reads as part of the same button system instead of a bespoke look. The full\n *    `variant`/`size` surface is exposed (same reuse `pagination.tsx` makes), so\n *    `<ToolbarButton variant=\"default\">` gives a brand-filled primary,\n *    `size=\"icon\"` gives a 32px square icon button, etc.\n *\n * ## Accessibility contract (APG Toolbar pattern)\n * - `Toolbar` (Root) renders `role=\"toolbar\"` and `aria-orientation` for free\n *   (Base UI). It is a composite widget with a **single tab stop**: `Tab` moves\n *   into the toolbar and lands on the first item (or the last-focused item);\n *   `ArrowRight`/`ArrowLeft` (and `ArrowUp`/`ArrowDown` when vertical) move focus\n *   between items and wrap around at the ends (`loopFocus`, on by default).\n *   Base UI's Toolbar deliberately does **not** enable `Home`/`End` (those keys\n *   keep their native behavior; the wrap-around covers jumping end-to-end).\n *   `toolbar.test.tsx` proves each of these.\n * - **A toolbar needs an accessible name — the consumer must supply it.** Like\n *   `PopoverContent` (whose `role=\"dialog\"` triggers axe's `aria-dialog-name`\n *   rule), a `role=\"toolbar\"` container has no implicit name, so pass either\n *   `aria-label` (e.g. `aria-label=\"Text formatting\"`) or `aria-labelledby`\n *   pointing at a visible heading. Every example in `toolbar.test.tsx` and the\n *   preview page does this.\n * - Disabled items stay **focusable and reachable** by arrow keys (Base UI's\n *   `focusableWhenDisabled` defaults to `true`, the APG-recommended behavior for\n *   discoverability) — see the `ToolbarButton` disabled note below.\n *\n * ## `\"use client\"` — intentionally omitted\n * Every Base UI Toolbar part module (`Root`, `Button`, `Group`, `Separator`,\n * `Link`, `Input`) carries its own `'use client'` directive at the source level\n * (verified in the compiled package output, same check `popover.tsx` documents).\n * This wrapper adds no hooks/handlers of its own and — unlike `select.tsx` /\n * `checkbox.tsx` — imports no `@fluentui/react-icons` (the `buttonVariants`\n * import is a pure `cva` helper). So none of the triggers in conventions §2/§9\n * that would force `\"use client\"` apply, and the file stays a plain,\n * server-renderable module (a Server Component tree can render these Client\n * Component children without re-declaring the directive). Add the directive here\n * only if that stops holding true.\n *\n * ## data-slot / data-orientation notes\n * Every part renders a real DOM element, so every part gets a `data-slot`:\n * `toolbar`, `toolbar-button`, `toolbar-group`, `toolbar-separator`,\n * `toolbar-link`, `toolbar-input`. Base UI already surfaces the toolbar's\n * orientation as `data-orientation=\"horizontal\" | \"vertical\"` on the root and\n * every item (and, on the separator, the *perpendicular* orientation), which\n * the layout classes below target with the bracketed `data-[orientation=…]:`\n * form (conventions §4).\n */\n\nfunction Toolbar({\n  className,\n  ...props\n}: ComponentProps<typeof ToolbarPrimitive.Root>) {\n  return (\n    <ToolbarPrimitive.Root\n      data-slot=\"toolbar\"\n      className={cn(\n        // Fluent 2: a transparent bar of 32px controls with small gaps. Sizes to\n        // its content (`w-fit`) so a floating toolbar doesn't stretch; override\n        // with `w-full` for an edge-to-edge formatting bar.\n        \"flex w-fit items-center gap-1\",\n        // vertical orientation — Base UI emits data-orientation; stack + stretch.\n        \"data-[orientation=vertical]:flex-col data-[orientation=vertical]:items-stretch\",\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\n/**\n * ToolbarButton — a toolbar item rendered as Fluent's *subtle* button by\n * default (`variant=\"ghost\"`), reusing `Button`'s `buttonVariants` so its\n * tokens/states stay identical to the kit `Button`. Exposes the full\n * `variant`/`size` surface; compose with another element/component via the\n * `render` prop (see divergence 1 in the file doc comment).\n *\n * Disabled handling: Base UI keeps a disabled toolbar item **focusable**\n * (`focusableWhenDisabled` defaults `true`), so it sets `aria-disabled` +\n * `data-disabled` and does **not** set the native `disabled` attribute — which\n * means `buttonVariants`' `disabled:` opacity would never match. The dimming is\n * re-expressed on `data-[disabled]:` here (the same fix `tabs.tsx` makes for\n * `Tabs.Tab`). Clicks and keyboard activation are still suppressed by Base UI.\n */\nfunction ToolbarButton({\n  className,\n  variant = \"ghost\",\n  size = \"default\",\n  ...props\n}: ComponentProps<typeof ToolbarPrimitive.Button> &\n  VariantProps<typeof buttonVariants>) {\n  return (\n    <ToolbarPrimitive.Button\n      data-slot=\"toolbar-button\"\n      data-variant={variant}\n      data-size={size}\n      className={cn(\n        buttonVariants({ variant, size }),\n        \"data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\n/**\n * ToolbarGroup — clusters related items (e.g. a bold/italic/underline trio)\n * with a tighter gap than the root. Renders `role=\"group\"` (Base UI); a\n * `disabled` prop disables every item inside it.\n */\nfunction ToolbarGroup({\n  className,\n  ...props\n}: ComponentProps<typeof ToolbarPrimitive.Group>) {\n  return (\n    <ToolbarPrimitive.Group\n      data-slot=\"toolbar-group\"\n      className={cn(\n        \"flex items-center gap-0.5\",\n        \"data-[orientation=vertical]:flex-col data-[orientation=vertical]:items-stretch\",\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\n/**\n * ToolbarSeparator — a 1px `--border` divider between item groups. Base UI's\n * `Toolbar.Separator` renders `role=\"separator\"` with the orientation\n * *perpendicular* to the toolbar, so a horizontal toolbar gets a vertical hair\n * line (inset within the 32px row, with horizontal margin) and a vertical\n * toolbar gets a horizontal one — targeted via `data-[orientation=…]:`.\n */\nfunction ToolbarSeparator({\n  className,\n  ...props\n}: ComponentProps<typeof ToolbarPrimitive.Separator>) {\n  return (\n    <ToolbarPrimitive.Separator\n      data-slot=\"toolbar-separator\"\n      className={cn(\n        \"shrink-0 bg-border\",\n        // vertical hairline (in a horizontal toolbar): 1px wide, inset height.\n        \"data-[orientation=vertical]:mx-1 data-[orientation=vertical]:h-5 data-[orientation=vertical]:w-px\",\n        // horizontal hairline (in a vertical toolbar): 1px tall, full width.\n        \"data-[orientation=horizontal]:my-1 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full\",\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\n/**\n * ToolbarLink — a toolbar item rendered as an `<a>` that participates in the\n * roving-tabindex sequence. Styled with Fluent's `BrandForegroundLink` ramp,\n * kept in sync with the kit `Link` component (rest `brand-70` → hover\n * `brand-60` → pressed `brand-50` in light; `brand-100`/`110`/`120` in dark) so\n * the two read identically without importing `Link`.\n */\nfunction ToolbarLink({\n  className,\n  ...props\n}: ComponentProps<typeof ToolbarPrimitive.Link>) {\n  return (\n    <ToolbarPrimitive.Link\n      data-slot=\"toolbar-link\"\n      className={cn(\n        \"inline-flex items-center gap-1 rounded-xs text-sm font-normal text-brand-70 underline-offset-4 dark:text-brand-100\",\n        \"outline-none transition-colors duration-fast ease-ease\",\n        \"hover:text-brand-60 hover:underline active:text-brand-50 dark:hover:text-brand-110 dark:active:text-brand-120\",\n        \"focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background\",\n        // anchors have no native `disabled`; style off aria-disabled like Link.\n        \"aria-disabled:pointer-events-none aria-disabled:opacity-50 aria-disabled:no-underline\",\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\n/**\n * ToolbarInput — a native `<input>` that stays inside the toolbar's keyboard\n * navigation (a font-size box, a search field, etc.). Styled to match the kit\n * `Input`: 32px Fluent field, the darker `--stroke-accessible` resting bottom\n * edge, and the signature no-reflow brand-underline focus accent (inset\n * box-shadow, conventions §4). Disabled is styled on `data-[disabled]:` because\n * — like `ToolbarButton` — Base UI keeps the input focusable-when-disabled and\n * sets `aria-disabled` rather than the native `disabled` attribute.\n */\nfunction ToolbarInput({\n  className,\n  ...props\n}: ComponentProps<typeof ToolbarPrimitive.Input>) {\n  return (\n    <ToolbarPrimitive.Input\n      data-slot=\"toolbar-input\"\n      className={cn(\n        \"flex h-8 min-w-0 rounded-md border border-input border-b-stroke-accessible bg-background px-2.5 text-sm\",\n        \"placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground\",\n        \"outline-none transition-[color,box-shadow] duration-fast ease-ease\",\n        \"data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n        // focus — Fluent bottom brand accent via inset box-shadow, no reflow.\n        \"focus-visible:border-primary focus-visible:shadow-[inset_0_-2px_0_0_var(--brand-80)] dark:focus-visible:shadow-[inset_0_-2px_0_0_var(--brand-100)]\",\n        // invalid — shadcn aria-invalid treatment (after focus so it wins the tie).\n        \"aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40\",\n        \"aria-invalid:focus-visible:shadow-[inset_0_-2px_0_0_var(--destructive)]\",\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nexport {\n  Toolbar,\n  ToolbarButton,\n  ToolbarGroup,\n  ToolbarSeparator,\n  ToolbarLink,\n  ToolbarInput,\n};\n"
    }
  ],
  "docs": "shadcn/ui has no Toolbar, so the API is modeled on Base UI's part family (renamed with the kit's Toolbar* convention). The root renders `role=\"toolbar\"`; consumers MUST provide an accessible name via `aria-label` or `aria-labelledby` (same requirement as PopoverContent). Base UI owns the keyboard model — one tab stop, ArrowRight/Left (ArrowUp/Down when vertical) with wrap-around at the ends, and focusable-when-disabled items reachable by arrows but not activatable (Base UI's Toolbar does not enable Home/End). Composition uses Base UI's `render` prop (e.g. `render={<Button variant=\"outline\">…</Button>}`), not Radix `asChild`. ToolbarButton reuses `buttonVariants` (default `variant=\"ghost\"`); set `size=\"icon\"` for 32px square icon buttons. See `toolbar.tsx`'s doc comment for the full mapping and a11y contract."
}
