import * as React from "react"; import { Text, View, Image, Easing, Animated, ViewStyle, TextStyle, TextInput, ScrollView, ImageStyle, LayoutAnimation, TouchableOpacity, TouchableHighlight, ColorValue, StyleProp, } from "react-native"; import Spinner, { SpinnerType } from "react-native-spinkit"; /** * ? Local Imports */ import Icon from "./components/Icon"; import styles, { _imageStyle, _menuBarContainer, _menuItemContainer, _menuItemTextStyle, _menuButtonContainer, _placeholderTextStyle, } from "./RNSingleSelect.style"; import { ThemeColors, DARK, LIGHT } from "./theme"; export interface ISingleSelectDataType { id: number; value: string; imageSource?: any; data?: any; } interface IProps { width?: number; height?: number; darkMode?: boolean; imageWidth?: number; TextComponent?: any; imageHeight?: number; placeholder?: string; ImageComponent?: any; value?: string; spinnerSize?: number; spinnerColor?: string; searchEnabled?: boolean; disableAbsolute?: boolean; placeholderTextStyle?: any; animatedBorderRadius?: number; placeholderTextColor?: string; nestedScrollEnabled?: boolean; menuBarContainerWidth?: number; menuBarContainerHeight?: number; disableFilterAnimation?: boolean; underlayColor?: string | ColorValue; arrowImageStyle?: StyleProp; menuItemTextStyle?: StyleProp; buttonContainerStyle?: StyleProp; menuBarContainerStyle?: StyleProp; data?: Array; initialValue?: ISingleSelectDataType; spinnerType?: SpinnerType; onTextChange?: (text: string) => void; onSelect: (selectedItem: ISingleSelectDataType) => void; } const RNSingleSelect = (props: IProps) => { let iconRef: any = undefined; const { data, width, height, value, darkMode, onSelect, placeholder, onTextChange, arrowImageStyle, animatedBorderRadius, placeholderTextColor, buttonContainerStyle, placeholderTextStyle, menuBarContainerStyle, menuBarContainerHeight = 150, menuBarContainerWidth = 250, disableAbsolute = false, ImageComponent = Image, TextComponent = Text, disableFilterAnimation = false, spinnerType = "ThreeBounce", spinnerSize = 30, spinnerColor, initialValue = null, searchEnabled = true, nestedScrollEnabled = false } = props; const [ selectedItem, setSelectedItem, ] = React.useState(initialValue); const [placeholderText, setPlaceholderText] = React.useState< string | undefined >(placeholder); const [menuToggled, setMenuToggled] = React.useState(false); const [dataBackup, setDataBackup] = React.useState< Array | undefined >(data); const [dataSource, setDataSource] = React.useState< Array | undefined >(data); const [borderRadiusAnimation, setBorderRadiusAnimation] = React.useState< Animated.Value >(new Animated.Value(animatedBorderRadius || 16)); const [ menuBarYTranslateAnimation, setMenuBarYTranslateAnimation, ] = React.useState(new Animated.Value(0)); const [theme, setTheme] = React.useState(DARK); React.useEffect(() => { if (darkMode) setTheme(DARK); else setTheme(LIGHT); }, []); React.useEffect(() => { setDataSource(data); setDataBackup(data); setSelectedItem(initialValue); setPlaceholderText(placeholder); }, [data]); const animateBorderRadius = () => { Animated.timing(borderRadiusAnimation, { toValue: menuToggled ? animatedBorderRadius || 16 : 0, duration: 1250, easing: Easing.bounce, useNativeDriver: true, }).start(); }; const animateSelectionBar = () => { Animated.timing(menuBarYTranslateAnimation, { toValue: menuToggled ? 0 : 100, duration: 250, easing: Easing.ease, useNativeDriver: true, }).start(); }; const handleOnToggleMenuBar = (isMenuToggled?: boolean) => { iconRef?.onPressAnimation(); animateBorderRadius(); animateSelectionBar(); setMenuToggled(isMenuToggled ? isMenuToggled : !menuToggled); }; const handleOnSelectItem = (item: ISingleSelectDataType) => { handleOnFilter(""); setSelectedItem(item); handleOnToggleMenuBar(); onSelect && onSelect(item); }; const triggerFilterAnimation = () => { LayoutAnimation.configureNext({ duration: 1000, create: { type: LayoutAnimation.Types.spring, property: LayoutAnimation.Properties.opacity, springDamping: 1, }, delete: { type: LayoutAnimation.Types.spring, property: LayoutAnimation.Properties.opacity, springDamping: 1, }, }); }; const handleOnFilter = (text: string) => { let newData = dataBackup; newData = dataBackup?.filter((item) => { const itemData = item.value.toLowerCase(); const textData = text.toLowerCase(); return itemData.indexOf(textData) > -1; }); const id = newData?.find((item) => item.value === text)?.id || dataBackup![0].id !disableFilterAnimation && triggerFilterAnimation(); setDataSource(newData); setSelectedItem({ id, value: text }); setDataSource(newData); }; /* -------------------------------------------------------------------------- */ /* Render Methods */ /* -------------------------------------------------------------------------- */ const renderTextInput = () => ( handleOnToggleMenuBar(false)} onChangeText={(text: string) => { if (text.length === 0) handleOnFilter(""); else handleOnFilter(text); onTextChange && onTextChange(text); }} > {selectedItem?.value} ); const renderReadOnlyMode = () => ( {selectedItem ? selectedItem?.value : placeholderText || "Select"} ); const renderSingleSelectButton = () => { return ( { handleOnToggleMenuBar(); }} > {searchEnabled ? renderTextInput() : renderReadOnlyMode()} (iconRef = ref)} style={[styles.arrowImageStyle, arrowImageStyle]} {...props} /> ); }; const renderMenuItem = (index: number, menuItem: any) => { const { id, value, imageSource } = menuItem; const { data, imageWidth, imageHeight, menuItemTextStyle, underlayColor, } = props; return ( { handleOnSelectItem(menuItem); }} > {imageSource && ( )} {value} ); }; const renderSpinner = () => ( 0)} /> ); const renderList = () => ( {dataSource && dataSource.map((item: any, index: number) => { return renderMenuItem(index, item); })} ); const renderMenuBar = () => { const rotate = menuBarYTranslateAnimation.interpolate({ inputRange: [0, 25, 50, 75, 100], outputRange: [0, 0.5, 0.75, 0.9, 1], }); return ( {dataSource && dataSource.length > 0 ? renderList() : renderSpinner()} ); }; return ( {renderSingleSelectButton()} {renderMenuBar()} ); }; export default RNSingleSelect;