{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "toggle",
  "type": "registry:ui",
  "title": "Toggle",
  "description": "Fluent 2-styled toggle button with shadcn APIs. Two variants (default, outline) and three sizes matching Button's height scale, aria-pressed/data-pressed state via Base UI, and a Fluent double-stroke focus ring.",
  "author": "graundtech <https://github.com/graundtech/fluent2-react-kit>",
  "dependencies": [
    "class-variance-authority",
    "@base-ui/react"
  ],
  "registryDependencies": [
    "https://fluent2-react-kit.graund.io/r/utils.json"
  ],
  "files": [
    {
      "path": "components/ui/toggle.tsx",
      "type": "registry:ui",
      "target": "components/ui/toggle.tsx",
      "content": "import { Toggle as TogglePrimitive } from \"@base-ui/react/toggle\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport type { ComponentProps } from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\n/**\n * Toggle — Fluent 2-styled, shadcn-API two-state toggle button.\n *\n * Maps to Fluent 2's `ToggleButton`: a button that persists a pressed/checked\n * visual state instead of firing a one-shot action (Bold/Italic in a Ribbon\n * are the canonical example — this is built to be dropped into one, see\n * `apps/demo/app/preview/toggle/page.tsx`'s icon-only row). The prop surface\n * (`Toggle`, `toggleVariants`, `variant: \"default\" | \"outline\"`,\n * `size: \"sm\" | \"default\" | \"lg\"`) mirrors shadcn/ui's `toggle`, which is\n * itself a thin styling layer over Radix's `Toggle` primitive; here the same\n * shape wraps `@base-ui/react/toggle` instead (conventions §9 — the\n * pressed/unpressed state machine, `aria-pressed` wiring, and Space/Enter\n * activation are genuine behavior a styled `<button>` alone would have to\n * reimplement).\n *\n * ## API divergence from shadcn/Radix\n * Radix's `Toggle` takes `pressed`/`defaultPressed`/`onPressedChange` and\n * composes via `asChild`. Base UI's `Toggle` has the *same* controlled/\n * uncontrolled prop names (`pressed`, `defaultPressed`, `onPressedChange`),\n * so that part of the API needs no shim — but Base UI has no `asChild`.\n * Composition instead goes through the `render` prop it inherits from\n * `BaseUIComponentProps` (`render={<a href=\"...\" />}` or a\n * `(props, state) => ReactElement` render function) — Base UI's idiom for\n * \"replace the rendered element\" across every primitive in the package. This\n * wrapper does not add its own `asChild`/`Slot` shim on top; `render` is\n * forwarded straight through via `...props` since `Toggle.Props` already\n * includes it. One signature difference worth flagging: Base UI's\n * `onPressedChange` is `(pressed, eventDetails) => void` (an extra\n * `eventDetails` argument), not Radix's single-arg `(pressed) => void` —\n * same divergence already documented on `switch.tsx`'s `onCheckedChange`.\n *\n * `\"use client\"` is intentionally omitted — same reasoning as `switch.tsx`/\n * `radio-group.tsx`: `@base-ui/react/toggle`'s `Toggle` carries its own\n * `'use client'` directive at the compiled-source level (verified in\n * `node_modules/@base-ui/react/toggle/Toggle.js` and `.mjs`), so it is\n * already a client boundary on its own. This wrapper has no hooks/handlers\n * of its own — it only forwards props — so a Server Component tree can\n * render it unchanged.\n *\n * Base UI's `Toggle` renders a real native `<button>` (`nativeButton`\n * defaults `true`), sets `aria-pressed` itself from its internal state, and\n * stamps presence data-attributes `data-pressed` / `data-disabled`\n * (`ToggleDataAttributes`) — boolean presence, not a Radix-style\n * `data-state=\"on\" | \"off\"` pair, matched below via Tailwind's bracketed\n * `data-[pressed]:` form (conventions §4). Because it is a genuine native\n * button, `disabled` is a real DOM attribute and Space/Enter activation is\n * free — no reimplementation needed, and the disabled recipe below uses the\n * ordinary `disabled:` pseudo-class (not `data-[disabled]:`, unlike\n * `checkbox.tsx`/`switch.tsx` which render a non-native `<span>`).\n *\n * Height scale, padding, and text sizes are lifted verbatim from\n * `button.tsx`'s `sm`/`default`/`lg` (24/32/40px) so a `Toggle` sits flush\n * next to a `Button` in the same toolbar/Ribbon row. A 1px transparent\n * border ships on every rest state (both variants already have a border, so\n * this just keeps `default`'s box identical) so the border that appears on\n * `data-[pressed]:` doesn't shift layout by 1px when the state flips.\n *\n * Pressed-state color: Fluent's checked `ToggleButton` reads as a filled\n * neutral \"selected\" surface, not a full brand fill (that's reserved for\n * `appearance=\"primary\"` buttons) — so the pressed state reuses `Button`'s\n * own `secondary` variant tokens (`bg-secondary`/`text-secondary-foreground`,\n * hover steps to `bg-accent`, active/pressed-and-held steps to `bg-input`),\n * the same \"filled neutral surface + border\" vocabulary `button.tsx` already\n * uses for `secondary`. `outline`'s pressed border swaps to `border-primary`\n * (the brand-tinted checked-border token `checkbox.tsx` uses for its checked\n * box) so an outlined toggle's \"on\" state reads as brand-accented at a\n * glance, while `default`'s pressed border uses the neutral `border-border`\n * token instead (no outline at rest to accent against).\n */\nconst toggleVariants = cva(\n  [\n    // layout\n    \"inline-flex shrink-0 items-center justify-center gap-2 whitespace-nowrap\",\n    \"rounded-md border border-transparent font-semibold select-none cursor-pointer\",\n    // motion — token duration + Fluent easyEase curve; honors prefers-reduced-motion\n    // automatically (the duration tokens collapse to 0.01ms in tokens.css).\n    \"outline-none transition-colors duration-fast ease-ease\",\n    // disabled — opacity read (conventions §4); real <button disabled>, so the\n    // native pseudo-class applies directly (see file header).\n    \"disabled:pointer-events-none disabled:opacity-50\",\n    // focus — Fluent double-stroke approximation: 2px brand ring + 2px surface-colored gap\n    \"focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background\",\n    // invalid — shadcn aria-invalid treatment\n    \"aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40\",\n    // icon defaults\n    \"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n  ],\n  {\n    variants: {\n      variant: {\n        // Fluent subtle ToggleButton — transparent at rest, neutral hover\n        // (identical ramp to Button's ghost), filled-neutral \"secondary\"\n        // surface once pressed (see file header for the token mapping).\n        default: [\n          \"bg-transparent hover:bg-accent hover:text-accent-foreground active:bg-accent/80\",\n          \"dark:hover:bg-accent/50 dark:active:bg-accent/70\",\n          \"data-[pressed]:border-border data-[pressed]:bg-secondary data-[pressed]:text-secondary-foreground\",\n          \"data-[pressed]:hover:bg-accent data-[pressed]:active:bg-input dark:data-[pressed]:hover:bg-input dark:data-[pressed]:active:bg-input\",\n        ],\n        // Fluent outline ToggleButton — bordered + transparent at rest\n        // (identical ramp to Button's outline), same pressed fill as\n        // `default` but the border brand-tints to signal \"on\" against the\n        // already-visible outline.\n        outline: [\n          \"border-input bg-transparent hover:bg-accent hover:text-accent-foreground active:bg-accent/80\",\n          \"dark:bg-input/30 dark:hover:bg-input/50 dark:active:bg-input/70\",\n          // dark:bg-input/30 (rest) outranks the unscoped pressed fill in the\n          // cascade, so the pressed fill must be re-asserted under dark: too\n          // (real-browser finding from e2e/toggle.spec.ts).\n          \"data-[pressed]:border-primary data-[pressed]:bg-secondary data-[pressed]:text-secondary-foreground dark:data-[pressed]:bg-secondary\",\n          \"data-[pressed]:hover:bg-accent data-[pressed]:active:bg-input dark:data-[pressed]:hover:bg-input dark:data-[pressed]:active:bg-input\",\n        ],\n      },\n      size: {\n        // Fluent medium (default) — 32px height, matches button.tsx's default exactly.\n        default: \"h-8 px-3 text-sm has-[>svg]:px-2.5\",\n        // Fluent small — 24px height, matches button.tsx's sm exactly.\n        sm: \"h-6 gap-1.5 px-2 text-xs has-[>svg]:px-1.5\",\n        // Fluent large — 40px height, matches button.tsx's lg exactly.\n        lg: \"h-10 px-4 text-base has-[>svg]:px-3.5\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n      size: \"default\",\n    },\n  }\n);\n\nfunction Toggle({\n  className,\n  variant = \"default\",\n  size = \"default\",\n  ...props\n}: ComponentProps<typeof TogglePrimitive> &\n  VariantProps<typeof toggleVariants>) {\n  return (\n    <TogglePrimitive\n      data-slot=\"toggle\"\n      data-variant={variant}\n      data-size={size}\n      className={cn(toggleVariants({ variant, size, className }))}\n      {...props}\n    />\n  );\n}\n\nexport { Toggle, toggleVariants };\n"
    }
  ]
}
