Skip to content

Commit 608a843

Browse files
committed
refactor SimpleStepper to use Flatlist
1 parent f7e0cf0 commit 608a843

6 files changed

Lines changed: 1117 additions & 4212 deletions

File tree

SimpleStepper.tsx

Lines changed: 139 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,11 @@ import {
88
TextStyle,
99
ViewStyle,
1010
ImageStyle,
11-
ImageSourcePropType,
1211
ColorValue,
12+
FlatList,
13+
ImageSourcePropType,
1314
} from 'react-native';
1415

15-
type HasReachedMaxMin = {
16-
hasReachedMax: boolean;
17-
hasReachedMin: boolean;
18-
};
19-
2016
export type SimpleStepperProps = {
2117
initialValue?: number;
2218
minimumValue?: number;
@@ -64,8 +60,15 @@ export type SimpleStepperProps = {
6460
decrementImageTestID?: string;
6561
incrementButtonTestID?: string;
6662
decrementButtonTestID?: string;
63+
textTestID?: string;
64+
horizontal?: boolean;
6765
};
6866

67+
interface SimpleStepperListItem {
68+
item: React.JSX.Element;
69+
index: number;
70+
}
71+
6972
const SimpleStepper: React.FunctionComponent<SimpleStepperProps> = ({
7073
initialValue = 0,
7174
minimumValue = 0,
@@ -89,26 +92,26 @@ const SimpleStepper: React.FunctionComponent<SimpleStepperProps> = ({
8992
showText = false,
9093
renderText = undefined,
9194
textStyle = {
92-
marginHorizontal: 8,
95+
textAlign: 'center',
96+
padding: 8,
9397
fontSize: 24,
9498
},
9599
containerStyle = {
96-
flexDirection: 'row',
100+
alignSelf: 'flex-start',
97101
borderWidth: 1,
98102
borderRadius: 8,
99-
alignItems: 'center',
100-
justifyContent: 'space-evenly',
101103
},
102104
separatorStyle = {
103-
width: StyleSheet.hairlineWidth,
104105
backgroundColor: 'black',
105-
height: '100%',
106+
width: StyleSheet.hairlineWidth,
106107
},
107108
incrementStepStyle = {
108-
padding: 4,
109+
alignItems: 'center',
110+
padding: 8,
109111
},
110112
decrementStepStyle = {
111-
padding: 4,
113+
alignItems: 'center',
114+
padding: 8,
112115
},
113116
incrementImageStyle = {
114117
height: 30,
@@ -122,16 +125,18 @@ const SimpleStepper: React.FunctionComponent<SimpleStepperProps> = ({
122125
disableIncrementImageTintColor = false,
123126
disableDecrementImageTintColor = false,
124127
useColor = false,
125-
color = 'blue',
128+
color = undefined,
126129
textDecimalPlaces = 2,
127130
containerTestID = 'container',
128131
separatorTestID = 'separator',
129132
incrementImageTestID = 'incrementImage',
130133
decrementImageTestID = 'decrementImage',
131134
incrementButtonTestID = 'incrementButton',
132135
decrementButtonTestID = 'decrementButton',
136+
textTestID = 'text',
137+
horizontal = true,
133138
}) => {
134-
const [value, setValue] = React.useState(initialValue);
139+
const [value, setValue] = React.useState<number>(initialValue);
135140

136141
function _decrementAction(): void {
137142
const nextValue = value - stepValue;
@@ -162,59 +167,62 @@ const SimpleStepper: React.FunctionComponent<SimpleStepperProps> = ({
162167
return actualValue;
163168
}
164169

165-
function _getHasMinMax(): HasReachedMaxMin {
166-
let hasReachedMax = true;
167-
let hasReachedMin = true;
168-
switch (true) {
169-
case wraps:
170-
hasReachedMin = false;
171-
hasReachedMax = false;
172-
break;
173-
case stepValue >= 0:
174-
hasReachedMax = value >= maximumValue;
175-
hasReachedMin = value <= minimumValue;
176-
break;
177-
case stepValue < 0:
178-
hasReachedMax = value <= minimumValue;
179-
hasReachedMin = value >= maximumValue;
180-
break;
170+
function hasReachedMax(): boolean {
171+
if (stepValue < 0) {
172+
return value <= minimumValue;
173+
}
174+
return value >= maximumValue;
175+
}
176+
177+
function hasReachedMin(): boolean {
178+
if (stepValue < 0) {
179+
return value >= maximumValue;
181180
}
182-
return {
183-
hasReachedMax,
184-
hasReachedMin,
185-
};
181+
return value <= minimumValue;
182+
}
183+
184+
if (hasReachedMin()) {
185+
onMin(value);
186+
}
187+
if (hasReachedMax()) {
188+
onMax(value);
186189
}
187190

188191
function _renderText(): React.JSX.Element {
189192
if (renderText) {
190193
return renderText(value);
191194
}
192-
const _textStyle = textStyle;
193-
if (useColor && color) {
194-
_textStyle.color = color;
195-
}
196195
let displayValue = value;
197196
if (!Number.isInteger(value)) {
198197
displayValue = Number(value.toFixed(textDecimalPlaces));
199198
}
200-
return <Text style={textStyle}>{displayValue}</Text>;
199+
return (
200+
<Text
201+
testID={textTestID}
202+
style={[textStyle, { color: useColor ? color : textStyle.color }]}
203+
disabled={disabled}
204+
>
205+
{displayValue}
206+
</Text>
207+
);
201208
}
202209

203210
function _renderIncrementImage(opacity: number): React.JSX.Element {
204211
if (renderIncrementImage) {
205212
return renderIncrementImage(opacity);
206213
}
207-
const _incrementImageStyle = incrementImageStyle;
208-
if (useColor && color && !disableIncrementImageTintColor) {
209-
_incrementImageStyle.tintColor = color;
210-
}
211-
if (disableIncrementImageTintColor && _incrementImageStyle.tintColor) {
214+
const _incrementImageStyle = Object.assign({}, incrementImageStyle);
215+
_incrementImageStyle.opacity = opacity;
216+
217+
if (disableIncrementImageTintColor) {
212218
_incrementImageStyle.tintColor = undefined;
219+
} else if (useColor) {
220+
_incrementImageStyle.tintColor = color;
213221
}
214222
return (
215223
<Image
216224
testID={incrementImageTestID}
217-
style={[_incrementImageStyle, { opacity }]}
225+
style={_incrementImageStyle}
218226
source={incrementImage}
219227
/>
220228
);
@@ -224,81 +232,107 @@ const SimpleStepper: React.FunctionComponent<SimpleStepperProps> = ({
224232
if (renderDecrementImage) {
225233
return renderDecrementImage(opacity);
226234
}
227-
const _decrementImageStyle = decrementImageStyle;
228-
if (useColor && color && !disableDecrementImageTintColor) {
229-
_decrementImageStyle.tintColor = color;
230-
}
231-
if (disableDecrementImageTintColor && _decrementImageStyle.tintColor) {
235+
const _decrementImageStyle = Object.assign({}, decrementImageStyle);
236+
_decrementImageStyle.opacity = opacity;
237+
238+
if (disableDecrementImageTintColor) {
232239
_decrementImageStyle.tintColor = undefined;
240+
} else if (useColor) {
241+
_decrementImageStyle.tintColor = color;
233242
}
234243
return (
235244
<Image
236245
testID={decrementImageTestID}
237-
style={[_decrementImageStyle, { opacity }]}
246+
style={_decrementImageStyle}
238247
source={decrementImage}
239248
/>
240249
);
241250
}
242251

243-
const { hasReachedMin, hasReachedMax } = _getHasMinMax();
244-
const decrementOpacity = hasReachedMin || disabled ? disabledOpacity : 1;
245-
const incrementOpacity = hasReachedMax || disabled ? disabledOpacity : 1;
246-
const isLeft = showText && textPosition === 'left';
247-
const isCenter = showText && textPosition === 'center';
248-
const isRight = showText && textPosition === 'right';
252+
function _renderIncrement(): React.JSX.Element {
253+
if (renderIncrementStep) {
254+
return renderIncrementStep(value, _incrementAction);
255+
}
256+
const isDisabled = disabled || (!wraps && hasReachedMax());
257+
const opacity = isDisabled ? disabledOpacity : 1;
258+
return (
259+
<TouchableOpacity
260+
testID={incrementButtonTestID}
261+
style={incrementStepStyle}
262+
activeOpacity={activeOpacity}
263+
onPress={_incrementAction}
264+
disabled={isDisabled}
265+
>
266+
{_renderIncrementImage(opacity)}
267+
</TouchableOpacity>
268+
);
269+
}
249270

250-
if (hasReachedMin) {
251-
onMin(value);
271+
function _renderDecrement(): React.JSX.Element {
272+
if (renderDecrementStep) {
273+
return renderDecrementStep(value, _decrementAction);
274+
}
275+
const isDisabled = disabled || (!wraps && hasReachedMin());
276+
const opacity = isDisabled ? disabledOpacity : 1;
277+
return (
278+
<TouchableOpacity
279+
testID={decrementButtonTestID}
280+
style={decrementStepStyle}
281+
activeOpacity={activeOpacity}
282+
onPress={_decrementAction}
283+
disabled={isDisabled}
284+
>
285+
{_renderDecrementImage(opacity)}
286+
</TouchableOpacity>
287+
);
252288
}
253-
if (hasReachedMax) {
254-
onMax(value);
289+
290+
function _renderItem(obj: SimpleStepperListItem): React.JSX.Element {
291+
return obj.item;
255292
}
256293

257-
const _containerStyle = containerStyle;
258-
const _separatorStyle = separatorStyle;
259-
if (useColor && color) {
260-
_containerStyle.borderColor = color;
261-
_separatorStyle.backgroundColor = color;
294+
function _renderSeparator(): React.JSX.Element {
295+
const _separatorStyle = Object.assign({}, separatorStyle);
296+
if (useColor && color) {
297+
_separatorStyle.backgroundColor = color;
298+
}
299+
if (!horizontal) {
300+
_separatorStyle.height = StyleSheet.hairlineWidth;
301+
_separatorStyle.width = undefined;
302+
}
303+
return <View testID={separatorTestID} style={_separatorStyle} />;
304+
}
305+
306+
function _getData(): React.JSX.Element[] {
307+
if (!showText) {
308+
return [_renderDecrement(), _renderIncrement()];
309+
}
310+
switch (textPosition) {
311+
case 'left':
312+
return [_renderText(), _renderDecrement(), _renderIncrement()];
313+
case 'center':
314+
return [_renderDecrement(), _renderText(), _renderIncrement()];
315+
case 'right':
316+
return [_renderDecrement(), _renderIncrement(), _renderText()];
317+
}
262318
}
263319

320+
const data = _getData();
264321
return (
265-
<View>
266-
<View testID={containerTestID} style={_containerStyle}>
267-
{isLeft && _renderText()}
268-
{isLeft && <View testID={separatorTestID} style={_separatorStyle} />}
269-
{renderDecrementStep ? (
270-
renderDecrementStep(value, _decrementAction)
271-
) : (
272-
<TouchableOpacity
273-
testID={decrementButtonTestID}
274-
style={decrementStepStyle}
275-
activeOpacity={activeOpacity}
276-
onPress={_decrementAction}
277-
disabled={hasReachedMin || disabled}
278-
>
279-
{_renderDecrementImage(decrementOpacity)}
280-
</TouchableOpacity>
281-
)}
282-
{isCenter && <View testID={separatorTestID} style={_separatorStyle} />}
283-
{isCenter && _renderText()}
284-
<View style={_separatorStyle} />
285-
{renderIncrementStep ? (
286-
renderIncrementStep(value, _incrementAction)
287-
) : (
288-
<TouchableOpacity
289-
testID={incrementButtonTestID}
290-
style={incrementStepStyle}
291-
activeOpacity={activeOpacity}
292-
onPress={_incrementAction}
293-
disabled={hasReachedMax || disabled}
294-
>
295-
{_renderIncrementImage(incrementOpacity)}
296-
</TouchableOpacity>
297-
)}
298-
{isRight && <View testID={separatorTestID} style={_separatorStyle} />}
299-
{isRight && _renderText()}
300-
</View>
301-
</View>
322+
<FlatList
323+
style={[
324+
containerStyle,
325+
{ borderColor: useColor ? color : containerStyle.borderColor },
326+
]}
327+
testID={containerTestID}
328+
data={data}
329+
keyExtractor={(_item, index) => `${index}`}
330+
initialNumToRender={data.length}
331+
renderItem={_renderItem}
332+
ItemSeparatorComponent={_renderSeparator}
333+
horizontal={horizontal}
334+
scrollEnabled={false}
335+
/>
302336
);
303337
};
304338

0 commit comments

Comments
 (0)