Skip to content

Commit eaaf7fe

Browse files
committed
feat: add range slider mode
1 parent a7c1169 commit eaaf7fe

8 files changed

Lines changed: 895 additions & 40 deletions

File tree

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,13 +83,19 @@ To use this library you need to ensure you are using the correct version of Reac
8383
| `minimumValue` | Initial minimum value of the slider.<br/>Default value is 0. | number | |
8484
| `lowerLimit` | Slide lower limit. The user won't be able to slide below this limit. | number | Android, iOS, Web |
8585
| `upperLimit` | Slide upper limit. The user won't be able to slide above this limit. | number | Android, iOS, Web |
86+
| `minimumRange` | Minimum distance between lower and upper thumbs when `range` is true.<br/>Default value is 0. | number | |
8687
| `onSlidingStart` | Callback that is called when the user picks up the slider.<br/>The initial value is passed as an argument to the callback handler. | function | |
8788
| `onSlidingComplete` | Callback that is called when the user releases the slider, regardless if the value has changed.<br/>The current value is passed as an argument to the callback handler. | function | |
8889
| `onValueChange` | Callback continuously called while the user is dragging the slider. | function | |
90+
| `onRangeSlidingStart` | Callback that is called when the user touches either range thumb.<br/>The current values and active thumb index are passed as arguments. Used when `range` is true. | function | |
91+
| `onRangeSlidingComplete` | Callback that is called when the user releases either range thumb.<br/>The current values and active thumb index are passed as arguments. Used when `range` is true. | function | |
92+
| `onValuesChange` | Callback continuously called while the user is dragging either range thumb.<br/>The current values and active thumb index are passed as arguments. Used when `range` is true. | function | |
8993
| `step` | Step value of the slider. The value should be between 0 and (maximumValue - minimumValue). Default value is 0.<br/>On Windows OS the default value is 1% of slider's range (from `minimumValue` to `maximumValue`). | number | |
9094
| `maximumTrackTintColor` | The color used for the track to the right of the button.<br/>Overrides the default gray gradient image on iOS. | [color](https://reactnative.dev/docs/colors) | |
9195
| `testID` | Used to locate this view in UI automation tests. | string | |
9296
| `value` | Write-only property representing the value of the slider. Can be used to programmatically control the position of the thumb. Entered once at the beginning still acts as an initial value. Changing the value programmatically does not trigger any event.<br/>The value should be between minimumValue and maximumValue, which default to 0 and 1 respectively. Default value is 0.<br/>_This is not a controlled component_, you don't need to update the value during dragging. | number | |
97+
| `range` | Enables two-thumb range selection.<br/>Default value is false. | bool | |
98+
| `values` | Write-only property representing the lower and upper values of the range slider. Used when `range` is true. | [number, number] | |
9399
| `tapToSeek` | Permits tapping on the slider track to set the thumb position.<br/>Defaults to false on iOS. No effect on Android or Windows. | bool | iOS |
94100
| `inverted` | Reverses the direction of the slider.<br/>Default value is false. | bool | |
95101
| `vertical` | Changes the orientation of the slider to vertical, if set to `true`.<br/>Default value is false. | bool | Windows |

example-web/src/Examples.tsx

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,28 @@ const SliderExample = (props: SliderProps) => {
2525
);
2626
};
2727

28+
const RangeSliderExample = () => {
29+
const [values, setValues] = useState<[number, number]>([20, 80]);
30+
31+
return (
32+
<View>
33+
<Text style={styles.text}>
34+
{values[0].toFixed(0)} - {values[1].toFixed(0)}
35+
</Text>
36+
<Slider
37+
range
38+
values={values}
39+
minimumValue={0}
40+
maximumValue={100}
41+
minimumRange={5}
42+
step={1}
43+
style={styles.slider}
44+
onValuesChange={(nextValues) => setValues(nextValues)}
45+
/>
46+
</View>
47+
);
48+
};
49+
2850
const SlidingStartExample = (props: SliderProps) => {
2951
const [slideStartingValue, setSlideStartingValue] = useState(0);
3052
const [slideStartingCount, setSlideStartingCount] = useState(0);
@@ -105,6 +127,12 @@ export const examples: Props[] = [
105127
return <SliderExample minimumValue={0} maximumValue={10} lowerLimit={2} upperLimit={7} />;
106128
},
107129
},
130+
{
131+
title: 'Range slider',
132+
render(): React.ReactElement {
133+
return <RangeSliderExample />;
134+
},
135+
},
108136
{
109137
title: 'step: 0.25, tap to seek on iOS',
110138
render(): React.ReactElement {

example/src/Examples.tsx

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,28 @@ const SliderExample = (props: SliderProps) => {
3131
);
3232
};
3333

34+
const RangeSliderExample = () => {
35+
const [values, setValues] = useState<[number, number]>([20, 80]);
36+
37+
return (
38+
<View style={{alignItems: 'center'}}>
39+
<Text style={styles.text}>
40+
{values[0].toFixed(0)} - {values[1].toFixed(0)}
41+
</Text>
42+
<Slider
43+
range
44+
values={values}
45+
minimumValue={0}
46+
maximumValue={100}
47+
minimumRange={5}
48+
step={1}
49+
style={styles.slider}
50+
onValuesChange={(nextValues) => setValues(nextValues)}
51+
/>
52+
</View>
53+
);
54+
};
55+
3456
const SlidingStartExample = (props: SliderProps) => {
3557
const [slideStartingValue, setSlideStartingValue] = useState(0);
3658
const [slideStartingCount, setSlideStartingCount] = useState(0);
@@ -594,6 +616,12 @@ export const examples: Props[] = [
594616
);
595617
},
596618
},
619+
{
620+
title: 'Range slider',
621+
render() {
622+
return <RangeSliderExample />;
623+
},
624+
},
597625
{
598626
title: 'onSlidingStart',
599627
render(): React.ReactElement {

package/__test__/Slider.test.tsx

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,4 +77,68 @@ describe('Slider', () => {
7777
fireEvent(getByTestId('slider'), 'onResponderTerminationRequest');
7878
expect(mockedRelease).not.toHaveBeenCalled();
7979
});
80+
81+
it('Calls the given onValuesChange when a range thumb is moved', () => {
82+
const onValuesChange = jest.fn();
83+
const {getByTestId} = render(
84+
<Slider
85+
testID="slider"
86+
range
87+
values={[2, 8]}
88+
minimumValue={0}
89+
maximumValue={10}
90+
onValuesChange={onValuesChange}
91+
/>,
92+
);
93+
const slider = getByTestId('slider');
94+
95+
fireEvent(slider, 'onLayout', {nativeEvent: {layout: {width: 120}}});
96+
fireEvent(slider, 'onResponderGrant', {nativeEvent: {locationX: 30}});
97+
fireEvent(slider, 'onResponderMove', {nativeEvent: {locationX: 50}});
98+
99+
expect(onValuesChange).toHaveBeenCalledWith([4, 8], 0);
100+
});
101+
102+
it('Calls the given onRangeSlidingComplete when a range thumb is released', () => {
103+
const onRangeSlidingComplete = jest.fn();
104+
const {getByTestId} = render(
105+
<Slider
106+
testID="slider"
107+
range
108+
values={[2, 8]}
109+
minimumValue={0}
110+
maximumValue={10}
111+
onRangeSlidingComplete={onRangeSlidingComplete}
112+
/>,
113+
);
114+
const slider = getByTestId('slider');
115+
116+
fireEvent(slider, 'onLayout', {nativeEvent: {layout: {width: 120}}});
117+
fireEvent(slider, 'onResponderGrant', {nativeEvent: {locationX: 90}});
118+
fireEvent(slider, 'onResponderRelease', {nativeEvent: {locationX: 80}});
119+
120+
expect(onRangeSlidingComplete).toHaveBeenCalledWith([2, 7], 1);
121+
});
122+
123+
it('Keeps range thumbs from crossing each other', () => {
124+
const onValuesChange = jest.fn();
125+
const {getByTestId} = render(
126+
<Slider
127+
testID="slider"
128+
range
129+
values={[2, 5]}
130+
minimumValue={0}
131+
maximumValue={10}
132+
minimumRange={1}
133+
onValuesChange={onValuesChange}
134+
/>,
135+
);
136+
const slider = getByTestId('slider');
137+
138+
fireEvent(slider, 'onLayout', {nativeEvent: {layout: {width: 120}}});
139+
fireEvent(slider, 'onResponderGrant', {nativeEvent: {locationX: 30}});
140+
fireEvent(slider, 'onResponderMove', {nativeEvent: {locationX: 100}});
141+
142+
expect(onValuesChange).toHaveBeenCalledWith([4, 5], 0);
143+
});
80144
});

package/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
"preset": "react-native",
6262
"verbose": true,
6363
"modulePathIgnorePatterns": [
64+
"/dist/",
6465
"/e2e/"
6566
]
6667
},

0 commit comments

Comments
 (0)