{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "toggle-group",
  "type": "registry:ui",
  "title": "ToggleGroup",
  "description": "Segmented tile control for one-of-n choices, such as a light / dark / system theme picker.",
  "categories": [
    "forms"
  ],
  "registryDependencies": [
    "https://whiskeyjack.net/r/merge-refs.json",
    "https://whiskeyjack.net/r/option-tile.json",
    "https://whiskeyjack.net/r/utils.json"
  ],
  "files": [
    {
      "path": "components/ui/toggle-group.tsx",
      "type": "registry:ui",
      "target": "components/ui/toggle-group.tsx",
      "content": "import * as React from 'react'\nimport { cn } from '@/lib/utils'\nimport { optionTileClass } from '@/lib/option-tile'\nimport { useMergedRefs } from '@/lib/merge-refs'\n\nexport interface ToggleGroupOption<T extends string = string> {\n  value: T\n  label: string\n  /**\n   * Optional icon element rendered above the label. The DS avoids bundling an\n   * icon library so the caller passes the icon node directly\n   * (e.g. `<Sun size={20} weight=\"regular\" />`).\n   */\n  icon?: React.ReactNode\n}\n\nexport interface ToggleGroupProps<T extends string = string> {\n  options: ToggleGroupOption<T>[]\n  value: T\n  onChange: (value: T) => void\n  className?: string\n  /**\n   * Tiles per row. Defaults to one column per option, so every option sits on\n   * a single row; pass a smaller number to wrap onto further rows.\n   */\n  columns?: number\n  /** Inline styles for the root element. */\n  style?: React.CSSProperties\n}\n\n/**\n * Segmented control (toggle group). Presentational only -- no theme or\n * sort logic inside. Pass translated option labels; the DS is i18n-free.\n *\n * Active option renders with the accent color. A check mark is shown in the\n * top-right corner of the selected tile. Supports an optional icon per option\n * (pass icon ReactNodes; the DS does not bundle an icon library).\n *\n * Layout: a `grid` whose column count is an inline `gridTemplateColumns`, one\n * column per option by default. An inline style outranks any class, so a\n * `grid-cols-*` in `className` has no effect; wrap with the `columns` prop.\n *\n * Usage:\n *\n * ```tsx\n * const themeOptions = [\n *   { value: 'light', label: t('settings.light'), icon: <Sun size={20} weight=\"regular\" /> },\n *   { value: 'dark',  label: t('settings.dark'),  icon: <Moon size={20} weight=\"regular\" /> },\n *   { value: 'system', label: t('settings.system'), icon: <Desktop size={20} weight=\"regular\" /> },\n * ]\n *\n * <ToggleGroup options={themeOptions} value={theme} onChange={setTheme} />\n * ```\n */\nfunction ToggleGroupInner<T extends string = string>(\n  { options, value, onChange, className, columns, style }: ToggleGroupProps<T>,\n  ref: React.ForwardedRef<HTMLDivElement>,\n) {\n  const containerRef = React.useRef<HTMLDivElement>(null)\n  const mergedRef = useMergedRefs(containerRef, ref)\n\n  const handleKeyDown = React.useCallback(\n    (e: React.KeyboardEvent<HTMLDivElement>) => {\n      if (!options.length) return\n      const currentIndex = options.findIndex((o) => o.value === value)\n\n      let nextIndex: number | null = null\n      if (e.key === 'ArrowRight' || e.key === 'ArrowDown') {\n        nextIndex = currentIndex < options.length - 1 ? currentIndex + 1 : 0\n      } else if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') {\n        nextIndex = currentIndex > 0 ? currentIndex - 1 : options.length - 1\n      }\n\n      if (nextIndex !== null) {\n        e.preventDefault()\n        const next = options[nextIndex]\n        onChange(next.value as T)\n        const btn = containerRef.current?.querySelectorAll<HTMLElement>('[role=\"radio\"]')[nextIndex]\n        btn?.focus()\n      }\n    },\n    [options, value, onChange]\n  )\n\n  return (\n    <div\n      ref={mergedRef}\n      role=\"radiogroup\"\n      onKeyDown={handleKeyDown}\n      className={cn('grid gap-3', className)}\n      style={{ gridTemplateColumns: `repeat(${columns ?? options.length}, minmax(0, 1fr))`, ...style }}\n    >\n      {options.map((option) => {\n        const isSelected = value === option.value\n        return (\n          <button\n            key={option.value}\n            role=\"radio\"\n            aria-checked={isSelected}\n            tabIndex={isSelected ? 0 : -1}\n            type=\"button\"\n            onClick={() => onChange(option.value as T)}\n            // The tile recipe is shared with CheckboxGroup (see optionTileClass),\n            // so a pick-one and a pick-any control cannot drift apart visually.\n            className={optionTileClass({ selected: isSelected, stacked: !!option.icon })}\n          >\n            {isSelected && (\n              <span className=\"absolute top-1.5 end-1.5\">\n                {/* Inline checkmark -- avoids icon library dependency */}\n                <svg\n                  viewBox=\"0 0 12 12\"\n                  fill=\"none\"\n                  stroke=\"currentColor\"\n                  strokeWidth=\"2.5\"\n                  strokeLinecap=\"round\"\n                  strokeLinejoin=\"round\"\n                  aria-hidden\n                  className=\"w-3 h-3\"\n                >\n                  <path d=\"M2 6l3 3 5-5\" />\n                </svg>\n              </span>\n            )}\n            {option.icon && (\n              <span aria-hidden>\n                {option.icon}\n              </span>\n            )}\n            <span>{option.label}</span>\n          </button>\n        )\n      })}\n    </div>\n  )\n}\n\n// forwardRef erases the generic type parameter, so re-assert it on the exported\n// component: ToggleGroup keeps `value`/`onChange` typed to the option union AND\n// forwards a ref to the radiogroup element.\nconst ToggleGroupWithRef = React.forwardRef(ToggleGroupInner)\nToggleGroupWithRef.displayName = 'ToggleGroup'\nexport const ToggleGroup = ToggleGroupWithRef as <T extends string = string>(\n  props: ToggleGroupProps<T> & { ref?: React.Ref<HTMLDivElement> },\n) => React.ReactElement\n"
    }
  ],
  "docs": "Presentational and generic over the value type. Pass translated labels and Phosphor icons. For more options than a few tiles can hold, use Select.",
  "meta": {
    "group": "forms",
    "related": [
      "toggle",
      "select",
      "use-theme"
    ],
    "exports": [
      "ToggleGroup"
    ],
    "siteSlug": "toggle-group"
  }
}
