UNPKG

@duocvo/react-native-select-dropdown

Version:

This package was forked from https://github.com/AdelRedaa97/react-native-select-dropdown. Allow multiple select dropdown with search and custom styles.

225 lines (221 loc) 8.18 kB
import React, {forwardRef, useImperativeHandle} from 'react'; import {View, TouchableOpacity, FlatList} from 'react-native'; import {isExist} from './helpers/isExist'; import Input from './components/Input'; import DropdownOverlay from './components/DropdownOverlay'; import DropdownModal from './components/DropdownModal'; import DropdownWindow from './components/DropdownWindow'; import {useSelectDropdown} from './hooks/useSelectDropdown'; import {useLayoutDropdown} from './hooks/useLayoutDropdown'; import {useRefs} from './hooks/useRefs'; import {findIndexInArr} from './helpers/findIndexInArr'; const SelectDropdown = ( { data /* array */, onSelect /* function */, renderButton /* function returns React component for the dropdown button */, renderButtonMultiple /* function returns React component for the dropdown button when multiple is true */, renderItem /* function returns React component for each dropdown Item */, defaultValue /* any */, defaultValueByIndex /* integer */, disabled /* boolean */, disabledIndexes /* array of disabled items index */, disableAutoScroll /* boolean */, testID /* dropdown menu testID */, onFocus /* function */, onBlur /* function */, onScrollEndReached /* function */, ///////////////////////////// statusBarTranslucent /* boolean */, dropdownStyle /* style object for search input */, dropdownOverlayColor /* string */, showsVerticalScrollIndicator /* boolean */, ///////////////////////////// search /* boolean */, searchInputStyle /* style object for search input */, searchInputTxtColor /* text color for search input */, searchInputTxtStyle /* text style for search input */, searchPlaceHolder /* placeholder text for search input */, searchPlaceHolderColor /* text color for search input placeholder */, renderSearchInputLeftIcon /* function returns React component for search input icon */, renderSearchInputRightIcon /* function returns React component for search input icon */, onChangeSearchInputText /* function callback when the search input text changes, this will automatically disable the dropdown's interna search to be implemented manually outside the component */, multiple = false, // for multiple select autoFocusSearchInput = false, // for auto focus the search input isRemoveDiacritics = false, // remove diacritics from search input text isShowFullHeight = false, // show full dropdown height when keyboard is opened }, ref, ) => { const disabledInternalSearch = !!onChangeSearchInputText; /* ******************* hooks ******************* */ const {dropdownButtonRef, dropDownFlatlistRef} = useRefs(); const { dataArr, // selectedItem, selectedItems, selectItem, selectItems, reset, searchTxt, setSearchTxt, } = useSelectDropdown(data, defaultValueByIndex, defaultValue, disabledInternalSearch, multiple, isRemoveDiacritics); const {isVisible, setIsVisible, buttonLayout, onDropdownButtonLayout, dropdownWindowStyle, onRequestClose} = useLayoutDropdown(data, dropdownStyle, isShowFullHeight); useImperativeHandle(ref, () => ({ reset: () => { reset(); }, openDropdown: () => { openDropdown(); }, closeDropdown: () => { closeDropdown(); }, selectIndex: index => { selectItem(index); }, })); /* ******************* Methods ******************* */ const openDropdown = () => { dropdownButtonRef.current.measure(async (fx, fy, w, h, px, py) => { onDropdownButtonLayout(w, h, px, py); await new Promise(resolve => setTimeout(resolve, 200)); setIsVisible(true); onFocus && onFocus(); if (!disableAutoScroll) { scrollToSelectedItem(); } }); }; const closeDropdown = () => { setIsVisible(false); setSearchTxt(''); onBlur && onBlur(); }; const scrollToSelectedItem = () => { const indexInCurrArr = findIndexInArr(selectedItem, dataArr); setTimeout(() => { if (disableAutoScroll) { return; } if (indexInCurrArr > 1) { dropDownFlatlistRef?.current?.scrollToIndex({ index: search ? indexInCurrArr - 1 : indexInCurrArr, animated: true, }); } }, 200); }; const onSelectItem = (item, index) => { const indexInOriginalArr = findIndexInArr(item, data); onSelect && onSelect(item, indexInOriginalArr); if (multiple) { selectItems(indexInOriginalArr); } else { selectItem(indexInOriginalArr); closeDropdown(); } }; const onScrollToIndexFailed = error => { dropDownFlatlistRef.current.scrollToOffset({ offset: error.averageItemLength * error.index, animated: true, }); setTimeout(() => { if (dataArr.length !== 0 && dropDownFlatlistRef) { dropDownFlatlistRef.current.scrollToIndex({index: error.index, animated: true}); } }, 100); }; /* ******************** Render Methods ******************** */ const renderSearchView = () => { return ( search && ( <Input searchViewWidth={buttonLayout.w} value={searchTxt} valueColor={searchInputTxtColor} placeholder={searchPlaceHolder} placeholderTextColor={searchPlaceHolderColor} onChangeText={txt => { setSearchTxt(txt); disabledInternalSearch && onChangeSearchInputText(txt); }} inputStyle={searchInputStyle} inputTextStyle={searchInputTxtStyle} renderLeft={renderSearchInputLeftIcon} renderRight={renderSearchInputRightIcon} autoFocus={autoFocusSearchInput} /> ) ); }; const renderFlatlistItem = ({item, index}) => { let isSelected = false; if (multiple) { isSelected = selectedItems.some(selectedItem => selectedItem?.index === index); } else { const indexInCurrArr = findIndexInArr(selectedItem, dataArr); isSelected = index == indexInCurrArr; } let clonedElement = renderItem ? renderItem(item, index, isSelected) : <View />; let props = {...clonedElement.props}; return ( isExist(item) && ( <TouchableOpacity {...props} disabled={disabledIndexes?.includes(index)} activeOpacity={0.8} onPress={() => onSelectItem(item, index)}> {props?.children} </TouchableOpacity> ) ); }; const renderDropdown = () => { return ( isVisible && ( <DropdownModal statusBarTranslucent={statusBarTranslucent} visible={isVisible} onRequestClose={onRequestClose}> <DropdownOverlay onPress={closeDropdown} backgroundColor={dropdownOverlayColor} /> <DropdownWindow layoutStyle={dropdownWindowStyle}> <FlatList testID={testID} data={dataArr} keyExtractor={(item, index) => index.toString()} ref={dropDownFlatlistRef} renderItem={renderFlatlistItem} ListHeaderComponent={renderSearchView()} stickyHeaderIndices={search && [0]} keyboardShouldPersistTaps="always" onEndReached={() => onScrollEndReached && onScrollEndReached()} onEndReachedThreshold={0.5} showsVerticalScrollIndicator={showsVerticalScrollIndicator} onScrollToIndexFailed={onScrollToIndexFailed} /> </DropdownWindow> </DropdownModal> ) ); }; /////////////////////////////////////////////////////// let clonedElement = multiple ? ( renderButtonMultiple ? ( renderButtonMultiple(selectedItems, isVisible) ) : ( <View /> ) ) : renderButton ? ( renderButton(selectedItem, isVisible) ) : ( <View /> ); let props = {...clonedElement.props}; return ( <TouchableOpacity {...props} activeOpacity={0.8} ref={dropdownButtonRef} disabled={disabled} onPress={openDropdown}> {renderDropdown()} {props?.children} </TouchableOpacity> ); }; export default forwardRef((props, ref) => SelectDropdown(props, ref));