import React, { useMemo, useCallback, memo } from 'react'; import isNil from 'lodash-es/isNil'; import toString from 'lodash-es/toString'; import { View, TextInput, StyleSheet, TextStyle, GestureResponderEvent } from 'react-native'; import { useThemeFactory } from '../Theme'; import { useRefState } from '../hooks'; import { formatNumber } from '../utils/number'; import type { StepperProps } from './type'; import { createStyle } from './style'; import StepButton from './StepButton'; const Stepper = (props: StepperProps): JSX.Element => { const { theme = 'default', max = Number.MAX_VALUE, min = 1, step = 1, defaultValue = 1, showPlus = true, showMinus = true, showInput = true, longPress = true, allowEmpty, decimalLength, buttonSize, inputWidth, integer = false, ...rest } = props; const format = useCallback( (value: string | number) => { if (allowEmpty && value === '') { return value; } value = formatNumber(String(value), !integer); value = value === '' ? 0 : +value; value = isNaN(value) ? min : value; value = Math.max(Math.min(max, value), min); // format decimal if (!isNil(decimalLength)) { value = value.toFixed(+decimalLength); } return value; }, [min, max, allowEmpty, decimalLength, integer] ); const getInitialValue = () => { const _defaultValue = props.value ?? defaultValue; return Number(format(_defaultValue)); }; const [current, setCurrent, currentRef] = useRefState(() => getInitialValue() ); const minusDisabled = useMemo( () => props.disabled || props.disableMinus || (current ?? 0) <= min, [props.disabled, props.disableMinus, min, current] ); const plusDisabled = useMemo( () => props.disabled || props.disablePlus || (current ?? 0) >= max, [props.disabled, props.disablePlus, max, current] ); const innerChange = useCallback( async (value?: number) => { const couldChange = await Promise.resolve(props.beforeChange?.(value) ?? true); if (couldChange) { setCurrent(value); props.onChange?.(value); } }, [props.onChange, props.beforeChange] ); const handleMinus = (event: GestureResponderEvent) => { const value = Number(format((currentRef.current ?? 0) - step)); innerChange(value); props.onMinus?.(event, value); }; const handlePlus = (event: GestureResponderEvent) => { const value = Number(format((currentRef.current ?? 0) + step)); innerChange(value); props.onPlus?.(event, value); }; const handleInput = (text: string) => { if (text === '') { innerChange(undefined); } else { let formatted = formatNumber(text, !integer); // limit max decimal length if (!isNil(decimalLength) && formatted.includes('.')) { const pair = formatted.split('.'); formatted = `${pair[0]}.${pair[1].slice(0, +decimalLength)}`; } innerChange(Number(formatted)); } }; const { styles, theme: globalTheme } = useThemeFactory(createStyle, { isRound: theme === 'round', buttonSize, inputWidth, minusDisabled, plusDisabled, }); const inputColor = StyleSheet.flatten(styles.input).color; return ( true}> {showMinus && ( )} {showInput && ( )} {showPlus && ( )} ); }; export default memo(Stepper);