{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "core-console-switch",
  "type": "registry:ui",
  "title": "Console Switch",
  "description": "An on/off switch sized for a console row on Base UI's Switch (role switch, aria-checked, a hidden form input, Space and Enter toggle it): a control-height hit area around a 28 by 16px track, outlined and muted when off, filled with the accent when on, with no true/false text. focusableWhenDisabled keeps a refused switch in the tab order with aria-disabled. Colours from its own hoisted stylesheet, motion only under prefers-reduced-motion: no-preference.",
  "dependencies": [
    "@base-ui/react",
    "maruaudio@>=0.0.3 <0.1.0"
  ],
  "registryDependencies": [
    "https://pluginui.com/r/core-console-button.json"
  ],
  "files": [
    {
      "path": "components/ui/console-switch.tsx",
      "content": "/**\n * Source: https://maru.audio/ui-kit/console-switch\n * Copyright © [Maru](https://maru.audio)\n *\n * Maru UI Kit — https://maru.audio/ui-kit\n *\n * Installed from the Maru UI registry. This file is yours now: edit it\n * freely, it will not be overwritten unless you re-run the add command.\n *\n * Keep this item’s companion files and CSS imports together. Dependencies\n * declared by the registry are installed with the source.\n */\n/**\n * ConsoleSwitch — an on/off switch sized for a console row: a boolean\n * parameter, \"Follow host tempo\".\n *\n * Built on Base UI's Switch, which owns `role=\"switch\"`, `aria-checked`, the\n * hidden form input and the keyboard (Space and Enter toggle it). It shows no\n * \"true\" or \"false\" text: the thumb position and the fill say it. The hit\n * area is a full control-height row (`--uikit-control-h-md`, 28px unless the\n * face sets it, 44px on a touch face) around a track of about 28 by 16px.\n *\n * Off is an outlined track with a muted thumb on the left; on is an accent\n * track with the thumb on the right. Both outlines measure at least 3:1\n * against the surface with the default tokens (off 5.3:1, on 4:1), and the\n * thumb against its track at least 3:1.\n *\n * `focusableWhenDisabled` keeps a refused switch in the tab order with\n * `aria-disabled`, so a reason shown next to it can be reached; it does not\n * toggle. Name it with `aria-labelledby` (the row's label) or `aria-label`.\n */\n\nimport {\n  createElement,\n  type CSSProperties,\n  type JSX,\n  type ReactElement,\n  type Ref,\n} from \"react\"\nimport { Switch } from \"@base-ui/react/switch\"\nimport {\n  consoleControlHeight,\n  type ConsoleControlSize,\n} from \"@/components/ui/console-button\"\n\nexport interface ConsoleSwitchProps {\n  readonly checked: boolean\n  readonly onCheckedChange: (checked: boolean) => void\n  readonly disabled?: boolean\n  /** With `disabled`: stay focusable, with `aria-disabled`, and do not toggle. */\n  readonly focusableWhenDisabled?: boolean\n  /** Row height of the hit area. Defaults to \"md\". */\n  readonly size?: ConsoleControlSize\n  readonly \"aria-labelledby\"?: string\n  readonly \"aria-label\"?: string\n  readonly \"aria-describedby\"?: string\n  readonly title?: string\n  readonly id?: string\n  readonly name?: string\n  readonly className?: string\n  readonly style?: CSSProperties\n  readonly ref?: Ref<HTMLElement>\n}\n\n/** Track size in pixels. */\nexport const CONSOLE_SWITCH_TRACK = { width: 28, height: 16 } as const\nconst THUMB = 10\nconst BORDER = 1.5\nconst INSET = 2\n/** How far the thumb travels from off to on, in pixels. */\nconst TRAVEL = CONSOLE_SWITCH_TRACK.width - BORDER * 2 - INSET * 2 - THUMB\n\nconst MUTED = \"var(--uikit-text-muted, rgba(232, 234, 240, 0.55))\"\nconst ACCENT = \"var(--uikit-accent, #6366f1)\"\nconst ON_ACCENT = \"var(--uikit-accent-contrast, #ffffff)\"\nconst FOCUS = \"var(--uikit-focus, var(--uikit-accent, #6366f1))\"\n\n/** The `href` React dedupes the switch stylesheet by. */\nexport const CONSOLE_SWITCH_STYLES_HREF = \"uikit-console-switch\"\n\n/** State colours of the switch: off, on, hover, focus, disabled. */\nexport const CONSOLE_SWITCH_STYLES = String.raw`\n[data-console-switch-track]{border-color:${MUTED};background:transparent}\n[data-console-switch-thumb]{background:${MUTED}}\n[data-console-switch][data-checked] [data-console-switch-track]{border-color:${ACCENT};background:${ACCENT}}\n[data-console-switch][data-checked] [data-console-switch-thumb]{background:${ON_ACCENT};transform:translateX(${TRAVEL}px)}\n@media (hover: hover){\n[data-console-switch]:hover:not([data-disabled]):not([aria-disabled=\"true\"]) [data-console-switch-track]{box-shadow:0 0 0 3px color-mix(in srgb, var(--uikit-text, #e8eaf0) 9%, transparent)}\n}\n[data-console-switch]{outline:none}\n[data-console-switch]:focus-visible [data-console-switch-track]{outline:2px solid ${FOCUS};outline-offset:2px}\n[data-console-switch][data-disabled],[data-console-switch][aria-disabled=\"true\"]{opacity:.45;cursor:not-allowed}\n@media (prefers-reduced-motion: no-preference){\n[data-console-switch-thumb]{transition:transform 120ms ease-out,background-color 120ms ease-out}\n[data-console-switch-track]{transition:background-color 120ms ease-out,border-color 120ms ease-out,box-shadow 120ms ease-out}\n}\n`.replace(/\\n/g, \"\")\n\nfunction ConsoleSwitchStyles(): ReactElement {\n  return createElement(\n    \"style\",\n    { href: CONSOLE_SWITCH_STYLES_HREF, precedence: \"default\" },\n    CONSOLE_SWITCH_STYLES\n  )\n}\n\n/** A console on/off switch. */\nexport function ConsoleSwitch({\n  checked,\n  onCheckedChange,\n  disabled = false,\n  focusableWhenDisabled = false,\n  size = \"md\",\n  \"aria-labelledby\": ariaLabelledBy,\n  \"aria-label\": ariaLabel,\n  \"aria-describedby\": ariaDescribedBy,\n  title,\n  id,\n  name,\n  className,\n  style,\n  ref,\n}: ConsoleSwitchProps): JSX.Element {\n  // A focusable refusal: read-only for Base UI (no toggle, still in the tab\n  // order), announced as disabled.\n  const refusedButFocusable = disabled && focusableWhenDisabled\n  const height = consoleControlHeight(size)\n  return (\n    <Switch.Root\n      ref={ref}\n      checked={checked}\n      onCheckedChange={(next) => onCheckedChange(next)}\n      disabled={disabled && !focusableWhenDisabled}\n      readOnly={refusedButFocusable}\n      aria-disabled={refusedButFocusable ? true : undefined}\n      aria-labelledby={ariaLabelledBy}\n      aria-label={ariaLabel}\n      aria-describedby={ariaDescribedBy}\n      title={title}\n      id={id}\n      name={name}\n      className={className}\n      data-console-switch=\"\"\n      data-size={size}\n      style={{\n        boxSizing: \"border-box\",\n        display: \"inline-flex\",\n        alignItems: \"center\",\n        justifyContent: \"center\",\n        flex: \"none\",\n        height,\n        minWidth: `max(36px, ${height})`,\n        padding: \"0 4px\",\n        margin: 0,\n        cursor: \"pointer\",\n        WebkitTapHighlightColor: \"transparent\",\n        ...style,\n      }}\n    >\n      <ConsoleSwitchStyles />\n      <span\n        aria-hidden\n        data-console-switch-track=\"\"\n        style={{\n          boxSizing: \"border-box\",\n          position: \"relative\",\n          display: \"inline-flex\",\n          alignItems: \"center\",\n          width: CONSOLE_SWITCH_TRACK.width,\n          height: CONSOLE_SWITCH_TRACK.height,\n          padding: `0 ${INSET}px`,\n          borderWidth: BORDER,\n          borderStyle: \"solid\",\n          borderRadius: CONSOLE_SWITCH_TRACK.height / 2,\n        }}\n      >\n        <Switch.Thumb\n          data-console-switch-thumb=\"\"\n          style={{\n            display: \"block\",\n            width: THUMB,\n            height: THUMB,\n            borderRadius: \"50%\",\n          }}\n        />\n      </span>\n    </Switch.Root>\n  )\n}\n",
      "type": "registry:component",
      "target": "@ui/console-switch.tsx"
    }
  ]
}
