{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "switch",
  "type": "registry:ui",
  "title": "Switch",
  "description": "Fluent 2-styled on/off toggle with shadcn APIs. Built on @base-ui/react's Switch primitive for real form participation, keyboard behavior, and label association; renders Fluent's outlined/transparent unchecked track and brand-filled checked track with a sliding thumb.",
  "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/switch.tsx",
      "type": "registry:ui",
      "target": "components/ui/switch.tsx",
      "content": "import { Switch as SwitchPrimitive } from \"@base-ui/react/switch\";\nimport type { ComponentProps } from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\n/**\n * Switch — Fluent 2-styled, shadcn-API on/off toggle.\n *\n * This is a Base UI case (conventions §9): checked/unchecked state, the\n * hidden `<input type=\"checkbox\">` + form bridge, and the `role=\"switch\"` +\n * `aria-checked` wiring are genuine interactive behavior plain markup can't\n * express, so this wraps `@base-ui/react/switch`'s `Switch.Root`/\n * `Switch.Thumb` (imported as the `Switch` namespace from the\n * `@base-ui/react/switch` subpath — that module is literally\n * `export * as Switch from \"./index.parts.js\"`, matching Avatar's precedent)\n * instead of a raw `<button>`/`<span>` pair. `Switch.Root` renders a `<span\n * role=\"switch\">` (not a native `<button>`/`<input>`) plus a visually-hidden\n * `<input type=\"checkbox\">` for form submission.\n *\n * `\"use client\"` is intentionally omitted, same reasoning as `avatar.tsx`:\n * `SwitchRoot`/`SwitchThumb` each carry their own `'use client'` directive at\n * the source level (verified in the compiled package output under\n * `@base-ui/react/switch/root/SwitchRoot.mjs` and `.../thumb/SwitchThumb.mjs`),\n * so they are already client boundaries on their own. This wrapper has no\n * hooks/handlers of its own — it only forwards props into those parts — so a\n * Server Component tree can render it unchanged (a Server Component may\n * render an already-client child without re-declaring the directive itself).\n * Confirmed via `pnpm typecheck` + the test suite; add the directive here\n * only if that stops holding true.\n *\n * Aesthetics follow Fluent 2's Toggle Switch (verified against the live\n * Storybook at storybooks.fluentui.dev, DOM-inspected directly since the\n * docs page itself carries no static spec text): a 40x20px (`h-5 w-10`) pill\n * track with a circular thumb — Fluent's own thumb measures ~18px against an\n * 18px content box (nearly flush); this kit intentionally uses the smaller\n * 14px (`size-3.5`) thumb specified for this component, leaving a visible\n * ~2px gap, which reads cleanly at this track size. Fluent's *unchecked*\n * track is outlined + transparent (measured: 1px solid `#616161`, background\n * transparent) — NOT gray-filled like shadcn's default Radix implementation\n * (`data-[state=unchecked]:bg-input`). This kit now binds that outline to the\n * `NeutralStrokeAccessible` ramp (`border-stroke-accessible` = `#616161` rest,\n * `#575757` hover, `#4d4d4d` pressed): spec-true to the measured Fluent value,\n * so this is no longer the earlier `border-input` (`#d1d1d1`) substitution —\n * that documented deviation is retired. The *checked* track measured exactly\n * `#0f6cbd` — this kit's `--primary`/`bg-primary` token — with a white thumb\n * (`#ffffff`, this kit's `--primary-foreground` token in both themes); on\n * hover/press the on-track steps through Fluent's `CompoundBrandBackground`\n * (border+fill `brand-70`/`brand-60`; dark hover `brand-80`), mirroring\n * Button's per-theme brand ramp (conventions §4).\n *\n * The prop surface (`Switch`, `checked`/`defaultChecked`/`onCheckedChange`,\n * no `size` prop) follows the classic shadcn/ui switch API. Note Base UI's\n * state data-attributes differ from Radix's: it emits presence-only\n * `data-checked`/`data-unchecked` (and `data-disabled`/`data-readonly`/\n * `data-required`), not Radix's `data-state=\"checked\" | \"unchecked\"` — the\n * classes below target Base UI's actual output via Tailwind's presence-based\n * `data-[checked]:` variant.\n */\nfunction Switch({\n  className,\n  ...props\n}: ComponentProps<typeof SwitchPrimitive.Root>) {\n  return (\n    <SwitchPrimitive.Root\n      data-slot=\"switch\"\n      className={cn(\n        // layout — Fluent medium toggle: 40x20 pill track, thumb centered via flex\n        \"inline-flex h-5 w-10 shrink-0 cursor-pointer items-center rounded-full border px-0.5\",\n        // motion — border/bg color transition on check/uncheck\n        \"outline-none transition-colors duration-fast ease-ease\",\n        // off (unchecked) — Fluent's outlined/transparent track, bound to the\n        // NeutralStrokeAccessible ramp (spec-true measured #616161 rest ->\n        // #575757 hover -> #4d4d4d pressed), see file header.\n        \"border-stroke-accessible bg-transparent hover:border-stroke-accessible-hover active:border-stroke-accessible-pressed\",\n        // on (checked) — brand fill (Fluent CompoundBrandBackground), stepping\n        // border+fill through Hover/Pressed; dark hover brightens to brand-80.\n        // The data-[checked]:hover/active selectors outrank the neutral\n        // hover/active border by specificity so the on-track stays brand.\n        \"data-[checked]:border-primary data-[checked]:bg-primary data-[checked]:hover:border-brand-70 data-[checked]:hover:bg-brand-70 data-[checked]:active:border-brand-60 data-[checked]:active:bg-brand-60 dark:data-[checked]:hover:border-brand-80 dark:data-[checked]:hover:bg-brand-80\",\n        // focus — Fluent double-stroke approximation (conventions §4)\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 (conventions §4); harmless\n        // unless the caller pairs this with a Field/validation integration\n        \"aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40\",\n        // disabled — opacity read (conventions §4), keyed off Base UI's\n        // `data-disabled` rather than the CSS `disabled:` pseudo-class: this\n        // control renders a <span role=\"switch\">, not a native\n        // <button>/<input>, so `:disabled` never matches it.\n        \"data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n        className\n      )}\n      {...props}\n    >\n      <SwitchPrimitive.Thumb\n        data-slot=\"switch-thumb\"\n        className={cn(\n          \"pointer-events-none block size-3.5 rounded-full\",\n          \"transition-transform duration-fast ease-ease\",\n          // off — neutral thumb, matches Fluent's measured `#616161`\n          \"bg-muted-foreground\",\n          // on — white thumb via the `primary-foreground` token (`#ffffff`\n          // in both themes) rather than a literal `bg-white`, per\n          // conventions §3.7 (never hardcode a color). Travel distance is\n          // the track's content-box width (40 - 2×1px border - 2×2px\n          // padding = 34px) minus the 14px thumb = 20px = `translate-x-5`.\n          \"data-[checked]:translate-x-5 data-[checked]:bg-primary-foreground\"\n        )}\n      />\n    </SwitchPrimitive.Root>\n  );\n}\n\nexport { Switch };\n"
    }
  ]
}
