{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"segmented-control","type":"registry:ui","title":"Segmented Control","description":"A row of options where one is picked, like a view switcher. The highlight slides to the choice.","dependencies":["radix-ui"],"registryDependencies":["https://dashral.com/r/utils.json","https://dashral.com/r/field.json"],"files":[{"path":"registry/dashral/segmented-control.tsx","type":"registry:file","target":"components/dashral/segmented-control.tsx","content":"\"use client\"\n\nimport * as React from \"react\"\nimport { RadioGroup as RadioGroupPrimitive } from \"radix-ui\"\n\nimport { cn } from \"./lib/utils\"\nimport { FieldWrapper, splitFieldProps, useFieldControl, type FieldClassNames, type FieldOwnProps } from \"./field\"\n\ntype SegmentedControlOption =\n  | string\n  | { value: string; label?: React.ReactNode; icon?: React.ReactNode; disabled?: boolean; \"aria-label\"?: string }\n\ntype SegmentedControlProps = Omit<React.ComponentProps<typeof RadioGroupPrimitive.Root>, \"children\" | \"orientation\"> &\n  FieldOwnProps & {\n    /** The segments: strings, or { value, label, icon, disabled }. An icon-only segment needs an aria-label. */\n    options: SegmentedControlOption[]\n    classNames?: FieldClassNames & { group?: string; item?: string }\n  }\n\n/**\n * A row of mutually exclusive options, like a view switcher (Day / Week / Month). Behaves as a radio group: Tab moves\n * into it, the arrow keys move and select. The highlight slides to the picked option (it just appears with reduced\n * motion). For switching between panels of content, use Tabs.\n */\nfunction SegmentedControl(props: SegmentedControlProps) {\n  const [field, control] = splitFieldProps(props)\n  return (\n    <FieldWrapper {...field} group>\n      <SegmentedControlGroup {...control} />\n    </FieldWrapper>\n  )\n}\n\nfunction SegmentedControlGroup({ options, className, classNames, ...rest }: Omit<SegmentedControlProps, keyof FieldOwnProps>) {\n  const props = useFieldControl(rest)\n  const ref = React.useRef<HTMLDivElement>(null)\n  const [thumb, setThumb] = React.useState<{ left: number; width: number } | null>(null)\n\n  // Measure the checked item so the thumb can slide to it; again whenever the control resizes.\n  React.useLayoutEffect(() => {\n    const root = ref.current\n    if (!root) return\n    const measure = () => {\n      const item = root.querySelector<HTMLElement>(\"[data-slot=segmented-control-item][data-state=checked]\")\n      setThumb(item ? { left: item.offsetLeft, width: item.offsetWidth } : null)\n    }\n    measure()\n    const observer = new ResizeObserver(measure)\n    observer.observe(root)\n    const mutations = new MutationObserver(measure)\n    mutations.observe(root, { attributes: true, subtree: true, attributeFilter: [\"data-state\"] })\n    return () => {\n      observer.disconnect()\n      mutations.disconnect()\n    }\n  }, [])\n\n  return (\n    <RadioGroupPrimitive.Root\n      ref={ref}\n      data-slot=\"segmented-control\"\n      data-measured={thumb ? \"\" : undefined}\n      orientation=\"horizontal\"\n      className={cn(\"relative inline-flex h-9 w-fit items-center rounded-lg bg-muted p-1 text-muted-foreground\", className, classNames?.group)}\n      {...props}\n    >\n      {thumb && (\n        <span\n          aria-hidden\n          className=\"absolute inset-y-1 left-0 rounded-md bg-background shadow-sm transition-[translate,width] duration-(--duration-considered) ease-standard motion-reduce:transition-none dark:bg-input/60\"\n          style={{ translate: `${thumb.left}px 0`, width: thumb.width }}\n        />\n      )}\n      {options.map((option) => {\n        const { value, label, icon, disabled, \"aria-label\": ariaLabel } =\n          typeof option === \"string\" ? { value: option, label: option } : option\n        return (\n          <SegmentedControlItem key={value} value={value} disabled={disabled} aria-label={ariaLabel} className={classNames?.item}>\n            {icon}\n            {label}\n          </SegmentedControlItem>\n        )\n      })}\n    </RadioGroupPrimitive.Root>\n  )\n}\n\nfunction SegmentedControlItem({ className, children, ...props }: React.ComponentProps<typeof RadioGroupPrimitive.Item>) {\n  return (\n    <RadioGroupPrimitive.Item\n      data-slot=\"segmented-control-item\"\n      className={cn(\n        \"relative z-10 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        // Until the sliding thumb is measured (server render, no JavaScript yet), the checked item draws its own.\n        \"not-in-data-measured:data-[state=checked]:bg-background not-in-data-measured:data-[state=checked]:shadow-sm dark:not-in-data-measured:data-[state=checked]:bg-input/60\",\n        \"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      {...props}\n    >\n      {children}\n    </RadioGroupPrimitive.Item>\n  )\n}\n\nexport { SegmentedControl, type SegmentedControlProps, type SegmentedControlOption }\n"}],"cssVars":{"theme":{"ease-standard":"var(--ease-standard)","ease-enter":"var(--ease-enter)","ease-exit":"var(--ease-exit)","ease-spring":"var(--ease-spring)"},"light":{"duration-fast":"160ms","duration-standard":"240ms","duration-considered":"480ms","ease-standard":"cubic-bezier(0.2, 0, 0, 1)","ease-enter":"cubic-bezier(0, 0, 0, 1)","ease-exit":"cubic-bezier(0.3, 0, 1, 1)","ease-spring":"linear(0, 0.071 4%, 0.234 8%, 0.433 12%, 0.626 17%, 0.791 21%, 0.917 25%, 1.004 29%, 1.055 33%, 1.079 38%, 1.083 42%, 1.075 46%, 1.06 50%, 1.043 54%, 1.027 58%, 1.015 62%, 1.005 67%, 0.998 71%, 0.995 75%, 0.993 79%, 0.993 83%, 0.994 88%, 0.995 92%, 0.997 96%, 1)"},"dark":{}}}