@storybook/addon-ondevice-controls
Version:
Display storybook controls on your device.
297 lines (296 loc) • 15.3 kB
JavaScript
"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;