{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"segmented-control","type":"registry:ui","title":"Segmented Control","description":"Mutually exclusive options with a highlight that slides to the selected one.","dependencies":["motion@^14.0.0"],"files":[{"path":"components/ui/segmented-control.tsx","type":"registry:ui","content":"\"use client\";\n\nimport { motion } from \"motion/react\";\nimport * as React from \"react\";\nimport { cn } from \"@/lib/utils\";\n\ntype SegmentedOption<T extends string> = {\n  value: T;\n  label: React.ReactNode;\n  ariaLabel?: string;\n};\n\nconst indicatorClassName = \"bg-background rounded-md shadow-xs dark:bg-zinc-700\";\n\nfunction SegmentedControl<T extends string>({\n  value,\n  options,\n  onChange,\n  size = \"sm\",\n  className,\n  ...props\n}: Omit<React.ComponentProps<\"div\">, \"onChange\"> & {\n  value: T;\n  options: SegmentedOption<T>[];\n  onChange: (value: T) => void;\n  size?: \"sm\" | \"default\";\n}) {\n  const buttons = React.useRef(new Map<T, HTMLButtonElement>());\n  const [indicator, setIndicator] = React.useState<{ x: number; width: number } | null>(null);\n  const optionKey = options.map((option) => option.value).join(\"|\");\n\n  React.useLayoutEffect(() => {\n    const active = buttons.current.get(value);\n    if (!active) {\n      setIndicator(null);\n      return;\n    }\n    const measure = () => setIndicator({ x: active.offsetLeft, width: active.offsetWidth });\n    measure();\n    const observer = new ResizeObserver(measure);\n    buttons.current.forEach((button) => observer.observe(button));\n    return () => observer.disconnect();\n  }, [value, optionKey]);\n\n  return (\n    <div\n      role=\"radiogroup\"\n      data-slot=\"segmented-control\"\n      data-size={size}\n      className={cn(\n        \"bg-muted relative inline-flex items-center rounded-lg p-0.5 data-[size=default]:h-9 data-[size=sm]:h-7\",\n        className,\n      )}\n      {...props}\n    >\n      {indicator && (\n        <motion.span\n          aria-hidden\n          initial={false}\n          animate={indicator}\n          transition={{ type: \"spring\", stiffness: 500, damping: 40 }}\n          className={cn(\"absolute inset-y-0.5 left-0\", indicatorClassName)}\n        />\n      )}\n      {options.map((option) => {\n        const active = option.value === value;\n        return (\n          <button\n            key={option.value}\n            ref={(button) => {\n              if (button) buttons.current.set(option.value, button);\n              else buttons.current.delete(option.value);\n            }}\n            type=\"button\"\n            role=\"radio\"\n            aria-checked={active}\n            aria-label={option.ariaLabel}\n            onClick={() => onChange(option.value)}\n            className={cn(\n              \"relative inline-flex h-full items-center justify-center gap-1.5 rounded-md px-2.5 text-xs font-medium whitespace-nowrap transition-colors outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-solid focus-visible:outline-foreground [&_svg]:size-4 [&_svg]:shrink-0\",\n              active ? \"text-foreground\" : \"text-muted-foreground hover:text-foreground\",\n              active && !indicator && indicatorClassName,\n            )}\n          >\n            <span className=\"relative inline-flex items-center gap-1.5\">{option.label}</span>\n          </button>\n        );\n      })}\n    </div>\n  );\n}\n\nexport { SegmentedControl, type SegmentedOption };\n"}]}