{ "$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 {\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. ``).\n */\n icon?: React.ReactNode\n}\n\nexport interface ToggleGroupProps {\n options: ToggleGroupOption[]\n value: T\n onChange: (value: T) => void\n className?: string\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: `grid` with `grid-cols-{n}` auto-sized by count (handled via\n * inline style). Callers may override with `className`.\n *\n * Usage:\n *\n * ```tsx\n * const themeOptions = [\n * { value: 'light', label: t('settings.light'), icon: },\n * { value: 'dark', label: t('settings.dark'), icon: },\n * { value: 'system', label: t('settings.system'), icon: },\n * ]\n *\n * \n * ```\n */\nfunction ToggleGroupInner(\n { options, value, onChange, className }: ToggleGroupProps,\n ref: React.ForwardedRef,\n) {\n const containerRef = React.useRef(null)\n const mergedRef = useMergedRefs(containerRef, ref)\n\n const handleKeyDown = React.useCallback(\n (e: React.KeyboardEvent) => {\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('[role=\"radio\"]')[nextIndex]\n btn?.focus()\n }\n },\n [options, value, onChange]\n )\n\n return (\n \n {options.map((option) => {\n const isSelected = value === option.value\n return (\n 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 \n {/* Inline checkmark -- avoids icon library dependency */}\n \n \n \n \n )}\n {option.icon && (\n \n {option.icon}\n \n )}\n {option.label}\n \n )\n })}\n \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 (\n props: ToggleGroupProps & { ref?: React.Ref },\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" } }