{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "slider", "type": "registry:ui", "title": "Slider", "files": [ { "path": "ui/slider.tsrx", "type": "registry:ui", "target": "components/ui/slider.tsrx", "content": "// Base UI base slider — TRANSCRIBED from upstream's `bases/base-ui/ui/slider.tsx`\n// (maintainer-supplied). Structure, part tree and class strings follow that source.\n//\n// THE PART TREE IS NOT THE RADIX ONE:\n//\n// radix Root > Track > Range, Thumb a SIBLING of Track (under Root)\n// Base UI Root > Control > Track > Indicator, Thumb a SIBLING of Track (under Control)\n//\n// The thumb sits OUTSIDE the track in both. That matters: the track carries `overflow-hidden` —\n// which is what gives the fill its rounded ends — and is only `h-1` tall, so a round thumb nested\n// inside it is clipped to a 4px sliver that reads as a missing thumb. Base UI positions the thumb\n// from slider context rather than from its DOM parent (`inset-inline-start: ` plus a\n// centering `translate`), so Control is the box those percentages resolve against.\n//\n// ONE DEPARTURE FROM THE SOURCE, AND IT IS DELIBERATE: upstream writes its orientation variants as\n// `data-horizontal:` / `data-vertical:`. No primitive in @octanejs/base-ui emits those attributes —\n// this one publishes `data-orientation=\"horizontal\" | \"vertical\"` on Root, Control, Track,\n// Indicator and Thumb (verified by rendering it), so the variants are written as\n// `data-[orientation=…]:` instead. Copied verbatim, upstream's spelling would match nothing and the\n// track would render with no height at all: an invisible rail. Whether the primitive should be\n// emitting the newer attribute names is a question about @octanejs/base-ui, not about this file.\n//\n// `thumbAlignment=\"edge\"` is forwarded as upstream does: it insets the thumb so the two extremes\n// sit flush with the ends of the track instead of overhanging them.\n//\n// Thumbs are composed with createElement for the same reason as the radix base: one per entry of\n// `_values`, and mapped descriptors are octane's channel for computed children.\nimport { createElement, useMemo } from 'octane';\nimport { Slider as SliderPrimitive } from '@octanejs/base-ui/slider';\n\nimport { cn } from '@/lib/utils';\n\nexport interface SliderProps extends Record {\n\tclassName?: string;\n\tdefaultValue?: number | number[];\n\tvalue?: number | number[];\n\tmin?: number;\n\tmax?: number;\n}\n\nexport function Slider({\n\tclassName,\n\tdefaultValue,\n\tvalue,\n\tmin = 0,\n\tmax = 100,\n\t...props\n}: SliderProps) {\n\t// This drives HOW MANY THUMBS get rendered, so a value shape it fails to recognise is a visible\n\t// bug rather than a type nicety.\n\t//\n\t// DEPARTS FROM THE TRANSCRIBED SOURCE, deliberately. Upstream only tests `Array.isArray` and\n\t// otherwise falls back to `[min, max]` — but Base UI's Slider accepts a SCALAR for a\n\t// single-value slider, and upstream types these props as `SliderPrimitive.Root.Props`, which\n\t// permits one. So `defaultValue={30}` renders two thumbs against a one-value slider: the range\n\t// fallback fires, and the second thumb has no value behind it.\n\tconst _values = useMemo(() => {\n\t\t// A controlled `value` wins when present; `??` rather than `||` so a legitimate 0 counts.\n\t\tconst provided = value ?? defaultValue;\n\t\tif (Array.isArray(provided)) {\n\t\t\treturn provided;\n\t\t}\n\t\tif (typeof provided === 'number') {\n\t\t\treturn [provided];\n\t\t}\n\t\t// Neither given: upstream's range-across-the-track default, kept as-is in both bases.\n\t\treturn [min, max];\n\t}, [value, defaultValue, min, max]);\n\n\treturn createElement(\n\t\tSliderPrimitive.Root,\n\t\t{\n\t\t\tclassName: cn(\n\t\t\t\t'data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full',\n\t\t\t\tclassName,\n\t\t\t),\n\t\t\t'data-slot': 'slider',\n\t\t\tdefaultValue,\n\t\t\tvalue,\n\t\t\tmin,\n\t\t\tmax,\n\t\t\tthumbAlignment: 'edge',\n\t\t\t...props,\n\t\t},\n\t\tcreateElement(\n\t\t\tSliderPrimitive.Control,\n\t\t\t{\n\t\t\t\tclassName: 'relative flex w-full touch-none items-center select-none data-disabled:opacity-50 data-[orientation=vertical]:h-full data-[orientation=vertical]:min-h-40 data-[orientation=vertical]:w-auto data-[orientation=vertical]:flex-col',\n\t\t\t},\n\t\t\tcreateElement(\n\t\t\t\tSliderPrimitive.Track,\n\t\t\t\t{\n\t\t\t\t\t'data-slot': 'slider-track',\n\t\t\t\t\tclassName: 'relative grow overflow-hidden rounded-full bg-muted select-none data-[orientation=horizontal]:h-1 data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-1',\n\t\t\t\t},\n\t\t\t\tcreateElement(SliderPrimitive.Indicator, {\n\t\t\t\t\t'data-slot': 'slider-range',\n\t\t\t\t\tclassName: 'bg-primary select-none data-[orientation=horizontal]:h-full data-[orientation=vertical]:w-full',\n\t\t\t\t}),\n\t\t\t),\n\t\t\t...Array.from(\n\t\t\t\t{ length: _values.length },\n\t\t\t\t(_, index) => createElement(SliderPrimitive.Thumb, {\n\t\t\t\t\t'data-slot': 'slider-thumb',\n\t\t\t\t\tkey: index,\n\t\t\t\t\tclassName: 'relative block size-3 shrink-0 rounded-full border border-ring bg-white ring-ring/50 transition-[color,box-shadow] select-none after:absolute after:-inset-2 hover:ring-3 focus-visible:ring-3 focus-visible:outline-hidden active:ring-3 disabled:pointer-events-none disabled:opacity-50',\n\t\t\t\t}),\n\t\t\t),\n\t\t),\n\t);\n}\n" } ], "dependencies": [ "@octanejs/base-ui@0.1.36" ], "registryDependencies": [ "@octane/utils" ] }