UNPKG

react-native-sectioned-multi-select

Version:

a multi (or single) select component with support for sub categories, search, chips.

333 lines (309 loc) 9.13 kB
import React, { Component } from 'react' import { View, TouchableOpacity, Text, Platform, UIManager, LayoutAnimation, FlatList, StyleSheet } from 'react-native' import RowSubItem from './RowSubItem' import ItemIcon from './ItemIcon' import { callIfFunction } from '../helpers' class RowItem extends Component { constructor(props) { super(props) if (Platform.OS === 'android') { UIManager.setLayoutAnimationEnabledExperimental && UIManager.setLayoutAnimationEnabledExperimental(true) } this.state = { showSubCategories: false, subToggled: null } } componentDidMount() { this._expandDropDowns() } shouldComponentUpdate(nextProps, nextState) { if (nextProps.selectedItems !== this.props.selectedItems) { if ( this.props.selectedItems.includes( this.props.item[this.props.uniqueKey] ) && !nextProps.selectedItems.includes(this.props.item[this.props.uniqueKey]) ) { return true } if ( !this.props.selectedItems.includes( this.props.item[this.props.uniqueKey] ) && nextProps.selectedItems.includes(this.props.item[this.props.uniqueKey]) ) { return true } if (this.state.subToggled !== nextState.subToggled) { return true } // propagate updates to child items // when adding/removing child items when // parent isn't selected if ( this.props.item[this.props.subKey] && this.props.item[this.props.subKey].findIndex((el) => nextProps.selectedItems.includes(el[this.props.uniqueKey]) ) !== -1 ) { return true } if ( this.props.item[this.props.subKey] && this.props.item[this.props.subKey].findIndex((el) => this.props.selectedItems.includes(el[this.props.uniqueKey]) ) !== -1 ) { return true } } if (this.props.searchTerm !== nextProps.searchTerm) { return true } if (this.state.showSubCategories !== nextState.showSubCategories) { return true } if (this.props.mergedStyles !== nextProps.mergedStyles) { return true } return false } _itemSelected = (item) => { const { uniqueKey, selectedItems } = this.props return selectedItems.includes(item[uniqueKey]) } _toggleItem = (item, hasChildren) => { this.props.toggleItem(item, hasChildren) } _toggleSubItem = (item) => { const { uniqueKey } = this.props const { subToggled } = this.state // we are only concerned about // triggering shouldComponentUpdate if (subToggled === item[uniqueKey]) { this.setState({ subToggled: false }) } else { this.setState({ subToggled: item[uniqueKey] }) } this.props.toggleItem(item, false) } _expandDropDowns = () => { const { expandDropDowns } = this.props if (expandDropDowns) { this.setState({ showSubCategories: true }) } } _toggleDropDown = () => { const { customLayoutAnimation, animateDropDowns } = this.props const animation = customLayoutAnimation || LayoutAnimation.Presets.easeInEaseOut animateDropDowns && LayoutAnimation.configureNext(animation) this.setState({ showSubCategories: !this.state.showSubCategories }) } _showSubCategoryDropDown = () => { const { showDropDowns, searchTerm } = this.props if (searchTerm.length) { return true } if (showDropDowns) { return this.state.showSubCategories } return true } _dropDownOrToggle = () => { const { readOnlyHeadings, showDropDowns, subKey, item } = this.props const hasChildren = !!(item[subKey] && item[subKey].length) if (readOnlyHeadings && item[subKey] && showDropDowns) { this._toggleDropDown() } else if (readOnlyHeadings) { } else { this._toggleItem(item, hasChildren) } } _renderSubItemFlatList = ({ item }) => ( <RowSubItem toggleSubItem={this._toggleSubItem} subItem={item} highlightedChildren={this.props.highlightedChildren} {...this.props} /> ) _renderSubSeparator = () => ( <View style={[ { flex: 1, height: StyleSheet.hairlineWidth, alignSelf: 'stretch', backgroundColor: '#dadada' }, this.props.mergedStyles.subSeparator ]} /> ) _renderSelectedIcon = () => { const { item, icons, selectedIconComponent, mergedColors, unselectedIconComponent, iconRenderer: Icon } = this.props return this._itemSelected(item) ? callIfFunction(selectedIconComponent) || ( <Icon style={{ color: mergedColors.success, paddingLeft: 10 }} {...icons.check} /> ) : callIfFunction(unselectedIconComponent) || null } render() { const { item, icons, mergedStyles, mergedColors, uniqueKey, subKey, showDropDowns, readOnlyHeadings, itemFontFamily, selectedIconOnLeft, dropDownToggleIconUpComponent, dropDownToggleIconDownComponent, itemNumberOfLines, displayKey, selectedItems, iconKey, iconRenderer: Icon, subItemsFlatListProps } = this.props const hasDropDown = item[subKey] && item[subKey].length > 0 && showDropDowns const itemSelected = this._itemSelected(item) const showSubCategoryDropDown = this._showSubCategoryDropDown() return ( <View> <View key={item[uniqueKey]} style={{ flexDirection: 'row', flex: 1, backgroundColor: mergedColors.itemBackground }} > <TouchableOpacity disabled={(readOnlyHeadings && !showDropDowns) || item.disabled} onPress={this._dropDownOrToggle} accessibilityState={{ selected: itemSelected }} accessibilityRole={readOnlyHeadings ? 'header' : 'menuitem'} style={[ { flex: 1, flexDirection: 'row', alignItems: 'center', justifyContent: 'flex-start', paddingVertical: 6 }, mergedStyles.item, itemSelected && mergedStyles.selectedItem ]} > {selectedIconOnLeft && this._renderSelectedIcon()} {iconKey && item[iconKey] && ( <ItemIcon iconRenderer={Icon} iconKey={iconKey} icon={item[iconKey]} style={mergedStyles.itemIconStyle} /> )} <Text numberOfLines={itemNumberOfLines} style={[ { flex: 1, color: item.disabled ? mergedColors.disabled : mergedColors.text }, itemFontFamily, mergedStyles.itemText, this._itemSelected(item) && mergedStyles.selectedItemText ]} > {item[displayKey]} </Text> {!selectedIconOnLeft && this._renderSelectedIcon()} </TouchableOpacity> {hasDropDown && ( <TouchableOpacity style={[ { alignItems: 'flex-end', justifyContent: 'center', paddingHorizontal: 10, backgroundColor: 'transparent' }, mergedStyles.toggleIcon ]} accessibilityState={{ expanded: showSubCategoryDropDown }} onPress={this._toggleDropDown} > {showSubCategoryDropDown ? ( <View> {callIfFunction(dropDownToggleIconUpComponent) || ( <Icon style={{ color: mergedColors.primary }} {...icons.arrowUp} /> )} </View> ) : ( <View> {callIfFunction(dropDownToggleIconDownComponent) || ( <Icon style={{ color: mergedColors.primary }} {...icons.arrowDown} /> )} </View> )} </TouchableOpacity> )} </View> {item[subKey] && showSubCategoryDropDown && ( <FlatList keyExtractor={(i) => `${i[uniqueKey]}`} data={item[subKey]} extraData={selectedItems} ItemSeparatorComponent={this._renderSubSeparator} renderItem={this._renderSubItemFlatList} initialNumToRender={20} {...subItemsFlatListProps} /> )} </View> ) } } export default RowItem