{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"range-slider","type":"registry:component","title":"Range Slider","description":"Native range input with a filled track and tick dots, so keyboard and screen reader support come built in.","author":"Ryan","dependencies":["clsx","tailwind-merge"],"registryDependencies":[],"files":[{"path":"components/motion/range-slider.tsx","type":"registry:component","target":"@components/motion/range-slider.tsx","content":"\"use client\";\n// easeui.dev/components/motion/range-slider\n\nimport { type ChangeEvent, useState } from \"react\";\nimport { cn } from \"@/lib/utils\";\n\nexport interface RangeSliderProps {\n  value?: number;\n  defaultValue?: number;\n  onValueChange?: (value: number) => void;\n  min?: number;\n  max?: number;\n  step?: number;\n  /** Draw a dot at each step when there are 20 steps or fewer. Default true. */\n  showTicks?: boolean;\n  disabled?: boolean;\n  name?: string;\n  id?: string;\n  className?: string;\n  \"aria-label\"?: string;\n  \"aria-labelledby\"?: string;\n}\n\nconst MAX_TICKS = 20;\n/** Thumb diameter in px. The fill and ticks line up with the thumb's center. */\nconst THUMB = 20;\n\n// Pseudo-element styles for the native thumb and track in both engines.\nconst THUMB_CLASS = [\n  \"[&::-webkit-slider-runnable-track]:bg-transparent\",\n  \"[&::-webkit-slider-thumb]:h-5 [&::-webkit-slider-thumb]:w-5 [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-background\",\n  \"[&::-webkit-slider-thumb]:shadow-[0_0_0_1px_var(--border-strong),0_1px_3px_rgb(0_0_0/0.25)]\",\n  \"[&::-webkit-slider-thumb]:transition-transform [&::-webkit-slider-thumb]:duration-150 [&::-webkit-slider-thumb]:ease-out\",\n  \"active:[&::-webkit-slider-thumb]:scale-110\",\n  \"focus-visible:[&::-webkit-slider-thumb]:shadow-[0_0_0_4px_color-mix(in_oklch,var(--foreground)_25%,transparent)]\",\n  \"[&::-moz-range-track]:bg-transparent\",\n  \"[&::-moz-range-thumb]:h-5 [&::-moz-range-thumb]:w-5 [&::-moz-range-thumb]:rounded-full [&::-moz-range-thumb]:border-0 [&::-moz-range-thumb]:bg-background\",\n  \"[&::-moz-range-thumb]:shadow-[0_0_0_1px_var(--border-strong),0_1px_3px_rgb(0_0_0/0.25)]\",\n  \"[&::-moz-range-thumb]:transition-transform [&::-moz-range-thumb]:duration-150 [&::-moz-range-thumb]:ease-out\",\n  \"active:[&::-moz-range-thumb]:scale-110\",\n].join(\" \");\n\n/** Position along the track, accounting for the thumb staying inside the edges. */\nconst along = (fraction: number) => `calc(${THUMB / 2}px + (100% - ${THUMB}px) * ${fraction})`;\n\nexport function RangeSlider({\n  value,\n  defaultValue,\n  onValueChange,\n  min = 0,\n  max = 100,\n  step = 1,\n  showTicks = true,\n  disabled = false,\n  className,\n  ...inputProps\n}: RangeSliderProps) {\n  const [inner, setInner] = useState(defaultValue ?? min);\n  const current = value ?? inner;\n  const fraction = max === min ? 0 : (current - min) / (max - min);\n  // toFixed guards against float noise like 0.3 / 0.1 = 2.9999999999999996.\n  const steps = Math.floor(Number(((max - min) / step).toFixed(6)));\n  const ticks =\n    showTicks && steps > 0 && steps <= MAX_TICKS\n      ? Array.from({ length: steps + 1 }, (_, i) => i / steps)\n      : [];\n\n  const onChange = (event: ChangeEvent<HTMLInputElement>) => {\n    const next = Number(event.target.value);\n    if (value === undefined) setInner(next);\n    onValueChange?.(next);\n  };\n\n  return (\n    <div className={cn(\"relative flex h-10 w-full items-center\", disabled && \"opacity-50\", className)}>\n      {/* Track and fill are decoration. The native input underneath does the work. */}\n      <div aria-hidden=\"true\" className=\"pointer-events-none absolute inset-x-0 h-1.5 rounded-full bg-muted\">\n        <div className=\"h-full rounded-full bg-foreground\" style={{ width: along(fraction) }} />\n      </div>\n      {ticks.map((tick) => (\n        <span\n          key={tick}\n          aria-hidden=\"true\"\n          className={cn(\n            \"pointer-events-none absolute top-1/2 size-1 -translate-x-1/2 -translate-y-1/2 rounded-full\",\n            tick <= fraction ? \"bg-background/70\" : \"bg-foreground/25\",\n          )}\n          style={{ left: along(tick) }}\n        />\n      ))}\n      <input\n        type=\"range\"\n        min={min}\n        max={max}\n        step={step}\n        value={current}\n        disabled={disabled}\n        onChange={onChange}\n        className={cn(\n          \"relative z-10 h-10 w-full cursor-pointer touch-manipulation appearance-none bg-transparent outline-none disabled:cursor-not-allowed\",\n          THUMB_CLASS,\n        )}\n        {...inputProps}\n      />\n    </div>\n  );\n}\n"},{"path":"lib/utils.ts","type":"registry:lib","target":"@lib/utils.ts","content":"import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"}]}