UNPKG

@storybook/addon-ondevice-controls

Version:

Display storybook controls on your device.

297 lines (296 loc) 15.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SelectModal = void 0; const jsx_runtime_1 = require("react/jsx-runtime"); // NOTE This is adapted from react-native-modal-selector https://github.com/peacechen/react-native-modal-selector/blob/master/index.js const react_1 = require("react"); const react_native_1 = require("react-native"); const PADDING = 8; const BORDER_RADIUS = 5; const FONT_SIZE = 16; const HIGHLIGHT_COLOR = 'rgba(0,118,255,0.9)'; const styles = react_native_1.StyleSheet.create({ overlayStyle: { flex: 1, padding: '5%', justifyContent: 'center', backgroundColor: 'rgba(0,0,0,0.7)', }, optionContainer: { borderRadius: BORDER_RADIUS, flexShrink: 1, marginBottom: 8, padding: PADDING, backgroundColor: 'rgba(255,255,255,0.8)', }, cancelContainer: { alignSelf: 'stretch', marginTop: 8, }, selectStyle: { borderColor: '#ccc', borderWidth: 1, padding: PADDING, borderRadius: BORDER_RADIUS, }, selectTextStyle: { textAlign: 'center', color: '#333', fontSize: FONT_SIZE, }, cancelStyle: { borderRadius: BORDER_RADIUS, backgroundColor: 'rgba(255,255,255,0.8)', padding: PADDING, }, cancelTextStyle: { textAlign: 'center', color: '#333', fontSize: FONT_SIZE, }, optionStyle: { paddingVertical: PADDING, paddingHorizontal: 16, borderBottomWidth: 1, borderBottomColor: '#ccc', }, optionTextStyle: { fontSize: FONT_SIZE, color: '#333', flex: 1, textAlign: 'left', }, sectionStyle: { padding: PADDING * 2, borderBottomWidth: 1, borderBottomColor: '#ccc', }, sectionTextStyle: { textAlign: 'center', fontSize: FONT_SIZE, }, initValueTextStyle: { textAlign: 'center', fontSize: FONT_SIZE, color: '#d3d3d3', }, selectedItemIndicator: { width: 24, height: 24, alignItems: 'center', justifyContent: 'center', marginLeft: 8, }, checkmark: { fontSize: FONT_SIZE, color: HIGHLIGHT_COLOR, }, optionRow: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', }, doneContainer: { alignSelf: 'stretch', marginTop: 8, }, doneButton: { borderRadius: BORDER_RADIUS, backgroundColor: 'rgba(255,255,255,0.8)', padding: PADDING, }, doneButtonText: { textAlign: 'center', fontSize: FONT_SIZE, color: HIGHLIGHT_COLOR, }, }); let componentIndex = 0; const SelectModal = ({ data = [], onChange, onModalOpen, onModalClose, keyExtractor = (item) => String(item.key || item.id || item), labelExtractor = (item) => String(item.label || item.name || item), closeOnChange = true, initValue = '', listType = 'SCROLLVIEW', animationType = 'slide', style, selectStyle: selectStyleProp, selectTextStyle: selectTextStyleProp, optionStyle: optionStyleProp, optionTextStyle: optionTextStyleProp, optionContainerStyle, sectionStyle: sectionStyleProp, childrenContainerStyle, touchableStyle, touchableActiveOpacity = 0.2, sectionTextStyle: sectionTextStyleProp, cancelContainerStyle, cancelStyle: cancelStyleProp, cancelTextStyle: cancelTextStyleProp, overlayStyle: overlayStyleProp, initValueTextStyle: initValueTextStyleProp, cancelText = 'Cancel', disabled = false, supportedOrientations = ['portrait'], keyboardShouldPersistTaps = 'always', backdropPressToClose = true, openButtonContainerAccessible = true, listItemAccessible = true, cancelButtonAccessible = true, scrollViewAccessible = true, scrollViewAccessibilityLabel, cancelButtonAccessibilityLabel, passThruProps, selectTextPassThruProps, optionTextPassThruProps, cancelTextPassThruProps, scrollViewPassThruProps, modalOpenerHitSlop, customSelector, children, header, optionsTestIDPrefix = 'select-modal', onEndReached, multiselect = false, selectedSeparator = ', ', maxSelectedItems, selectedItemIndicatorStyle, selectedItemIndicatorColor = HIGHLIGHT_COLOR, doneText = 'Done', onDone, }) => { const [modalVisible, setModalVisible] = (0, react_1.useState)(false); const [selected, setSelected] = (0, react_1.useState)(multiselect ? (Array.isArray(initValue) ? initValue : []) : initValue || ''); const [selectedItems, setSelectedItems] = (0, react_1.useState)(() => { if (multiselect) { if (Array.isArray(initValue)) { return data.filter((item) => initValue.includes(String(keyExtractor(item)))); } return []; } // For single select, initialize with the item matching initValue const initialItem = data.find((item) => String(keyExtractor(item)) === String(initValue)); return initialItem ? [initialItem] : []; }); const modalRef = (0, react_1.useRef)(null); const selectedItemsMap = (0, react_1.useMemo)(() => { if (multiselect) { return (selectedItems || []).reduce((acc, item) => { acc[keyExtractor(item)] = true; return acc; }, {}); } else { // For single select, find the currently selected item const selectedItem = data.find((item) => labelExtractor(item) === selected); return selectedItem ? { [keyExtractor(selectedItem)]: true } : {}; } }, [selectedItems, keyExtractor, multiselect, selected, data, labelExtractor]); const open = (0, react_1.useCallback)(() => { if (!disabled) { setModalVisible(true); onModalOpen?.(); } }, [disabled, onModalOpen]); const handleLongPress = (0, react_1.useCallback)(() => { open(); }, [open]); const close = (0, react_1.useCallback)(() => { setModalVisible(false); onModalClose?.(); }, [onModalClose]); const handleDone = (0, react_1.useCallback)(() => { if (multiselect) { onChange?.(selectedItems); onDone?.(selectedItems); } close(); }, [multiselect, selectedItems, onChange, onDone, close]); const handleChange = (0, react_1.useCallback)((item) => { if (multiselect) { const itemKey = keyExtractor(item); const isSelected = selectedItemsMap[itemKey]; let newSelectedItems; if (isSelected) { newSelectedItems = selectedItems.filter((i) => keyExtractor(i) !== itemKey); } else { if (maxSelectedItems && selectedItems.length >= maxSelectedItems) { return; } newSelectedItems = [...selectedItems, item]; } setSelectedItems(newSelectedItems); setSelected(newSelectedItems.map(labelExtractor).join(selectedSeparator)); if (!closeOnChange) { onChange?.(newSelectedItems); } } else { setSelected(labelExtractor(item)); if (closeOnChange) { close(); } onChange?.(item); } }, [ multiselect, selectedItems, selectedItemsMap, keyExtractor, labelExtractor, maxSelectedItems, closeOnChange, onChange, close, selectedSeparator, ]); const renderOption = (0, react_1.useCallback)((option, isLastItem, isFirstItem) => { const optionLabel = labelExtractor(option); const optionKey = keyExtractor(option); const isSelected = selectedItemsMap[optionKey]; const content = ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [styles.optionTextStyle, optionTextStyleProp], ...optionTextPassThruProps, children: optionLabel }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [styles.selectedItemIndicator, selectedItemIndicatorStyle], children: isSelected && ((0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [styles.checkmark, { color: selectedItemIndicatorColor }], children: "\u2713" })) })] })); return ((0, jsx_runtime_1.jsx)(react_native_1.TouchableOpacity, { testID: `${optionsTestIDPrefix}-${optionLabel}`, onPress: () => handleChange(option), activeOpacity: touchableActiveOpacity, accessible: listItemAccessible, accessibilityLabel: option.accessibilityLabel, importantForAccessibility: isFirstItem ? 'yes' : 'no', ...passThruProps, children: (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [styles.optionStyle, optionStyleProp, isLastItem && { borderBottomWidth: 0 }], children: (0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.optionRow, children: content }) }) }, optionKey)); }, [ keyExtractor, labelExtractor, handleChange, touchableActiveOpacity, listItemAccessible, passThruProps, optionStyleProp, optionTextStyleProp, optionTextPassThruProps, optionsTestIDPrefix, selectedItemsMap, selectedItemIndicatorStyle, selectedItemIndicatorColor, ]); const renderSection = (0, react_1.useCallback)((section) => { const sectionLabel = labelExtractor(section); return ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: [styles.sectionStyle, sectionStyleProp], children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [styles.sectionTextStyle, sectionTextStyleProp], children: sectionLabel }) }, keyExtractor(section))); }, [keyExtractor, labelExtractor, sectionStyleProp, sectionTextStyleProp]); const renderFlatlistOption = (0, react_1.useCallback)(({ item, index }) => { if (item.section) { return renderSection(item); } return renderOption(item, index === data.length - 1, index === 0); }, [renderSection, renderOption, data.length]); const renderOptionList = (0, react_1.useCallback)(() => { const OverlayComponent = backdropPressToClose ? react_native_1.TouchableWithoutFeedback : react_native_1.View; const key = backdropPressToClose ? `modalSelector${componentIndex++}` : undefined; const overlayProps = backdropPressToClose ? { accessible: false, onPress: close, } : { style: { flex: 1 }, }; const optionsContainerStyle = { paddingHorizontal: 10, ...(scrollViewPassThruProps?.horizontal && { flexDirection: 'row' }), }; return ((0, jsx_runtime_1.jsx)(OverlayComponent, { ...overlayProps, children: (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [styles.overlayStyle, overlayStyleProp], children: [(0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [styles.optionContainer, optionContainerStyle], children: [header, listType === 'FLATLIST' ? ((0, jsx_runtime_1.jsx)(react_native_1.FlatList, { data: data, keyboardShouldPersistTaps: keyboardShouldPersistTaps, accessible: scrollViewAccessible, accessibilityLabel: scrollViewAccessibilityLabel, keyExtractor: keyExtractor, renderItem: renderFlatlistOption, onEndReached: onEndReached })) : ((0, jsx_runtime_1.jsx)(react_native_1.ScrollView, { keyboardShouldPersistTaps: keyboardShouldPersistTaps, accessible: scrollViewAccessible, accessibilityLabel: scrollViewAccessibilityLabel, ...scrollViewPassThruProps, children: (0, jsx_runtime_1.jsx)(react_native_1.View, { style: optionsContainerStyle, children: data.map((item, index) => item.section ? renderSection(item) : renderOption(item, index === data.length - 1, index === 0)) }) }))] }), multiselect && ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: [styles.doneContainer], children: (0, jsx_runtime_1.jsx)(react_native_1.TouchableOpacity, { style: styles.doneButton, onPress: handleDone, activeOpacity: touchableActiveOpacity, children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.doneButtonText, children: doneText }) }) })), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [styles.cancelContainer, cancelContainerStyle], children: (0, jsx_runtime_1.jsx)(react_native_1.TouchableOpacity, { onPress: close, activeOpacity: touchableActiveOpacity, accessible: cancelButtonAccessible, accessibilityLabel: cancelButtonAccessibilityLabel, children: (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [styles.cancelStyle, cancelStyleProp], children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [styles.cancelTextStyle, cancelTextStyleProp], ...cancelTextPassThruProps, children: cancelText }) }) }) })] }) }, key)); }, [ data, backdropPressToClose, close, scrollViewPassThruProps, overlayStyleProp, optionContainerStyle, header, listType, keyboardShouldPersistTaps, scrollViewAccessible, scrollViewAccessibilityLabel, keyExtractor, renderFlatlistOption, onEndReached, renderSection, renderOption, cancelContainerStyle, touchableActiveOpacity, cancelButtonAccessible, cancelButtonAccessibilityLabel, cancelStyleProp, cancelTextStyleProp, cancelTextPassThruProps, cancelText, multiselect, handleDone, doneText, ]); const renderChildren = (0, react_1.useCallback)(() => { if (children) { return children; } const initSelectStyle = selected === initValue ? [styles.initValueTextStyle, initValueTextStyleProp] : [styles.selectTextStyle, selectTextStyleProp]; return ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: [styles.selectStyle, selectStyleProp], children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: initSelectStyle, ...selectTextPassThruProps, children: selected }) })); }, [ children, selected, initValue, initValueTextStyleProp, selectTextStyleProp, selectStyleProp, selectTextPassThruProps, ]); return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: style, ...passThruProps, children: [(0, jsx_runtime_1.jsx)(react_native_1.Modal, { transparent: true, ref: modalRef, supportedOrientations: supportedOrientations, visible: modalVisible, onRequestClose: close, animationType: animationType, onDismiss: () => selectedItems.length > 0 && onChange?.(selectedItems), children: renderOptionList() }), customSelector || ((0, jsx_runtime_1.jsx)(react_native_1.TouchableOpacity, { hitSlop: modalOpenerHitSlop, activeOpacity: touchableActiveOpacity, style: touchableStyle, onPress: open, onLongPress: handleLongPress, disabled: disabled, accessible: openButtonContainerAccessible, children: (0, jsx_runtime_1.jsx)(react_native_1.View, { style: childrenContainerStyle, pointerEvents: "none", children: renderChildren() }) }))] })); }; exports.SelectModal = SelectModal; exports.default = exports.SelectModal;