@devrue/rn-select
Version:
Custom typescript only select component for react native
251 lines • 10.1 kB
JavaScript
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