UNPKG

@devrue/rn-select

Version:

Custom typescript only select component for react native

251 lines 10.1 kB
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } import React, { useCallback, useMemo, useState } from 'react'; import SearchBox from './SearchBox'; import { Text, View } from 'react-native'; import useStyles from '../hooks/useStyles'; import SelectRow from './SelectRow'; import BottomSpacer from './BottomSpacer'; import Divider from './Divider'; import Anchor from './Anchor'; import pickBy from 'lodash/pickBy'; import { FlatList } from 'react-native'; import ListContainer from './ListContainer'; import EmptyList from './EmptyList'; import { Platform } from 'react-native'; import { MIN_WIDTH } from './common'; import { uniqBy } from 'lodash'; function extractStyleProps(obj, startPattern, endPattern) { return pickBy(obj, (_, key) => key.startsWith(startPattern) && key.endsWith(endPattern)); } export default function Select({ options, value, onChangeInput, onCreateItem, placeholder, searchPlaceholder, searchPlaceholderTextColor, listTitle, showSelectionCount = true, reverse, selectionEffectColor, optionsScrollIndicator = true, emptyOptionsPlaceholder = 'No Options', emptySearchMsg, clearable = true, disabled, searchable = true, createable, avoidBottom, renderAnchor, renderSearch, renderOption, optionDivider, statsTextStyle, optionCheckColors, emptyTextStyle, ...rest }) { const [showlist, setShowlist] = useState(false); const [search, setSearch] = useState(''); const [anchorPosition, setAnchorPosition] = useState({}); const [createdOptions, setCreatedOptions] = useState([]); const [bottomSpacerHeight, setBottomSpacerHeight] = useState(50); const styles = useStyles(({ tokens: { size } }) => ({ row: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', columnGap: 8 }, optionsFlatlist: { flex: 1, paddingHorizontal: size.sm }, optionsFlatlistContent: { flexGrow: 1 }, statsRow: { paddingHorizontal: size.sm }, optionListContainer: Platform.select({ web: { minWidth: MIN_WIDTH, ...((anchorPosition.width ?? 0) >= MIN_WIDTH && { width: anchorPosition.width }) }, default: {} }) }), [anchorPosition]); const [selectedMap, selectedOptions] = useMemo(() => { const optionsMap = new Map([...options, ...createdOptions]); const foundOptions = []; if (value) { if (Array.isArray(value)) { const foundValues = value.map(item => { return [item, optionsMap.get(item)]; }).filter(item => item[1]); foundOptions.push(...foundValues); } else { const item = optionsMap.get(value); if (item) foundOptions.push([value, item]); } } return [new Map(foundOptions), foundOptions]; }, [createdOptions, options, value]); const list = useMemo(() => uniqBy([...options, ...createdOptions], ([key]) => key).filter(([_, val]) => val.toLowerCase().includes(search.toLowerCase())), [createdOptions, options, search]); const handleSearch = useCallback(text => { setSearch(text); onChangeInput === null || onChangeInput === void 0 || onChangeInput(text); }, [onChangeInput]); const handleDismiss = useCallback(() => { handleSearch(''); setShowlist(false); }, [handleSearch]); const handleRowPress = useCallback(key => { if ('multi' in rest) { if (selectedMap.has(key)) { var _rest$onChangeValue; selectedMap.delete(key); (_rest$onChangeValue = rest.onChangeValue) === null || _rest$onChangeValue === void 0 || _rest$onChangeValue.call(rest, [...selectedMap.keys()]); } else { var _rest$onChangeValue2; (_rest$onChangeValue2 = rest.onChangeValue) === null || _rest$onChangeValue2 === void 0 || _rest$onChangeValue2.call(rest, [...selectedMap.keys(), key]); } } else { var _rest$onChangeValue3, _rest$onChangeValue4; if (selectedMap.has(key)) (_rest$onChangeValue3 = rest.onChangeValue) === null || _rest$onChangeValue3 === void 0 || _rest$onChangeValue3.call(rest, '');else (_rest$onChangeValue4 = rest.onChangeValue) === null || _rest$onChangeValue4 === void 0 || _rest$onChangeValue4.call(rest, key); handleDismiss(); } }, [handleDismiss, rest, selectedMap]); const handleRemove = useCallback(key => { var _rest$onChangeValue5; selectedMap.delete(key); if ('multi' in rest) (_rest$onChangeValue5 = rest.onChangeValue) === null || _rest$onChangeValue5 === void 0 || _rest$onChangeValue5.call(rest, [...selectedMap.keys()]); }, [rest, selectedMap]); const handleClear = useCallback(() => { var _rest$onChangeValue6, _rest$onChangeValue7; if ('multi' in rest) (_rest$onChangeValue6 = rest.onChangeValue) === null || _rest$onChangeValue6 === void 0 || _rest$onChangeValue6.call(rest, []);else (_rest$onChangeValue7 = rest.onChangeValue) === null || _rest$onChangeValue7 === void 0 || _rest$onChangeValue7.call(rest, ''); }, [rest]); const handleLaunch = useCallback(() => setShowlist(!showlist), [showlist]); const handleLayout = useCallback(rect => { const { top, left, width } = rect; setAnchorPosition({ x: left ?? 0, y: top ?? 0, width }); }, []); const handleCreateItem = useCallback(createValue => { onCreateItem === null || onCreateItem === void 0 || onCreateItem(createValue); setCreatedOptions(c => [...c, [createValue, createValue]]); handleRowPress(createValue); }, [handleRowPress, onCreateItem]); const anchorStyleProps = extractStyleProps(rest, 'select', 'Style'); const searchStyleProps = extractStyleProps(rest, 'search', 'Style'); const optionStyleProps = extractStyleProps(rest, 'option', 'Style'); const noOptions = options.length === 0 ? emptyOptionsPlaceholder : undefined; const multi = useMemo(() => 'multi' in rest ? true : false, [rest]); const renderItem = useCallback(({ item: [key, val] }) => /*#__PURE__*/React.createElement(React.Fragment, null, !renderOption && /*#__PURE__*/React.createElement(SelectRow, _extends({ value: val, onPress: () => handleRowPress(key), multi: multi, checked: selectedMap.has(key), reverse: reverse, selectionEffectColor: selectionEffectColor, optionCheckColors: optionCheckColors, role: "option", "aria-selected": selectedMap.has(key) }, optionStyleProps)), renderOption === null || renderOption === void 0 ? void 0 : renderOption({ optionKey: key, optionValue: val, isChecked: selectedMap.has(key), onPress: () => handleRowPress(key) })), [handleRowPress, multi, optionCheckColors, optionStyleProps, renderOption, reverse, selectedMap, selectionEffectColor]); const customAnchor = useMemo(() => renderAnchor === null || renderAnchor === void 0 ? void 0 : renderAnchor({ launch: handleLaunch, remove: handleRemove, clear: handleClear, setRect: handleLayout }), [handleClear, handleLaunch, handleLayout, handleRemove, renderAnchor]); return /*#__PURE__*/React.createElement(View, { role: "list" }, !renderAnchor && /*#__PURE__*/React.createElement(Anchor, _extends({ placeholder: placeholder, selected: selectedOptions ?? [], multi: multi, onPress: disabled ? null : handleLaunch, onRemove: handleRemove, onClear: handleClear, onLayout: handleLayout, disabled: disabled, clearable: clearable }, anchorStyleProps)), customAnchor, /*#__PURE__*/React.createElement(ListContainer, _extends({ visible: showlist, onRequestClose: handleDismiss, hardwareAccelerated: true, style: [styles.optionListContainer, optionStyleProps.optionListContainerStyle] }, Platform.select({ web: { animationType: 'fade', transparent: true, position: anchorPosition }, default: { animationType: 'slide' } }), { avoidBottom: avoidBottom, onOptionsOffet: setBottomSpacerHeight }), searchable && /*#__PURE__*/React.createElement(React.Fragment, null, !renderSearch && /*#__PURE__*/React.createElement(SearchBox, _extends({ autoFocus: true, onBackPress: handleDismiss, placeholder: searchPlaceholder, value: search, onChangeText: handleSearch, role: "searchbox", placeholderTextColor: searchPlaceholderTextColor }, searchStyleProps)), renderSearch === null || renderSearch === void 0 ? void 0 : renderSearch({ search, dismiss: handleDismiss, onChangeSearch: handleSearch })), /*#__PURE__*/React.createElement(View, { style: [styles.statsRow, styles.row] }, listTitle && /*#__PURE__*/React.createElement(Text, { style: statsTextStyle }, listTitle), /*#__PURE__*/React.createElement(View, { style: [styles.row] }), showSelectionCount && multi && /*#__PURE__*/React.createElement(Text, { style: statsTextStyle }, "Selections: ", selectedMap.size)), /*#__PURE__*/React.createElement(FlatList, { data: list, keyExtractor: ([key]) => key, renderItem: renderItem, showsVerticalScrollIndicator: optionsScrollIndicator, ItemSeparatorComponent: optionDivider ?? Divider, keyboardShouldPersistTaps: "handled", ListFooterComponent: /*#__PURE__*/React.createElement(BottomSpacer, { height: bottomSpacerHeight }), ListEmptyComponent: /*#__PURE__*/React.createElement(EmptyList, { textStyle: emptyTextStyle, msg: noOptions ?? emptySearchMsg, createOption: typeof createable === 'function' ? createable(() => handleCreateItem(search)) : createable ? search : undefined, onCreate: handleCreateItem }), style: [styles.optionsFlatlist, optionStyleProps.optionListStyle], contentContainerStyle: styles.optionsFlatlistContent }))); } //# sourceMappingURL=Select.js.map