UNPKG

react-native-ui-lib

Version:

<p align="center"> <img src="https://user-images.githubusercontent.com/1780255/105469025-56759000-5ca0-11eb-993d-3568c1fd54f4.png" height="250px" style="display:block"/> </p> <p align="center">UI Toolset & Components Library for React Native</p> <p a

251 lines (234 loc) • 8.05 kB
import _pt from "prop-types"; // TODO: Support style customization import { isFunction, isUndefined } from 'lodash'; import React, { useCallback, useRef, useMemo, useEffect, useState } from 'react'; import { FlatList, StyleSheet } from 'react-native'; import Animated, { useSharedValue, useAnimatedScrollHandler } from 'react-native-reanimated'; import { Colors, Spacings } from "../../style"; import { Constants, asBaseComponent } from "../../commons/new"; import View from "../../components/view"; import Fader, { FaderPosition } from "../../components/fader"; import Item, { ItemProps } from "./Item"; import Text from "../../components/text"; import usePresenter from "./usePresenter"; import { WheelPickerAlign } from "./types"; const AnimatedFlatList = Animated.createAnimatedComponent(FlatList); const WheelPicker = ({ items: propItems, itemHeight = 44, numberOfVisibleRows = 5, activeTextColor = Colors.primary, inactiveTextColor, textStyle, label, labelStyle, labelProps, onChange, align = WheelPickerAlign.CENTER, style, children, initialValue = 0, testID }) => { const scrollView = useRef(); const offset = useSharedValue(0); const scrollHandler = useAnimatedScrollHandler(e => { offset.value = e.contentOffset.y; }); const { height, items, index: currentIndex, getRowItemAtOffset } = usePresenter({ initialValue, items: propItems, children, itemHeight, preferredNumVisibleRows: numberOfVisibleRows }); const prevInitialValue = useRef(initialValue); const prevIndex = useRef(currentIndex); const [flatListWidth, setFlatListWidth] = useState(0); const keyExtractor = useCallback((item, index) => `${item}.${index}`, []); useEffect(() => { // This effect making sure to reset index if initialValue has changed !isUndefined(initialValue) && scrollToIndex(currentIndex, true); }, [currentIndex]); const _onChange = useCallback((value, index) => { if (prevInitialValue.current !== initialValue) { // don't invoke 'onChange' if 'initialValue' changed prevInitialValue.current = initialValue; } else { onChange?.(value, index); } }, [initialValue, onChange]); const onValueChange = useCallback(event => { const { index, value } = getRowItemAtOffset(event.nativeEvent.contentOffset.y); _onChange(value, index); }, [_onChange, getRowItemAtOffset]); const onMomentumScrollEndAndroid = index => { // handle Android bug: ScrollView does not call 'onMomentumScrollEnd' when scrolled programmatically (https://github.com/facebook/react-native/issues/26661) if (Constants.isAndroid && prevIndex.current !== index) { prevIndex.current = index; _onChange(items?.[index]?.value, index); } }; const scrollToOffset = (index, animated) => { // TODO: we should remove this split (the getNode section) in V6 and remove support for reanimated 1 //@ts-expect-error for some reason scrollToOffset isn't recognized if (isFunction(scrollView.current?.scrollToOffset)) { //@ts-expect-error scrollView.current?.scrollToOffset({ offset: index * itemHeight, animated }); } else { //@ts-expect-error scrollView.current?.getNode()?.scrollToOffset({ offset: index * itemHeight, animated }); } }; const scrollToIndex = (index, animated) => { onMomentumScrollEndAndroid(index); setTimeout(() => scrollToOffset(index, animated), 100); }; const scrollToPassedIndex = useCallback(() => { scrollToIndex(currentIndex, false); }, []); const selectItem = useCallback(index => { scrollToIndex(index, true); }, [itemHeight]); const renderItem = useCallback(({ item, index }) => { return <Item index={index} itemHeight={itemHeight} offset={offset} activeColor={activeTextColor} inactiveColor={inactiveTextColor} style={textStyle} {...item} fakeLabel={label} fakeLabelStyle={labelStyle} fakeLabelProps={labelProps} centerH={!label} onSelect={selectItem} testID={`${testID}.item_${index}`} />; }, [itemHeight]); const getItemLayout = useCallback((_data, index) => { return { length: itemHeight, offset: itemHeight * index, index }; }, [itemHeight]); const updateFlatListWidth = useCallback(width => { setFlatListWidth(width); }, []); const alignmentStyle = useMemo(() => { return align === WheelPickerAlign.RIGHT ? { alignSelf: undefined } : align === WheelPickerAlign.LEFT ? { alignSelf: 'flex-start' } : { alignSelf: 'center' }; }, [align]); const contentContainerStyle = useMemo(() => { return [{ paddingVertical: height / 2 - itemHeight / 2 }, alignmentStyle]; }, [height, itemHeight, alignmentStyle]); const labelContainerStyle = useMemo(() => { return [{ position: 'absolute', top: 0, bottom: 0 }, alignmentStyle]; }, [alignmentStyle]); const labelContainer = useMemo(() => { return (// @ts-expect-error <View style={labelContainerStyle} width={flatListWidth} pointerEvents="none"> <View style={styles.label} centerV pointerEvents="none"> <Text marginL-s2 marginR-s5 text80M {...labelProps} color={activeTextColor} style={labelStyle}> {label} </Text> </View> </View> ); }, [flatListWidth, labelContainerStyle, label, labelProps, activeTextColor, labelStyle]); const fader = useMemo(() => position => { return <Fader visible position={position} size={60} />; }, []); const separators = useMemo(() => { return <View absF centerV pointerEvents="none"> <View style={styles.separators} /> </View>; }, []); return <View testID={testID} bg-white style={style}> <View row centerH> <View flexG> <AnimatedFlatList testID={`${testID}.list`} height={height} data={items} // @ts-ignore reanimated2 keyExtractor={keyExtractor} scrollEventThrottle={100} onScroll={scrollHandler} onMomentumScrollEnd={onValueChange} showsVerticalScrollIndicator={false} onLayout={scrollToPassedIndex} // @ts-ignore ref={scrollView} // @ts-expect-error contentContainerStyle={contentContainerStyle} snapToInterval={itemHeight} decelerationRate={Constants.isAndroid ? 0.98 : 'normal'} renderItem={renderItem} getItemLayout={getItemLayout} initialScrollIndex={currentIndex} onContentSizeChange={updateFlatListWidth} /* This fixes an issue with RTL when centering flatlist content using alignSelf */ centerContent={align === 'center' && Constants.isRTL} /> </View> </View> {label && labelContainer} {fader(FaderPosition.BOTTOM)} {fader(FaderPosition.TOP)} {separators} </View>; }; WheelPicker.propTypes = { /** * Data source for WheelPicker */ items: _pt.array, /** * Describe the height of each item in the WheelPicker * default value: 44 */ itemHeight: _pt.number, /** * Describe the number of rows visible * default value: 5 */ numberOfVisibleRows: _pt.number, /** * Text color for the focused row */ activeTextColor: _pt.string, /** * Text color for other, non-focused rows */ inactiveTextColor: _pt.string, /** * Additional label on the right of the item text */ label: _pt.string, /** * Event, on active row change */ onChange: _pt.func, /** * Support passing items as children props */ children: _pt.oneOfType([_pt.element, _pt.arrayOf(_pt.element)]), testID: _pt.string }; WheelPicker.alignments = WheelPickerAlign; WheelPicker.displayName = 'Incubator.WheelPicker'; export default asBaseComponent(WheelPicker); export { ItemProps as WheelPickerItemProps }; const styles = StyleSheet.create({ separators: { borderTopWidth: 1, borderBottomWidth: 1, height: Spacings.s9, borderColor: Colors.grey60 }, label: { position: 'absolute', right: 0, top: 0, bottom: 0 } });