{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "slider",
  "title": "Slider",
  "description": "The M3 Expressive slider, continuous or stepped, with the value label.",
  "dependencies": [
    "@base-ui/react@^1.7.0"
  ],
  "registryDependencies": [
    "https://guillermo-rebolledo.github.io/materialcn/r/materialcn-theme.json",
    "https://guillermo-rebolledo.github.io/materialcn/r/utils.json"
  ],
  "files": [
    {
      "path": "src/components/ui/slider.tsx",
      "content": "import { useState } from \"react\"\nimport { Slider as SliderPrimitive } from \"@base-ui/react/slider\"\n\nimport { cn } from \"@/lib/utils\"\n\n/**\n * Material 3 Expressive slider.\n *\n * Kit geometry (Sliders page): five sizes with a thick track and a narrow 4dp\n * handle that squeezes to 2dp while dragged. The active and inactive tracks\n * are separate shapes — outer ends fully round, handle-facing ends 2dp — with\n * a real 6dp gap either side of the handle, so the handle appears to part the\n * track rather than sit on top of it.\n *\n * | Size | Track | Handle |\n * | ---- | ----- | ------ |\n * | xs   | 16    | 44     |\n * | sm   | 24    | 44     |\n * | md   | 40    | 52     |\n * | lg   | 56    | 68     |\n * | xl   | 96    | 108    |\n */\n\ntype SliderSize = \"xs\" | \"sm\" | \"md\" | \"lg\" | \"xl\"\n/** @deprecated `small` / `medium` / `large` map to `xs` / `sm` / `md`. */\ntype LegacySliderSize = \"small\" | \"medium\" | \"large\"\ntype SliderVariant = \"standard\" | \"centered\"\ntype SliderProps = SliderPrimitive.Root.Props & {\n  getAriaLabel?: SliderPrimitive.Thumb.Props[\"getAriaLabel\"]\n  showTicks?: boolean\n  showValue?: boolean\n  size?: SliderSize | LegacySliderSize\n  variant?: SliderVariant\n}\n\nconst legacySizes: Record<LegacySliderSize, SliderSize> = { small: \"xs\", medium: \"sm\", large: \"md\" }\n\nconst trackSize: Record<SliderSize, string> = {\n  xs: \"data-horizontal:h-4 data-vertical:w-4\",\n  sm: \"data-horizontal:h-6 data-vertical:w-6\",\n  md: \"data-horizontal:h-10 data-vertical:w-10\",\n  lg: \"data-horizontal:h-14 data-vertical:w-14\",\n  xl: \"data-horizontal:h-24 data-vertical:w-24\",\n}\n\nconst handleSize: Record<SliderSize, string> = {\n  xs: \"data-horizontal:h-11 data-vertical:w-11\",\n  sm: \"data-horizontal:h-11 data-vertical:w-11\",\n  md: \"data-horizontal:h-13 data-vertical:w-13\",\n  lg: \"data-horizontal:h-17 data-vertical:w-17\",\n  xl: \"data-horizontal:h-27 data-vertical:w-27\",\n}\n\n/** Half the 4dp handle plus the kit's 6dp gap. */\nconst HANDLE_CLEARANCE = 8\n\nfunction Slider({\n  \"aria-label\": ariaLabel,\n  className,\n  defaultValue,\n  getAriaLabel,\n  max = 100,\n  min = 0,\n  onValueChange,\n  orientation = \"horizontal\",\n  showTicks = false,\n  showValue = false,\n  size: sizeProp = \"xs\",\n  step = 1,\n  value,\n  variant = \"standard\",\n  ...props\n}: SliderProps) {\n  const size: SliderSize = sizeProp in legacySizes ? legacySizes[sizeProp as LegacySliderSize] : (sizeProp as SliderSize)\n  const normalizeValues = (input: number | readonly number[] | undefined) =>\n    typeof input === \"number\" ? [input] : Array.isArray(input) ? [...input] : [min]\n  const [uncontrolledValues, setUncontrolledValues] = useState(() => normalizeValues(defaultValue))\n  const values = value === undefined ? uncontrolledValues : normalizeValues(value)\n  const percentage = (number: number) => ((number - min) / (max - min)) * 100\n  const centered = variant === \"centered\" && values.length === 1\n  const currentPercentage = percentage(values[0] ?? min)\n  const tickCount = Math.min(100, Math.max(1, Math.floor((max - min) / step)))\n  const horizontal = orientation === \"horizontal\"\n\n  // The track is drawn as segments between the thumbs (and the midpoint for a\n  // centered slider) so each one can carry its own corners and clearance.\n  type Boundary = { at: number; handle: boolean }\n  const boundaries: Boundary[] = [\n    { at: 0, handle: false },\n    ...values.map((item) => ({ at: percentage(item), handle: true })),\n    ...(centered ? [{ at: 50, handle: false }] : []),\n    { at: 100, handle: false },\n  ].sort((a, b) => a.at - b.at)\n  const segments = boundaries.slice(0, -1).map((start, index) => {\n    const end = boundaries[index + 1]\n    const active = centered\n      ? Math.min(start.at, end.at) >= Math.min(50, currentPercentage) && Math.max(start.at, end.at) <= Math.max(50, currentPercentage) && start.at !== end.at\n      : values.length > 1\n        ? index === 1\n        : index === 0\n    return { start, end, active }\n  })\n  const clearance = (boundary: Boundary) => (boundary.handle ? HANDLE_CLEARANCE : 0)\n  const segmentStyle = (start: Boundary, end: Boundary) =>\n    horizontal\n      ? { left: `calc(${start.at}% + ${clearance(start)}px)`, right: `calc(${100 - end.at}% + ${clearance(end)}px)` }\n      : { bottom: `calc(${start.at}% + ${clearance(start)}px)`, top: `calc(${100 - end.at}% + ${clearance(end)}px)` }\n  // Outer ends are round; ends that face a handle are the kit's 2dp corner.\n  const segmentRadius = (start: Boundary, end: Boundary) =>\n    cn(\n      \"rounded-[2px]\",\n      !start.handle && (horizontal ? \"rounded-l-m3-full\" : \"rounded-b-m3-full\"),\n      !end.handle && (horizontal ? \"rounded-r-m3-full\" : \"rounded-t-m3-full\"),\n    )\n\n  return (\n    <SliderPrimitive.Root\n      className={cn(\"data-horizontal:w-full data-vertical:h-full\", className)}\n      aria-label={ariaLabel}\n      data-slot=\"slider\"\n      data-size={size}\n      data-variant={variant}\n      defaultValue={defaultValue}\n      value={value}\n      min={min}\n      max={max}\n      step={step}\n      orientation={orientation}\n      thumbAlignment=\"edge\"\n      onValueChange={(nextValue, eventDetails) => {\n        if (value === undefined) setUncontrolledValues(normalizeValues(nextValue))\n        onValueChange?.(nextValue, eventDetails)\n      }}\n      {...props}\n    >\n      <SliderPrimitive.Control className=\"relative flex w-full touch-none items-center select-none data-vertical:h-full data-vertical:min-h-40 data-vertical:w-auto data-vertical:flex-col\">\n        <SliderPrimitive.Track\n          data-slot=\"slider-track\"\n          className={cn(\n            \"relative grow select-none\",\n            \"data-horizontal:w-full data-vertical:h-full\",\n            trackSize[size],\n          )}\n        >\n          {/* Base UI's indicator is kept for its semantics but painted by the segments below. */}\n          <SliderPrimitive.Indicator data-slot=\"slider-range\" className=\"hidden\" />\n          {segments.map(({ start, end, active }, index) => (\n            <span\n              key={index}\n              aria-hidden=\"true\"\n              data-slot={active ? \"slider-active-track\" : \"slider-inactive-track\"}\n              className={cn(\n                \"absolute\",\n                horizontal ? \"inset-y-0\" : \"inset-x-0\",\n                active ? \"bg-m3-primary\" : \"bg-m3-secondary-container\",\n                segmentRadius(start, end),\n              )}\n              style={segmentStyle(start, end)}\n            />\n          ))}\n          {centered && (\n            <span\n              aria-hidden=\"true\"\n              data-slot=\"slider-center-stop\"\n              className={cn(\"absolute z-10 size-1 -translate-1/2 rounded-full bg-m3-primary\", horizontal ? \"top-1/2 left-1/2\" : \"bottom-1/2 left-1/2\")}\n            />\n          )}\n          {showTicks && Array.from({ length: tickCount + 1 }, (_, index) => {\n            const at = (index / tickCount) * 100\n            const onActive = values.length > 1\n              ? at >= percentage(values[0]) && at <= percentage(values[1])\n              : centered\n                ? at >= Math.min(50, currentPercentage) && at <= Math.max(50, currentPercentage)\n                : at <= currentPercentage\n            return (\n              <span\n                key={index}\n                aria-hidden=\"true\"\n                data-slot=\"slider-stop\"\n                className={cn(\"absolute z-10 size-1 -translate-1/2 rounded-full\", onActive ? \"bg-m3-on-primary\" : \"bg-m3-primary\")}\n                style={horizontal\n                  ? { left: `${at}%`, top: \"50%\" }\n                  : { bottom: `${at}%`, left: \"50%\" }}\n              />\n            )\n          })}\n        </SliderPrimitive.Track>\n        {Array.from({ length: values.length }, (_, index) => (\n          <SliderPrimitive.Thumb\n            data-slot=\"slider-thumb\"\n            key={index}\n            getAriaLabel={getAriaLabel ?? (ariaLabel\n              ? (thumbIndex) => values.length > 1\n                ? `${ariaLabel} ${thumbIndex === 0 ? \"minimum\" : \"maximum\"}`\n                : ariaLabel\n              : undefined)}\n            className={cn(\n              \"group/slider-thumb relative block shrink-0 cursor-grab rounded-[2px] bg-m3-primary select-none\",\n              handleSize[size],\n              // Expressive squeezes the 4dp handle to 2dp while it is dragged.\n              horizontal ? \"w-1 active:w-0.5 data-dragging:w-0.5\" : \"h-1 active:h-0.5 data-dragging:h-0.5\",\n              \"transition-[width,height] duration-(--m3-spring-effects-fast-duration) ease-(--m3-spring-effects-fast)\",\n              // 48dp minimum touch target.\n              \"after:absolute after:-inset-x-6 after:-inset-y-1\",\n              \"active:cursor-grabbing\",\n              \"outline-none focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-m3-secondary\",\n              \"data-disabled:pointer-events-none data-disabled:bg-m3-on-surface/38\",\n            )}\n          >\n            {showValue && (\n              <span\n                data-slot=\"slider-value\"\n                className={cn(\n                  \"pointer-events-none absolute flex h-11 items-center rounded-m3-full bg-m3-inverse-surface px-4 text-m3-label-lg whitespace-nowrap text-m3-inverse-on-surface\",\n                  horizontal ? \"bottom-full left-1/2 mb-1 -translate-x-1/2\" : \"top-1/2 left-full ml-2 -translate-y-1/2\",\n                )}\n              >\n                {values[index]}\n              </span>\n            )}\n          </SliderPrimitive.Thumb>\n        ))}\n      </SliderPrimitive.Control>\n    </SliderPrimitive.Root>\n  )\n}\n\nexport { Slider, type SliderProps, type SliderSize, type SliderVariant }\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "forms"
  ],
  "type": "registry:ui"
}