From 177e50c026672cc1e5181de4c4e4f925b37be0d3 Mon Sep 17 00:00:00 2001 From: nikhilSriva Date: Sun, 4 Oct 2020 11:52:53 +0530 Subject: [PATCH] Add modal view of option list --- lib/react-native-multi-select.js | 1429 ++++++++++++++++-------------- 1 file changed, 752 insertions(+), 677 deletions(-) diff --git a/lib/react-native-multi-select.js b/lib/react-native-multi-select.js index fce2b2d..396e630 100644 --- a/lib/react-native-multi-select.js +++ b/lib/react-native-multi-select.js @@ -1,592 +1,598 @@ -import React, { Component } from 'react'; +import React, {Component} from 'react'; import { - Text, - View, - TextInput, - TouchableWithoutFeedback, - TouchableOpacity, - FlatList, - UIManager, - ViewPropTypes + Text, + View, + Dimensions, + TextInput, + TouchableWithoutFeedback, + TouchableOpacity, + FlatList, + UIManager, + ViewPropTypes, } from 'react-native'; +import Modal from 'react-native-modal'; + import PropTypes from 'prop-types'; import reject from 'lodash/reject'; import find from 'lodash/find'; import get from 'lodash/get'; import Icon from 'react-native-vector-icons/MaterialCommunityIcons'; -import styles, { colorPack } from './styles'; +import styles, {colorPack} from './styles'; import nodeTypes from './helpers/nodeTypes'; // set UIManager LayoutAnimationEnabledExperimental if (UIManager.setLayoutAnimationEnabledExperimental) { - UIManager.setLayoutAnimationEnabledExperimental(true); + UIManager.setLayoutAnimationEnabledExperimental(true); } const defaultSearchIcon = ( - + ); export default class MultiSelect extends Component { - static propTypes = { - single: PropTypes.bool, - selectedItems: PropTypes.array, - items: PropTypes.array.isRequired, - uniqueKey: PropTypes.string, - tagBorderColor: PropTypes.string, - tagTextColor: PropTypes.string, - fontFamily: PropTypes.string, - tagRemoveIconColor: PropTypes.string, - onSelectedItemsChange: PropTypes.func.isRequired, - selectedItemFontFamily: PropTypes.string, - selectedItemTextColor: PropTypes.string, - itemFontFamily: PropTypes.string, - itemTextColor: PropTypes.string, - itemFontSize: PropTypes.number, - selectedItemIconColor: PropTypes.string, - searchIcon: nodeTypes, - searchInputPlaceholderText: PropTypes.string, - searchInputStyle: PropTypes.object, - selectText: PropTypes.string, - styleDropdownMenu: ViewPropTypes.style, - styleDropdownMenuSubsection: ViewPropTypes.style, - styleInputGroup: ViewPropTypes.style, - styleItemsContainer: ViewPropTypes.style, - styleListContainer: ViewPropTypes.style, - styleMainWrapper: ViewPropTypes.style, - styleRowList: ViewPropTypes.style, - styleSelectorContainer: ViewPropTypes.style, - styleTextDropdown: Text.propTypes.style, - styleTextDropdownSelected: Text.propTypes.style, - altFontFamily: PropTypes.string, - hideSubmitButton: PropTypes.bool, - hideDropdown: PropTypes.bool, - submitButtonColor: PropTypes.string, - submitButtonText: PropTypes.string, - textColor: PropTypes.string, - fontSize: PropTypes.number, - fixedHeight: PropTypes.bool, - hideTags: PropTypes.bool, - canAddItems: PropTypes.bool, - onAddItem: PropTypes.func, - onChangeInput: PropTypes.func, - displayKey: PropTypes.string, - textInputProps: PropTypes.object, - flatListProps: PropTypes.object, - filterMethod: PropTypes.string, - onClearSelector: PropTypes.func, - onToggleList: PropTypes.func, - removeSelected: PropTypes.bool - }; - - static defaultProps = { - single: false, - selectedItems: [], - uniqueKey: '_id', - tagBorderColor: colorPack.primary, - tagTextColor: colorPack.primary, - fontFamily: '', - tagRemoveIconColor: colorPack.danger, - selectedItemFontFamily: '', - selectedItemTextColor: colorPack.primary, - searchIcon: defaultSearchIcon, - itemFontFamily: '', - itemTextColor: colorPack.textPrimary, - itemFontSize: 16, - selectedItemIconColor: colorPack.primary, - searchInputPlaceholderText: 'Search', - searchInputStyle: { color: colorPack.textPrimary }, - textColor: colorPack.textPrimary, - selectText: 'Select', - altFontFamily: '', - hideSubmitButton: false, - submitButtonColor: '#CCC', - submitButtonText: 'Submit', - fontSize: 14, - fixedHeight: false, - hideTags: false, - hideDropdown: false, - onChangeInput: () => {}, - displayKey: 'name', - canAddItems: false, - onAddItem: () => {}, - onClearSelector: () => {}, - onToggleList: () => {}, - removeSelected: false - }; - - constructor(props) { - super(props); - this.state = { - selector: false, - searchTerm: '' + static propTypes = { + single: PropTypes.bool, + selectedItems: PropTypes.array, + items: PropTypes.array.isRequired, + uniqueKey: PropTypes.string, + tagBorderColor: PropTypes.string, + tagTextColor: PropTypes.string, + fontFamily: PropTypes.string, + tagRemoveIconColor: PropTypes.string, + onSelectedItemsChange: PropTypes.func.isRequired, + selectedItemFontFamily: PropTypes.string, + selectedItemTextColor: PropTypes.string, + itemFontFamily: PropTypes.string, + itemTextColor: PropTypes.string, + itemFontSize: PropTypes.number, + selectedItemIconColor: PropTypes.string, + searchIcon: nodeTypes, + searchInputPlaceholderText: PropTypes.string, + searchInputStyle: PropTypes.object, + selectText: PropTypes.string, + styleDropdownMenu: ViewPropTypes.style, + styleDropdownMenuSubsection: ViewPropTypes.style, + styleInputGroup: ViewPropTypes.style, + styleItemsContainer: ViewPropTypes.style, + styleListContainer: ViewPropTypes.style, + styleMainWrapper: ViewPropTypes.style, + styleRowList: ViewPropTypes.style, + styleSelectorContainer: ViewPropTypes.style, + styleTextDropdown: Text.propTypes.style, + styleTextDropdownSelected: Text.propTypes.style, + altFontFamily: PropTypes.string, + hideSubmitButton: PropTypes.bool, + hideDropdown: PropTypes.bool, + submitButtonColor: PropTypes.string, + submitButtonText: PropTypes.string, + textColor: PropTypes.string, + fontSize: PropTypes.number, + fixedHeight: PropTypes.bool, + hideTags: PropTypes.bool, + canAddItems: PropTypes.bool, + onAddItem: PropTypes.func, + onChangeInput: PropTypes.func, + displayKey: PropTypes.string, + textInputProps: PropTypes.object, + flatListProps: PropTypes.object, + filterMethod: PropTypes.string, + onClearSelector: PropTypes.func, + onToggleList: PropTypes.func, + removeSelected: PropTypes.bool, + }; + + static defaultProps = { + single: false, + selectedItems: [], + uniqueKey: '_id', + tagBorderColor: colorPack.primary, + tagTextColor: colorPack.primary, + fontFamily: '', + tagRemoveIconColor: colorPack.danger, + selectedItemFontFamily: '', + selectedItemTextColor: colorPack.primary, + searchIcon: defaultSearchIcon, + itemFontFamily: '', + itemTextColor: colorPack.textPrimary, + itemFontSize: 16, + selectedItemIconColor: colorPack.primary, + searchInputPlaceholderText: 'Search', + searchInputStyle: {color: colorPack.textPrimary}, + textColor: colorPack.textPrimary, + selectText: 'Select', + altFontFamily: '', + hideSubmitButton: false, + submitButtonColor: '#CCC', + submitButtonText: 'Submit', + fontSize: 14, + fixedHeight: false, + hideTags: false, + hideDropdown: false, + onChangeInput: () => { + }, + displayKey: 'name', + canAddItems: false, + onAddItem: () => { + }, + onClearSelector: () => { + }, + onToggleList: () => { + }, + removeSelected: false, }; - } - - shouldComponentUpdate() { - // console.log('Component Updating: ', nextProps.selectedItems); - return true; - } - - getSelectedItemsExt = optionalSelctedItems => ( - - {this._displaySelectedItems(optionalSelctedItems)} - - ); - - _onChangeInput = value => { - const { onChangeInput } = this.props; - if (onChangeInput) { - onChangeInput(value); - } - this.setState({ searchTerm: value }); - }; - _getSelectLabel = () => { - const { selectText, single, selectedItems, displayKey } = this.props; - if (!selectedItems || selectedItems.length === 0) { - return selectText; + constructor(props) { + super(props); + this.state = { + selector: false, + searchTerm: '', + }; } - if (single) { - const item = selectedItems[0]; - const foundItem = this._findItem(item); - return get(foundItem, displayKey) || selectText; + + shouldComponentUpdate() { + // console.log('Component Updating: ', nextProps.selectedItems); + return true; } - return `${selectText} (${selectedItems.length} selected)`; - }; - - _findItem = itemKey => { - const { items, uniqueKey } = this.props; - return find(items, singleItem => singleItem[uniqueKey] === itemKey) || {}; - }; - - _displaySelectedItems = optionalSelctedItems => { - const { - fontFamily, - tagRemoveIconColor, - tagBorderColor, - uniqueKey, - tagTextColor, - selectedItems, - displayKey - } = this.props; - const actualSelectedItems = optionalSelctedItems || selectedItems; - return actualSelectedItems.map(singleSelectedItem => { - const item = this._findItem(singleSelectedItem); - if (!item[displayKey]) return null; - return ( + + getSelectedItemsExt = optionalSelctedItems => ( - - {item[displayKey]} - - { - this._removeItem(item); + style={{ + flexDirection: 'row', + flexWrap: 'wrap', }} - > - - + > + {this._displaySelectedItems(optionalSelctedItems)} - ); - }); - }; - - _removeItem = item => { - const { uniqueKey, selectedItems, onSelectedItemsChange } = this.props; - const newItems = reject( - selectedItems, - singleItem => item[uniqueKey] === singleItem ); - // broadcast new selected items state to parent component - onSelectedItemsChange(newItems); - }; - - _removeAllItems = () => { - const { onSelectedItemsChange } = this.props; - // broadcast new selected items state to parent component - onSelectedItemsChange([]); - }; - - _clearSelector = () => { - this.setState({ - selector: false - }); - }; - - _clearSelectorCallback = () => { - const { onClearSelector } = this.props; - this._clearSelector(); - if (onClearSelector) { - onClearSelector(); - } - }; - - _toggleSelector = () => { - const { onToggleList } = this.props; - this.setState({ - selector: !this.state.selector - }); - if (onToggleList) { - onToggleList(); - } - }; - - _clearSearchTerm = () => { - this.setState({ - searchTerm: '' - }); - }; - - _submitSelection = () => { - this._toggleSelector(); - // reset searchTerm - this._clearSearchTerm(); - }; - - _itemSelected = item => { - const { uniqueKey, selectedItems } = this.props; - return selectedItems.indexOf(item[uniqueKey]) !== -1; - }; - - _addItem = () => { - const { - uniqueKey, - items, - selectedItems, - onSelectedItemsChange, - onAddItem - } = this.props; - let newItems = []; - let newSelectedItems = []; - const newItemName = this.state.searchTerm; - if (newItemName) { - const newItemId = newItemName - .split(' ') - .filter(word => word.length) - .join('-'); - newItems = [...items, { [uniqueKey]: newItemId, name: newItemName }]; - newSelectedItems = [...selectedItems, newItemId]; - onAddItem(newItems); - onSelectedItemsChange(newSelectedItems); - this._clearSearchTerm(); - } - }; - - _toggleItem = item => { - const { - single, - uniqueKey, - selectedItems, - onSelectedItemsChange - } = this.props; - if (single) { - this._submitSelection(); - onSelectedItemsChange([item[uniqueKey]]); - } else { - const status = this._itemSelected(item); - let newItems = []; - if (status) { - newItems = reject( - selectedItems, - singleItem => item[uniqueKey] === singleItem + + _onChangeInput = value => { + const {onChangeInput} = this.props; + if (onChangeInput) { + onChangeInput(value); + } + this.setState({searchTerm: value}); + }; + + _getSelectLabel = () => { + const {selectText, single, selectedItems, displayKey} = this.props; + if (!selectedItems || selectedItems.length === 0) { + return selectText; + } + if (single) { + const item = selectedItems[0]; + const foundItem = this._findItem(item); + return get(foundItem, displayKey) || selectText; + } + return `${selectText} (${selectedItems.length} selected)`; + }; + + _findItem = itemKey => { + const {items, uniqueKey} = this.props; + return find(items, singleItem => singleItem[uniqueKey] === itemKey) || {}; + }; + + _displaySelectedItems = optionalSelctedItems => { + const { + fontFamily, + tagRemoveIconColor, + tagBorderColor, + uniqueKey, + tagTextColor, + selectedItems, + displayKey, + } = this.props; + const actualSelectedItems = optionalSelctedItems || selectedItems; + return actualSelectedItems.map(singleSelectedItem => { + const item = this._findItem(singleSelectedItem); + if (!item[displayKey]) return null; + return ( + + + {item[displayKey]} + + { + this._removeItem(item); + }} + > + + + + ); + }); + }; + + _removeItem = item => { + const {uniqueKey, selectedItems, onSelectedItemsChange} = this.props; + const newItems = reject( + selectedItems, + singleItem => item[uniqueKey] === singleItem, ); - } else { - newItems = [...selectedItems, item[uniqueKey]]; - } - // broadcast new selected items state to parent component - onSelectedItemsChange(newItems); - } - }; - - _itemStyle = item => { - const { - selectedItemFontFamily, - selectedItemTextColor, - itemFontFamily, - itemTextColor, - itemFontSize - } = this.props; - const isSelected = this._itemSelected(item); - const fontFamily = {}; - if (isSelected && selectedItemFontFamily) { - fontFamily.fontFamily = selectedItemFontFamily; - } else if (!isSelected && itemFontFamily) { - fontFamily.fontFamily = itemFontFamily; - } - const color = isSelected - ? { color: selectedItemTextColor } - : { color: itemTextColor }; - return { - ...fontFamily, - ...color, - fontSize: itemFontSize + // broadcast new selected items state to parent component + onSelectedItemsChange(newItems); + }; + + _removeAllItems = () => { + const {onSelectedItemsChange} = this.props; + // broadcast new selected items state to parent component + onSelectedItemsChange([]); + }; + + _clearSelector = () => { + this.setState({ + selector: false, + }); + }; + + _clearSelectorCallback = () => { + const {onClearSelector} = this.props; + this._clearSelector(); + if (onClearSelector) { + onClearSelector(); + } }; - }; - - _getRow = item => { - const { selectedItemIconColor, displayKey, styleRowList } = this.props; - return ( - this._toggleItem(item)} - style={[ - styleRowList && styleRowList, - { paddingLeft: 20, paddingRight: 20 } - ]} - > - - - { + const {onToggleList} = this.props; + this.setState({ + selector: !this.state.selector, + }); + if (onToggleList) { + onToggleList(); + } + }; + + _clearSearchTerm = () => { + this.setState({ + searchTerm: '', + }); + }; + + _submitSelection = () => { + this._toggleSelector(); + // reset searchTerm + this._clearSearchTerm(); + }; + + _itemSelected = item => { + const {uniqueKey, selectedItems} = this.props; + return selectedItems.indexOf(item[uniqueKey]) !== -1; + }; + + _addItem = () => { + const { + uniqueKey, + items, + selectedItems, + onSelectedItemsChange, + onAddItem, + } = this.props; + let newItems = []; + let newSelectedItems = []; + const newItemName = this.state.searchTerm; + if (newItemName) { + const newItemId = newItemName + .split(' ') + .filter(word => word.length) + .join('-'); + newItems = [...items, {[uniqueKey]: newItemId, name: newItemName}]; + newSelectedItems = [...selectedItems, newItemId]; + onAddItem(newItems); + onSelectedItemsChange(newSelectedItems); + this._clearSearchTerm(); + } + }; + + _toggleItem = item => { + const { + single, + uniqueKey, + selectedItems, + onSelectedItemsChange, + } = this.props; + if (single) { + this._submitSelection(); + onSelectedItemsChange([item[uniqueKey]]); + } else { + const status = this._itemSelected(item); + let newItems = []; + if (status) { + newItems = reject( + selectedItems, + singleItem => item[uniqueKey] === singleItem, + ); + } else { + newItems = [...selectedItems, item[uniqueKey]]; + } + // broadcast new selected items state to parent component + onSelectedItemsChange(newItems); + } + }; + + _itemStyle = item => { + const { + selectedItemFontFamily, + selectedItemTextColor, + itemFontFamily, + itemTextColor, + itemFontSize, + } = this.props; + const isSelected = this._itemSelected(item); + const fontFamily = {}; + if (isSelected && selectedItemFontFamily) { + fontFamily.fontFamily = selectedItemFontFamily; + } else if (!isSelected && itemFontFamily) { + fontFamily.fontFamily = itemFontFamily; + } + const color = isSelected + ? {color: selectedItemTextColor} + : {color: itemTextColor}; + return { + ...fontFamily, + ...color, + fontSize: itemFontSize, + }; + }; + + _getRow = item => { + const {selectedItemIconColor, displayKey, styleRowList} = this.props; + return ( + this._toggleItem(item)} + style={[ + styleRowList && styleRowList, + {paddingLeft: 20, paddingRight: 20}, + ]} > - {item[displayKey]} - - {this._itemSelected(item) ? ( - - ) : null} - - - - ); - }; - - _getRowNew = item => ( - this._addItem(item)} - style={{ paddingLeft: 20, paddingRight: 20 }} - > - - - - Add {item.name} (tap or press return) - - - - - ); - - _filterItems = searchTerm => { - switch (this.props.filterMethod) { - case 'full': - return this._filterItemsFull(searchTerm); - default: - return this._filterItemsPartial(searchTerm); - } - }; - - _filterItemsPartial = searchTerm => { - const { items, displayKey } = this.props; - const filteredItems = []; - items.forEach(item => { - const parts = searchTerm.trim().split(/[ \-:]+/); - const regex = new RegExp(`(${parts.join('|')})`, 'ig'); - if (regex.test(get(item, displayKey))) { - filteredItems.push(item); - } - }); - return filteredItems; - }; - - _filterItemsFull = searchTerm => { - const { items, displayKey } = this.props; - const filteredItems = []; - items.forEach(item => { - if ( - item[displayKey] - .toLowerCase() - .indexOf(searchTerm.trim().toLowerCase()) >= 0 - ) { - filteredItems.push(item); - } - }); - return filteredItems; - }; - - _renderItems = () => { - const { - canAddItems, - items, - fontFamily, - uniqueKey, - selectedItems, - flatListProps, - styleListContainer, - removeSelected - } = this.props; - const { searchTerm } = this.state; - let component = null; - // If searchTerm matches an item in the list, we should not add a new - // element to the list. - let searchTermMatch; - let itemList; - let addItemRow; - let renderItems = searchTerm ? this._filterItems(searchTerm) : items; - // Filtering already selected items - if (removeSelected) { - renderItems = renderItems.filter( - item => !selectedItems.includes(item[uniqueKey]) - ); - } - if (renderItems.length) { - itemList = ( - item[uniqueKey]} - listKey={item => item[uniqueKey]} - renderItem={rowData => this._getRow(rowData.item)} - {...flatListProps} - nestedScrollEnabled - /> - ); - searchTermMatch = renderItems.filter(item => item.name === searchTerm) - .length; - } else if (!canAddItems) { - itemList = ( - - - No item to display. - - - ); - } + + + + {item[displayKey]} + + {this._itemSelected(item) ? ( + + ) : null} + + + + ); + }; - if (canAddItems && !searchTermMatch && searchTerm.length) { - addItemRow = this._getRowNew({ name: searchTerm }); - } - component = ( - - {itemList} - {addItemRow} - + _getRowNew = item => ( + this._addItem(item)} + style={{paddingLeft: 20, paddingRight: 20}} + > + + + + Add {item.name} (tap or press return) + + + + ); - return component; - }; - - render() { - const { - selectedItems, - single, - fontFamily, - altFontFamily, - searchInputPlaceholderText, - searchInputStyle, - styleDropdownMenu, - styleDropdownMenuSubsection, - hideSubmitButton, - hideDropdown, - submitButtonColor, - submitButtonText, - fontSize, - textColor, - fixedHeight, - hideTags, - textInputProps, - styleMainWrapper, - styleInputGroup, - styleItemsContainer, - styleSelectorContainer, - styleTextDropdown, - styleTextDropdownSelected, - searchIcon - } = this.props; - const { searchTerm, selector } = this.state; - return ( - - {selector ? ( - + + _filterItems = searchTerm => { + switch (this.props.filterMethod) { + case 'full': + return this._filterItemsFull(searchTerm); + default: + return this._filterItemsPartial(searchTerm); + } + }; + + _filterItemsPartial = searchTerm => { + const {items, displayKey} = this.props; + const filteredItems = []; + items.forEach(item => { + const parts = searchTerm.trim().split(/[ \-:]+/); + const regex = new RegExp(`(${parts.join('|')})`, 'ig'); + if (regex.test(get(item, displayKey))) { + filteredItems.push(item); + } + }); + return filteredItems; + }; + + _filterItemsFull = searchTerm => { + const {items, displayKey} = this.props; + const filteredItems = []; + items.forEach(item => { + if ( + item[displayKey] + .toLowerCase() + .indexOf(searchTerm.trim().toLowerCase()) >= 0 + ) { + filteredItems.push(item); + } + }); + return filteredItems; + }; + + _renderItems = () => { + const { + canAddItems, + items, + fontFamily, + uniqueKey, + selectedItems, + flatListProps, + styleListContainer, + removeSelected, + } = this.props; + const {searchTerm} = this.state; + let component = null; + // If searchTerm matches an item in the list, we should not add a new + // element to the list. + let searchTermMatch; + let itemList; + let addItemRow; + let renderItems = searchTerm ? this._filterItems(searchTerm) : items; + // Filtering already selected items + if (removeSelected) { + renderItems = renderItems.filter( + item => !selectedItems.includes(item[uniqueKey]), + ); + } + if (renderItems.length) { + itemList = ( + item[uniqueKey]} + listKey={item => item[uniqueKey]} + renderItem={rowData => this._getRow(rowData.item)} + {...flatListProps} + /> + ); + searchTermMatch = renderItems.filter(item => item.name === searchTerm) + .length; + } else if (!canAddItems) { + itemList = ( + + + No item to display. + + + ); + } + + if (canAddItems && !searchTermMatch && searchTerm.length) { + addItemRow = this._getRowNew({name: searchTerm}); + } + component = ( + + {itemList} + {addItemRow} + + ); + return component; + }; + + render() { + const { + selectedItems, + single, + fontFamily, + altFontFamily, + searchInputPlaceholderText, + searchInputStyle, + styleDropdownMenu, + styleDropdownMenuSubsection, + hideSubmitButton, + hideDropdown, + submitButtonColor, + submitButtonText, + fontSize, + textColor, + fixedHeight, + hideTags, + textInputProps, + styleMainWrapper, + styleInputGroup, + styleItemsContainer, + styleSelectorContainer, + styleTextDropdown, + styleTextDropdownSelected, + searchIcon, + } = this.props; + const {searchTerm, selector} = this.state; + return ( - {searchIcon} + {selector ? ( + + + {/*{searchIcon} - {hideSubmitButton && ( - - - - )} - {!hideDropdown && ( + />*/} + + {/*{!hideDropdown && ( - )} - - - - {this._renderItems()} - - {!single && !hideSubmitButton && ( - this._submitSelection()} - style={[ - styles.button, - { backgroundColor: submitButtonColor } - ]} - > - - {submitButtonText} - - - )} - - - ) : ( - - - - - - - {this._getSelectLabel()} - - - - - + )}*/} + + {!hideSubmitButton && ( + + + + + + {this._getSelectLabel()} + + + + + + + )} + { + this.setState({selector: false}); + }} + isVisible={selector}> + + {this._renderItems()} + + + {!single && !hideSubmitButton && ( + this._submitSelection()} + style={[ + styles.button, + {backgroundColor: submitButtonColor}, + ]} + > + + {submitButtonText} + + + )} + + + + ) : ( + + + + + + + {this._getSelectLabel()} + + + + + + + {!single && !hideTags && selectedItems.length ? ( + + {this._displaySelectedItems()} + + ) : null} + + )} - {!single && !hideTags && selectedItems.length ? ( - - {this._displaySelectedItems()} - - ) : null} - - )} - - ); - } + ); + } }