{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"motion-segmented-control","type":"registry:component","title":"Segmented Control (Motion)","description":"SegmentedControl with motion: the picked option's card springs to the one you pick, a soft highlight follows the pointer, and options press in while held.","dependencies":["motion","radix-ui"],"registryDependencies":["https://dashral.com/r/utils.json","https://dashral.com/r/motion-field.json","https://dashral.com/r/motion-hover.json","https://dashral.com/r/motion-reduced.json","https://dashral.com/r/motion-tokens.json","https://dashral.com/r/field.json","https://dashral.com/r/segmented-control.json"],"files":[{"path":"registry/dashral/motion/segmented-control.tsx","type":"registry:file","target":"components/dashral/motion/segmented-control.tsx","content":"\"use client\"\n\nimport * as React from \"react\"\nimport { RadioGroup as RadioGroupPrimitive } from \"radix-ui\"\nimport { LayoutGroup, motion } from \"motion/react\"\nimport { FieldWrapper, splitFieldProps, useFieldControl, type FieldOwnProps } from \"../field\"\nimport type { SegmentedControlProps } from \"../segmented-control\"\nimport { cn } from \"../lib/utils\"\n\nimport { useHoverPill } from \"../lib/motion-hover\"\nimport { motionMessages } from \"../lib/motion-field\"\nimport { useReducedMotion } from \"../lib/motion-reduced\"\nimport { spring } from \"../lib/motion-tokens\"\n\nexport type { SegmentedControlProps, SegmentedControlOption } from \"../segmented-control\"\n\n/**\n * The kit's SegmentedControl with motion: the same options, label and props. The picked option's card springs to the\n * one you pick, with a touch of overshoot to settle, and a soft highlight glides after the pointer from option to\n * option; an option presses in while held. With reduced motion the card moves at once.\n */\nexport function SegmentedControl(props: SegmentedControlProps) {\n  const [field, control] = splitFieldProps(props)\n  return (\n    <FieldWrapper {...field} group renderMessages={motionMessages}>\n      <SegmentedControlGroup {...control} />\n    </FieldWrapper>\n  )\n}\n\nfunction SegmentedControlGroup({ options, value, defaultValue, onValueChange, className, classNames, ...rest }: Omit<SegmentedControlProps, keyof FieldOwnProps>) {\n  const props = useFieldControl(rest)\n  const reduced = useReducedMotion()\n  const id = React.useId()\n  const first = options[0]\n  const [own, setOwn] = React.useState(defaultValue ?? (typeof first === \"string\" ? first : first?.value) ?? \"\")\n  const picked = value ?? own\n  const hover = useHoverPill<string>(reduced)\n  const move = reduced ? { duration: 0 } : spring.move\n\n  return (\n    <LayoutGroup id={id}>\n      <RadioGroupPrimitive.Root\n        data-slot=\"segmented-control\"\n        orientation=\"horizontal\"\n        value={picked}\n        onValueChange={(next) => {\n          if (value === undefined) setOwn(next)\n          onValueChange?.(next)\n        }}\n        onPointerLeave={hover.leave}\n        className={cn(\"relative isolate inline-flex h-9 w-fit items-center rounded-lg bg-muted p-1 text-muted-foreground\", className, classNames?.group)}\n        {...props}\n      >\n        {options.map((option) => {\n          const { value: itemValue, label, icon, disabled, \"aria-label\": ariaLabel } =\n            typeof option === \"string\" ? { value: option, label: option } : option\n          return (\n            <RadioGroupPrimitive.Item\n              key={itemValue}\n              value={itemValue}\n              disabled={disabled}\n              aria-label={ariaLabel}\n              data-slot=\"segmented-control-item\"\n              onPointerEnter={() => hover.enter(itemValue)}\n              asChild\n            >\n              <motion.button\n                type=\"button\"\n                whileTap={reduced || disabled ? undefined : { scale: 0.96 }}\n                transition={spring.press}\n                className={cn(\n                  \"relative inline-flex h-full flex-1 items-center justify-center gap-1.5 rounded-md px-3 text-sm font-medium whitespace-nowrap transition-colors outline-none\",\n                  \"hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 data-[state=checked]:text-foreground\",\n                  \"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n                  classNames?.item\n                )}\n              >\n                {/* The picked option's card: one element that springs from option to option (motion's shared layout). */}\n                {picked === itemValue && (\n                  <motion.span\n                    layoutId=\"picked\"\n                    aria-hidden\n                    transition={move}\n                    className=\"absolute inset-0 -z-10 rounded-md bg-background shadow-sm dark:bg-input/60\"\n                  />\n                )}\n                {/* A softer one under the pointer, gliding the same way from option to option. */}\n                {hover.hot === itemValue && (\n                  <motion.span\n                    layoutId=\"hovered\"\n                    aria-hidden\n                    // Its opacity is the control's, so it doesn't start from nothing at each new option.\n                    style={{ opacity: hover.opacity }}\n                    transition={move}\n                    className=\"absolute inset-0 -z-20 rounded-md bg-foreground/5\"\n                  />\n                )}\n                {icon}\n                {label}\n              </motion.button>\n            </RadioGroupPrimitive.Item>\n          )\n        })}\n      </RadioGroupPrimitive.Root>\n    </LayoutGroup>\n  )\n}\n"}]}