{
  "name": "slider",
  "type": "registry:ui",
  "title": "Slider",
  "description": "Single-value and range selection across a continuous or stepped visual scale.",
  "registryDependencies": [
    "@siteplane/base"
  ],
  "dependencies": [
    "@base-ui/react@1.5.0",
    "class-variance-authority@0.7.1"
  ],
  "files": [
    {
      "path": "src/components/ui/slider.tsx",
      "type": "registry:ui",
      "content": "\"use client\";\n\nimport { Slider as SliderPrimitive } from \"@base-ui/react/slider\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport * as React from \"react\";\nimport {\n  type SiteplaneControlSize,\n  useResolvedControlSize,\n} from \"@/components/ui/siteplane-provider\";\nimport { cn } from \"@/lib/utils\";\n\nconst sliderControlVariants = cva(\n  \"relative flex cursor-pointer touch-none select-none active:cursor-grabbing before:absolute before:content-[''] data-disabled:cursor-not-allowed data-[orientation=horizontal]:w-full data-[orientation=horizontal]:items-center data-[orientation=horizontal]:before:inset-x-0 data-[orientation=vertical]:h-full data-[orientation=vertical]:items-center data-[orientation=vertical]:flex-col data-[orientation=vertical]:before:inset-y-0 data-disabled:opacity-64\",\n  {\n    defaultVariants: {\n      size: \"default\",\n    },\n    variants: {\n      size: {\n        default:\n          \"data-[orientation=horizontal]:min-h-6 data-[orientation=horizontal]:min-w-44 data-[orientation=horizontal]:before:inset-y-[-10px] data-[orientation=vertical]:min-h-44 data-[orientation=vertical]:min-w-6 data-[orientation=vertical]:before:inset-x-[-10px]\",\n        lg: \"data-[orientation=horizontal]:min-h-7 data-[orientation=horizontal]:min-w-48 data-[orientation=horizontal]:before:inset-y-[-12px] data-[orientation=vertical]:min-h-48 data-[orientation=vertical]:min-w-7 data-[orientation=vertical]:before:inset-x-[-12px]\",\n        sm: \"data-[orientation=horizontal]:min-h-5 data-[orientation=horizontal]:min-w-40 data-[orientation=horizontal]:before:inset-y-[-8px] data-[orientation=vertical]:min-h-40 data-[orientation=vertical]:min-w-5 data-[orientation=vertical]:before:inset-x-[-8px]\",\n        xl: \"data-[orientation=horizontal]:min-h-8 data-[orientation=horizontal]:min-w-52 data-[orientation=horizontal]:before:inset-y-[-14px] data-[orientation=vertical]:min-h-52 data-[orientation=vertical]:min-w-8 data-[orientation=vertical]:before:inset-x-[-14px]\",\n      },\n    },\n  },\n);\n\nconst sliderTrackVariants = cva(\n  \"relative grow cursor-inherit select-none active:cursor-grabbing before:absolute before:rounded-sm before:bg-input data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=horizontal]:before:inset-x-0.5 data-[orientation=vertical]:before:inset-x-0 data-[orientation=horizontal]:before:inset-y-0 data-[orientation=vertical]:before:inset-y-0.5\",\n  {\n    defaultVariants: {\n      size: \"default\",\n    },\n    variants: {\n      size: {\n        default: \"data-[orientation=horizontal]:h-1 data-[orientation=vertical]:w-1\",\n        lg: \"data-[orientation=horizontal]:h-1.5 data-[orientation=vertical]:w-1.5\",\n        sm: \"data-[orientation=horizontal]:h-1 data-[orientation=vertical]:w-1\",\n        xl: \"data-[orientation=horizontal]:h-2 data-[orientation=vertical]:w-2\",\n      },\n    },\n  },\n);\n\nconst sliderThumbVariants = cva(\n  \"block shrink-0 cursor-inherit select-none rounded-md border border-input bg-white not-dark:bg-clip-padding shadow-xs/5 outline-none transition-[box-shadow,scale] [transition-duration:var(--motion-duration-slow)] [transition-timing-function:var(--motion-ease-apple-out)] active:cursor-grabbing before:absolute before:inset-0 before:rounded-[inherit] before:shadow-[0_1px_--theme(--color-black/4%)] has-focus-visible:ring-[3px] has-focus-visible:ring-ring/24 data-dragging:scale-120 data-dragging:cursor-grabbing motion-reduce:transition-none dark:border-background dark:has-focus-visible:ring-ring/48 [:has(*:focus-visible),[data-dragging]]:shadow-none\",\n  {\n    defaultVariants: {\n      size: \"default\",\n    },\n    variants: {\n      size: {\n        default: \"size-5 sm:size-4\",\n        lg: \"size-6 sm:size-5\",\n        sm: \"size-4.5 sm:size-4\",\n        xl: \"size-7 sm:size-6\",\n      },\n    },\n  },\n);\n\nconst sliderValueVariants = cva(\"flex justify-end\", {\n  defaultVariants: {\n    size: \"default\",\n  },\n  variants: {\n    size: {\n      default: \"text-sm\",\n      lg: \"text-base sm:text-sm\",\n      sm: \"text-xs\",\n      xl: \"text-base\",\n    },\n  },\n});\n\nconst SliderSizeContext = React.createContext<SiteplaneControlSize | null>(null);\n\nexport interface SliderProps\n  extends Omit<SliderPrimitive.Root.Props, \"size\">,\n    VariantProps<typeof sliderControlVariants> {}\n\nexport function Slider({\n  className,\n  children,\n  defaultValue,\n  value,\n  min = 0,\n  max = 100,\n  onPointerCancel,\n  onPointerDown,\n  onPointerMove,\n  onPointerUp,\n  size,\n  ...props\n}: SliderProps): React.ReactElement {\n  const [isPointerDragging, setIsPointerDragging] = React.useState(false);\n  const [isPointerDown, setIsPointerDown] = React.useState(false);\n  const pointerStartRef = React.useRef<{ x: number; y: number } | null>(null);\n  const resolvedSize = useResolvedControlSize(size);\n\n  const _values = React.useMemo(() => {\n    if (value !== undefined) {\n      return Array.isArray(value) ? value : [value];\n    }\n    if (defaultValue !== undefined) {\n      return Array.isArray(defaultValue) ? defaultValue : [defaultValue];\n    }\n    return [min];\n  }, [value, defaultValue, min]);\n\n  function stopPointerDrag() {\n    pointerStartRef.current = null;\n    setIsPointerDragging(false);\n    setIsPointerDown(false);\n  }\n\n  return (\n    <SliderSizeContext.Provider value={resolvedSize}>\n      <SliderPrimitive.Root\n        className={cn(\"data-[orientation=horizontal]:w-full\", className)}\n        data-size={resolvedSize}\n        data-slot=\"slider\"\n        defaultValue={defaultValue}\n        max={max}\n        min={min}\n        onPointerCancel={(event) => {\n          stopPointerDrag();\n          onPointerCancel?.(event);\n        }}\n        onPointerDown={(event) => {\n          pointerStartRef.current = {\n            x: event.clientX,\n            y: event.clientY,\n          };\n          setIsPointerDown(true);\n          setIsPointerDragging(false);\n          onPointerDown?.(event);\n        }}\n        onPointerMove={(event) => {\n          const start = pointerStartRef.current;\n\n          if (start && !isPointerDragging) {\n            const distance = Math.hypot(event.clientX - start.x, event.clientY - start.y);\n\n            if (distance >= 6) {\n              setIsPointerDragging(true);\n            }\n          }\n\n          onPointerMove?.(event);\n        }}\n        onPointerUp={(event) => {\n          stopPointerDrag();\n          onPointerUp?.(event);\n        }}\n        thumbAlignment=\"edge\"\n        value={value}\n        {...props}\n      >\n        {children}\n        <SliderPrimitive.Control\n          className={cn(\n            sliderControlVariants({ size: resolvedSize }),\n            isPointerDown && \"cursor-grabbing\",\n          )}\n          data-size={resolvedSize}\n          data-slot=\"slider-control\"\n        >\n          <SliderPrimitive.Track\n            className={cn(\n              sliderTrackVariants({ size: resolvedSize }),\n              isPointerDown && \"cursor-grabbing\",\n            )}\n            data-size={resolvedSize}\n            data-slot=\"slider-track\"\n          >\n            <SliderPrimitive.Indicator\n              className={cn(\n                \"select-none rounded-sm bg-primary transition-[width,height] [transition-duration:var(--motion-duration-slow)] [transition-timing-function:var(--motion-ease-apple-out)] motion-reduce:transition-none data-[orientation=horizontal]:ms-0.5 data-[orientation=vertical]:mb-0.5\",\n                isPointerDragging && \"transition-none\",\n              )}\n              data-slot=\"slider-indicator\"\n            />\n            {Array.from({ length: _values.length }, (_, index) => (\n              <SliderPrimitive.Thumb\n                className={cn(\n                  sliderThumbVariants({ size: resolvedSize }),\n                  isPointerDown && \"cursor-grabbing\",\n                  isPointerDragging && \"transition-none\",\n                )}\n                data-size={resolvedSize}\n                data-slot=\"slider-thumb\"\n                index={index}\n                key={String(index)}\n              />\n            ))}\n          </SliderPrimitive.Track>\n        </SliderPrimitive.Control>\n      </SliderPrimitive.Root>\n    </SliderSizeContext.Provider>\n  );\n}\n\nexport interface SliderValueProps\n  extends Omit<SliderPrimitive.Value.Props, \"size\">,\n    VariantProps<typeof sliderValueVariants> {}\n\nexport function SliderValue({\n  className,\n  size,\n  ...props\n}: SliderValueProps): React.ReactElement {\n  const groupSize = React.useContext(SliderSizeContext);\n  const resolvedSize = useResolvedControlSize(size ?? groupSize);\n\n  return (\n    <SliderPrimitive.Value\n      className={cn(sliderValueVariants({ size: resolvedSize }), className)}\n      data-size={resolvedSize}\n      data-slot=\"slider-value\"\n      {...props}\n    />\n  );\n}\n\nexport { SliderPrimitive };\n"
    }
  ],
  "categories": [
    "form-controls"
  ],
  "docs": "# Slider\n\n> Siteplane UI release `0.1.1`.\n\n## Public Purpose\n\nUse Slider for choosing one or more values from a continuous or stepped visual range.\n\n## Import\n\n`import { Slider, SliderValue } from \"@/components/ui/slider\";`\n\n`import { Field, FieldLabel } from \"@/components/ui/field\";`\n\n## Public API\n\n- `Slider` accepts controlled or uncontrolled values, `min`, `max`, `step`, `orientation`, `disabled` and `size=\"sm\" | \"default\" | \"lg\" | \"xl\"` through the Base UI root contract.\n- `thumbCollisionBehavior=\"push\" | \"swap\" | \"none\"` controls how thumbs behave when a range selection reaches another thumb.\n- `SliderValue` displays the current value and accepts its own size override; otherwise it inherits the root or provider size.\n- A value array renders one thumb per value; keep value order and labels meaningful for range sliders.\n\n## Public Motion\n\n- Track fill and thumb feedback use shared Siteplane transition tokens. Dragging disables delayed interpolation so the control follows the pointer directly.\n- The operating-system reduced-motion preference removes thumb and track transitions. The forced `reducedMotionPreset` shortens their shared slow duration from 320ms to 100ms without changing drag or keyboard behavior.\n\n## Public Accessibility\n\n- Provide an accessible label for every slider and distinct labels for multiple thumbs.\n- Keep keyboard increments, orientation and value limits aligned with the visible range.\n- Expose understandable value text when raw numbers do not describe the unit or meaning.\n\n## Public Agent Guidance\n\n- Use Number Field when exact typed entry is primary, Meter for read-only measurement and Progress for task completion.\n- Choose `thumbCollisionBehavior` deliberately for multi-thumb sliders instead of assuming every range should push.\n- Do not rebuild track, indicator or thumbs from raw elements.\n\n## Public Links\n\n- [Documentation and preview](https://ui.siteplane.io/docs/components/form-controls/slider)\n\n- [Registry source](https://ui.siteplane.io/r/slider.json)\n",
  "meta": {
    "siteplane:agentContractVersion": 1,
    "siteplane:docs": "https://ui.siteplane.io/docs/components/form-controls/slider",
    "siteplane:releaseVersion": "0.1.1",
    "siteplane:sourceOwned": true
  },
  "devDependencies": []
}
