Skip to content

Commit ccc202e

Browse files
authored
Incubator.Slider with Reanimated (wix#2448)
1 parent 9764ee2 commit ccc202e

File tree

10 files changed

+877
-3
lines changed

10 files changed

+877
-3
lines changed

demo/src/screens/MenuStructure.js

+1
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ export const navigationData = {
6969
},
7070
{title: 'Stepper', tags: 'stepper form', screen: 'unicorn.components.StepperScreen'},
7171
{title: 'Slider', tags: 'slider', screen: 'unicorn.components.SliderScreen'},
72+
{title: 'Slider (Incubator)', tags: 'slider', screen: 'unicorn.components.IncubatorSliderScreen'},
7273
{title: 'Switch', tags: 'switch toggle', screen: 'unicorn.components.SwitchScreen'},
7374
{title: 'Masked Inputs', tags: 'text input form mask', screen: 'unicorn.components.MaskedInputScreen'},
7475
{

demo/src/screens/PlaygroundScreen.tsx

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import _ from 'lodash';
21
import React, {Component} from 'react';
32
import {StyleSheet} from 'react-native';
43
import {View, Text, Card, TextField, Button} from 'react-native-ui-lib'; //eslint-disable-line
@@ -8,7 +7,7 @@ export default class PlaygroundScreen extends Component {
87
return (
98
<View bg-grey80 flex padding-20>
109
<View marginT-20>
11-
<TextField migrate placeholder="Placeholder" />
10+
<TextField migrate placeholder="Placeholder"/>
1211
</View>
1312
<Card height={100} center padding-20>
1413
<Text text50>Playground Screen</Text>
@@ -22,5 +21,5 @@ export default class PlaygroundScreen extends Component {
2221
}
2322

2423
const styles = StyleSheet.create({
25-
container: {},
24+
container: {}
2625
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
import React, {useState, useRef, useCallback} from 'react';
2+
import {StyleSheet, ScrollView} from 'react-native';
3+
import {Constants, Colors, View, Text, Button, Incubator} from 'react-native-ui-lib'; //eslint-disable-line
4+
import {renderBooleanOption} from '../ExampleScreenPresenter';
5+
6+
const VALUE = 20;
7+
const NEGATIVE_VALUE = -30;
8+
const MIN = 0;
9+
const MAX = 100;
10+
const INITIAL_MIN = 10;
11+
const INITIAL_MAX = 70;
12+
13+
const IncubatorSliderScreen = () => {
14+
const [disableRTL, setDisableRTL] = useState<boolean>(false);
15+
16+
const [sliderValue, setSliderValue] = useState(0);
17+
const [customSliderValue, setCustomSliderValue] = useState(VALUE);
18+
const [negativeSliderValue, setNegativeSliderValue] = useState(NEGATIVE_VALUE);
19+
const [sliderMinValue, setSliderMinValue] = useState(INITIAL_MIN);
20+
const [sliderMaxValue, setSliderMaxValue] = useState(INITIAL_MAX);
21+
22+
const slider = useRef<typeof Incubator.Slider>();
23+
const customSlider = useRef<typeof Incubator.Slider>();
24+
const negativeSlider = useRef<typeof Incubator.Slider>();
25+
const rangeSlider = useRef<typeof Incubator.Slider>();
26+
27+
const resetSliders = useCallback(() => {
28+
slider.current?.reset();
29+
customSlider.current?.reset();
30+
rangeSlider.current?.reset();
31+
negativeSlider.current?.reset();
32+
}, []);
33+
34+
const onValueChange = useCallback((value: number) => {
35+
setSliderValue(value);
36+
}, []);
37+
38+
const onCustomValueChange = useCallback((value: number) => {
39+
setCustomSliderValue(value);
40+
}, []);
41+
42+
const onNegativeValueChange = useCallback((value: number) => {
43+
setNegativeSliderValue(value);
44+
}, []);
45+
46+
const onRangeChange = useCallback((value: {min: number; max: number}) => {
47+
setSliderMaxValue(value.max);
48+
setSliderMinValue(value.min);
49+
}, []);
50+
51+
const renderValuesBox = (min: number, max?: number) => {
52+
if (max !== undefined) {
53+
return (
54+
<View row spread marginB-20>
55+
<Text bodySmall $textNeutral>
56+
min. {min}
57+
</Text>
58+
<Text bodySmall $textNeutral>
59+
max. {max}
60+
</Text>
61+
</View>
62+
);
63+
} else {
64+
return (
65+
<View center marginB-20>
66+
<Text bodySmall $textNeutral>
67+
value: {min}
68+
</Text>
69+
</View>
70+
);
71+
}
72+
};
73+
74+
const renderDefaultSliderExample = () => {
75+
return (
76+
<View>
77+
<Text margin-10 text70BL $textDefault>
78+
Default Slider values 0 to 1
79+
</Text>
80+
{renderValuesBox(sliderValue)}
81+
<Incubator.Slider
82+
ref={slider}
83+
onValueChange={onValueChange}
84+
containerStyle={styles.container}
85+
disableRTL={disableRTL}
86+
// maximumValue={0}
87+
// maximumValue={50}
88+
step={0.1}
89+
/>
90+
</View>
91+
);
92+
};
93+
94+
const renderDisabledSliderExample = () => {
95+
return (
96+
<View marginT-20>
97+
<Text margin-10 text70BL $textDefault>
98+
Disabled Slider
99+
</Text>
100+
<Incubator.Slider value={0.4} containerStyle={styles.container} disableRTL={disableRTL} disabled/>
101+
</View>
102+
);
103+
};
104+
105+
const renderCustomSliderExample = () => {
106+
return (
107+
<View marginT-20 marginH-40>
108+
<Text margin-10 text70BL $textDefault>
109+
Custom Slider
110+
</Text>
111+
{renderValuesBox(customSliderValue)}
112+
<Incubator.Slider
113+
ref={customSlider}
114+
onValueChange={onCustomValueChange}
115+
value={20}
116+
minimumValue={0}
117+
maximumValue={100}
118+
containerStyle={styles.container}
119+
trackStyle={styles.customTrack}
120+
minimumTrackTintColor={Colors.grey30}
121+
maximumTrackTintColor={Colors.grey70}
122+
// thumbTintColor={Colors.orange30}
123+
thumbStyle={styles.customThumb}
124+
activeThumbStyle={styles.customActiveThumb}
125+
disableRTL={disableRTL}
126+
// disableActiveStyling
127+
/>
128+
</View>
129+
);
130+
};
131+
132+
const renderNegativeSliderExample = () => {
133+
return (
134+
<View marginT-20>
135+
<Text margin-10 text70BL $textDefault>
136+
Negative values -20 to -100 initial -30
137+
</Text>
138+
{renderValuesBox(negativeSliderValue)}
139+
<Incubator.Slider
140+
ref={negativeSlider}
141+
onValueChange={onNegativeValueChange}
142+
value={-30}
143+
minimumValue={-100}
144+
maximumValue={-20}
145+
// step={10}
146+
containerStyle={styles.container}
147+
disableRTL={disableRTL}
148+
/>
149+
</View>
150+
);
151+
};
152+
153+
const renderRangeSliderExample = () => {
154+
return (
155+
<View marginT-20>
156+
<Text margin-10 text70BL $textDefault>
157+
Range Slider values 0 to 100
158+
</Text>
159+
<View marginH-20>
160+
{renderValuesBox(sliderMinValue, sliderMaxValue)}
161+
<Incubator.Slider
162+
ref={rangeSlider}
163+
useRange
164+
useGap
165+
onRangeChange={onRangeChange}
166+
minimumValue={MIN}
167+
maximumValue={MAX}
168+
initialMinimumValue={INITIAL_MIN}
169+
initialMaximumValue={INITIAL_MAX}
170+
step={1}
171+
disableRTL={disableRTL}
172+
/>
173+
</View>
174+
</View>
175+
);
176+
};
177+
178+
return (
179+
<ScrollView showsVerticalScrollIndicator={false} style={{backgroundColor: Colors.$backgroundDefault}}>
180+
<View row spread margin-20>
181+
<Text h1 $textDefault>
182+
Slider
183+
</Text>
184+
<Button link label="Reset Sliders" onPress={resetSliders}/>
185+
</View>
186+
<View marginL-20>
187+
{Constants.isRTL &&
188+
renderBooleanOption('Disable RTL', 'disableRTL', {spread: false, state: disableRTL, setState: setDisableRTL})}
189+
</View>
190+
{renderDefaultSliderExample()}
191+
{renderDisabledSliderExample()}
192+
{renderCustomSliderExample()}
193+
{renderNegativeSliderExample()}
194+
{renderRangeSliderExample()}
195+
</ScrollView>
196+
);
197+
};
198+
199+
export default IncubatorSliderScreen;
200+
201+
const styles = StyleSheet.create({
202+
container: {
203+
marginHorizontal: 20
204+
},
205+
customTrack: {
206+
borderRadius: 1.5,
207+
height: 3
208+
},
209+
customThumb: {
210+
width: 14,
211+
height: 14,
212+
borderRadius: 7,
213+
backgroundColor: Colors.black,
214+
borderColor: Colors.black
215+
},
216+
customActiveThumb: {
217+
backgroundColor: Colors.red30,
218+
borderWidth: 2,
219+
borderColor: Colors.red70
220+
}
221+
});

demo/src/screens/incubatorScreens/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ export function registerScreens(registrar) {
88
registrar('unicorn.components.IncubatorExpandableOverlayScreen', () => require('./IncubatorExpandableOverlayScreen').default);
99
registrar('unicorn.components.IncubatorToastScreen', () => require('./IncubatorToastScreen').default);
1010
registrar('unicorn.incubator.PanViewScreen', () => require('./PanViewScreen').default);
11+
registrar('unicorn.components.IncubatorSliderScreen', () => gestureHandlerRootHOC(require('./IncubatorSliderScreen').default));
1112
}
+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import {StyleProp, ViewStyle, StyleSheet} from 'react-native';
2+
import {SharedValue, interpolate} from 'react-native-reanimated';
3+
import {SliderProps} from '../../components/slider';
4+
5+
export function getOffsetForValue(value: number, span: number, minimumValue = 0, maximumValue = 1) {
6+
const range = maximumValue - minimumValue;
7+
const relativeValue = minimumValue - value;
8+
const v = minimumValue < 0 ? Math.abs(relativeValue) : value - minimumValue; // for negative values
9+
const ratio = v / range;
10+
const x = ratio * span;
11+
return x;
12+
}
13+
14+
function countDecimals(value: number) {
15+
'worklet';
16+
if (Math.floor(value.valueOf()) === value.valueOf()) {
17+
return 0;
18+
}
19+
return value.toString().split('.')[1].length || 0;
20+
}
21+
22+
export function getValueForOffset(offset: number, span: number, minimum = 0, maximum = 1, step = 0) {
23+
'worklet';
24+
if (span) {
25+
const ratio = offset / span;
26+
const range = maximum - minimum;
27+
let val = ratio * range;
28+
if (step > 0) {
29+
const decimals = countDecimals(step);
30+
val = Number((Math.round((ratio * range) / step) * step).toFixed(decimals));
31+
}
32+
return Math.max(minimum, Math.min(maximum, minimum + val));
33+
}
34+
return 0;
35+
}
36+
37+
function inRange(value: number, min: number, max: number) {
38+
return value >= min && value <= max;
39+
}
40+
41+
export function validateValues(props: SliderProps) {
42+
const {useRange, value, minimumValue, maximumValue, initialMinimumValue, initialMaximumValue} = props;
43+
if (minimumValue > maximumValue ||
44+
useRange && initialMinimumValue && initialMaximumValue && initialMinimumValue > initialMaximumValue) {
45+
console.error('Your passed values are invalid. Please check if minimum values are not higher than maximum values');
46+
}
47+
if (value !== undefined && minimumValue && maximumValue && !inRange(value, minimumValue, maximumValue)) {
48+
console.error(`Your passed value (${value}) is invalid.
49+
Please check that it is in range of the minimum (${minimumValue}) and maximum (${maximumValue}) values`);
50+
}
51+
if (useRange && initialMinimumValue && initialMaximumValue) {
52+
if (!inRange(initialMinimumValue, minimumValue, maximumValue) ||
53+
!inRange(initialMaximumValue, minimumValue, maximumValue)) {
54+
console.error('Your passed values are invalid. Please check that they are in range of the minimum and maximum values');
55+
}
56+
}
57+
}
58+
59+
export function unpackStyle(style?: StyleProp<ViewStyle>) {
60+
if (style) {
61+
return JSON.parse(JSON.stringify(StyleSheet.flatten(style)));
62+
}
63+
}
64+
65+
export function getStepInterpolated(trackWidth: number, minimumValue: number, maximumValue: number, stepXValue: SharedValue<number>) {
66+
'worklet';
67+
const outputRange = [0, trackWidth];
68+
const inputRange = minimumValue < 0 ?
69+
[Math.abs(maximumValue), Math.abs(minimumValue)] : [minimumValue, maximumValue];
70+
return interpolate(stepXValue.value, inputRange, outputRange);
71+
}

0 commit comments

Comments
 (0)