Skip to content

Commit 8b38903

Browse files
[Slider] Add ability to name each thumb for more flexibility (radix-ui#2766)
Fixes radix-ui#2454
1 parent be80c2a commit 8b38903

File tree

3 files changed

+51
-16
lines changed

3 files changed

+51
-16
lines changed

.yarn/versions/b20a66e3.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
releases:
2+
"@radix-ui/react-slider": minor
3+
4+
declined:
5+
- primitives

packages/react/slider/src/Slider.stories.tsx

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -262,10 +262,20 @@ export const SmallSteps = () => {
262262
};
263263

264264
export const WithinForm = () => {
265-
const [data, setData] = React.useState({ single: [0], multiple: [10, 15, 20, 80] });
265+
const [data, setData] = React.useState({
266+
single: [0],
267+
multiple: [10, 15, 20, 80],
268+
price: {
269+
min: 30,
270+
max: 70,
271+
},
272+
});
266273
return (
267274
<form
268-
onSubmit={(event) => event.preventDefault()}
275+
onSubmit={(event) => {
276+
event.preventDefault();
277+
console.log(serialize(event.currentTarget, { hash: true }));
278+
}}
269279
onChange={(event) => {
270280
const formData = serialize(event.currentTarget, { hash: true });
271281
setData(formData as any);
@@ -296,6 +306,22 @@ export const WithinForm = () => {
296306
<Slider.Thumb className={thumbClass()} />
297307
</Slider.Root>
298308
</fieldset>
309+
310+
<br />
311+
<br />
312+
313+
<fieldset>
314+
<legend>Multiple values (with named thumbs): {JSON.stringify(data.price)}</legend>
315+
<Slider.Root defaultValue={[data.price.min, data.price.max]} className={rootClass()}>
316+
<Slider.Track className={trackClass()}>
317+
<Slider.Range className={rangeClass()} />
318+
</Slider.Track>
319+
<Slider.Thumb className={thumbClass()} name="price[min]" />
320+
<Slider.Thumb className={thumbClass()} name="price[max]" />
321+
</Slider.Root>
322+
</fieldset>
323+
324+
<button type="submit">Submit</button>
299325
</form>
300326
);
301327
};

packages/react/slider/src/Slider.tsx

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ const [createSliderContext, createSliderScope] = createContextScope(SLIDER_NAME,
4141
]);
4242

4343
type SliderContextValue = {
44+
name?: string;
4445
disabled?: boolean;
4546
min: number;
4647
max: number;
@@ -90,13 +91,9 @@ const Slider = React.forwardRef<SliderElement, SliderProps>(
9091
inverted = false,
9192
...sliderProps
9293
} = props;
93-
const [slider, setSlider] = React.useState<HTMLSpanElement | null>(null);
94-
const composedRefs = useComposedRefs(forwardedRef, (node) => setSlider(node));
9594
const thumbRefs = React.useRef<SliderContextValue['thumbs']>(new Set());
9695
const valueIndexToChangeRef = React.useRef<number>(0);
9796
const isHorizontal = orientation === 'horizontal';
98-
// We set this to true by default so that events bubble to forms without JS (SSR)
99-
const isFormControl = slider ? Boolean(slider.closest('form')) : true;
10097
const SliderOrientation = isHorizontal ? SliderHorizontal : SliderVertical;
10198

10299
const [values = [], setValues] = useControllableState({
@@ -147,6 +144,7 @@ const Slider = React.forwardRef<SliderElement, SliderProps>(
147144
return (
148145
<SliderProvider
149146
scope={props.__scopeSlider}
147+
name={name}
150148
disabled={disabled}
151149
min={min}
152150
max={max}
@@ -161,7 +159,7 @@ const Slider = React.forwardRef<SliderElement, SliderProps>(
161159
aria-disabled={disabled}
162160
data-disabled={disabled ? '' : undefined}
163161
{...sliderProps}
164-
ref={composedRefs}
162+
ref={forwardedRef}
165163
onPointerDown={composeEventHandlers(sliderProps.onPointerDown, () => {
166164
if (!disabled) valuesBeforeSlideStartRef.current = values;
167165
})}
@@ -189,14 +187,6 @@ const Slider = React.forwardRef<SliderElement, SliderProps>(
189187
/>
190188
</Collection.Slot>
191189
</Collection.Provider>
192-
{isFormControl &&
193-
values.map((value, index) => (
194-
<BubbleInput
195-
key={index}
196-
name={name ? name + (values.length > 1 ? '[]' : '') : undefined}
197-
value={value}
198-
/>
199-
))}
200190
</SliderProvider>
201191
);
202192
}
@@ -556,15 +546,18 @@ const SliderThumb = React.forwardRef<SliderThumbElement, SliderThumbProps>(
556546
type SliderThumbImplElement = React.ElementRef<typeof Primitive.span>;
557547
interface SliderThumbImplProps extends PrimitiveSpanProps {
558548
index: number;
549+
name?: string;
559550
}
560551

561552
const SliderThumbImpl = React.forwardRef<SliderThumbImplElement, SliderThumbImplProps>(
562553
(props: ScopedProps<SliderThumbImplProps>, forwardedRef) => {
563-
const { __scopeSlider, index, ...thumbProps } = props;
554+
const { __scopeSlider, index, name, ...thumbProps } = props;
564555
const context = useSliderContext(THUMB_NAME, __scopeSlider);
565556
const orientation = useSliderOrientationContext(THUMB_NAME, __scopeSlider);
566557
const [thumb, setThumb] = React.useState<HTMLSpanElement | null>(null);
567558
const composedRefs = useComposedRefs(forwardedRef, (node) => setThumb(node));
559+
// We set this to true by default so that events bubble to forms without JS (SSR)
560+
const isFormControl = thumb ? Boolean(thumb.closest('form')) : true;
568561
const size = useSize(thumb);
569562
// We cast because index could be `-1` which would return undefined
570563
const value = context.values[index] as number | undefined;
@@ -618,6 +611,17 @@ const SliderThumbImpl = React.forwardRef<SliderThumbImplElement, SliderThumbImpl
618611
})}
619612
/>
620613
</Collection.ItemSlot>
614+
615+
{isFormControl && (
616+
<BubbleInput
617+
key={index}
618+
name={
619+
name ??
620+
(context.name ? context.name + (context.values.length > 1 ? '[]' : '') : undefined)
621+
}
622+
value={value}
623+
/>
624+
)}
621625
</span>
622626
);
623627
}

0 commit comments

Comments
 (0)