@storybook/addon-ondevice-controls
Version:
Display storybook controls on your device.
147 lines (146 loc) • 6.78 kB
JavaScript
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { styled } from '@storybook/react-native-theming';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { ScrollView, StyleSheet, TouchableOpacity, TouchableWithoutFeedback, useWindowDimensions, View, } from 'react-native';
import { ModalPortal } from './ModalPortal';
const styles = StyleSheet.create({
overlay: {
flex: 1,
padding: '5%',
justifyContent: 'center',
backgroundColor: 'rgba(0,0,0,0.7)',
},
scrollContent: {
padding: 8,
paddingHorizontal: 10,
},
option: {
minHeight: 34,
paddingVertical: 8,
paddingHorizontal: 16,
borderBottomWidth: 1,
},
lastOption: {
borderBottomWidth: 0,
},
optionContent: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
},
optionText: {
flex: 1,
textAlign: 'left',
},
indicator: {
width: 24,
height: 24,
alignItems: 'center',
justifyContent: 'center',
marginLeft: 8,
},
checkmark: {
fontSize: 16,
},
footer: {
padding: 8,
},
doneFooter: {
paddingBottom: 4,
},
cancelFooter: {
paddingTop: 4,
},
buttonText: {
textAlign: 'center',
},
triggerContent: {
pointerEvents: 'none',
},
});
const Dialog = styled.View(({ theme, maxHeight }) => ({
maxHeight,
marginHorizontal: 24,
backgroundColor: theme.background.content,
borderColor: theme.appBorderColor,
borderWidth: 1,
borderRadius: 8,
overflow: 'hidden',
boxShadow: `0px 8px 24px 0px ${theme.color.border}`,
elevation: 10,
}));
const OptionButton = styled.View(({ theme, selected }) => ({
borderBottomColor: theme.appBorderColor,
backgroundColor: selected ? theme.color.secondary : undefined,
}));
const OptionLabel = styled.Text(({ theme, selected }) => ({
color: selected ? theme.color.lightest : theme.color.defaultText,
fontSize: theme.typography.size.s2,
fontWeight: selected ? theme.typography.weight.bold : 'normal',
}));
const Checkmark = styled.Text(({ theme }) => ({
color: theme.color.lightest,
}));
const Footer = styled.View(({ theme }) => ({
backgroundColor: theme.barBg,
}));
const FooterButton = styled.View(({ theme, primary }) => ({
minHeight: 32,
borderRadius: theme.input.borderRadius,
backgroundColor: primary ? theme.color.secondary : theme.button.background,
borderColor: primary ? theme.color.secondary : theme.button.border,
borderWidth: 1,
alignItems: 'center',
justifyContent: 'center',
paddingHorizontal: 12,
}));
const FooterButtonText = styled.Text(({ theme, primary }) => ({
color: primary ? theme.color.lightest : theme.input.color,
fontSize: theme.typography.size.s1,
fontWeight: theme.typography.weight.bold,
}));
const optionKey = (option) => String(option.key);
const valueKeys = (value, multiple) => {
if (multiple) {
return Array.isArray(value) ? value.map(String) : [];
}
return value === undefined || value === null ? [] : [String(value)];
};
export const SelectModal = ({ options, value, multiple = false, onChange, children, }) => {
const { height } = useWindowDimensions();
const [visible, setVisible] = useState(false);
const [selectedKeys, setSelectedKeys] = useState(() => valueKeys(value, multiple));
useEffect(() => {
// eslint-disable-next-line react-hooks/set-state-in-effect
setSelectedKeys(valueKeys(value, multiple));
}, [multiple, value]);
const selectedKeySet = useMemo(() => new Set(selectedKeys), [selectedKeys]);
const close = useCallback(() => {
setVisible(false);
}, []);
const open = useCallback(() => {
setSelectedKeys(valueKeys(value, multiple));
setVisible(true);
}, [multiple, value]);
const selectOption = useCallback((option) => {
const key = optionKey(option);
if (!multiple) {
onChange(option.key);
close();
return;
}
setSelectedKeys((current) => current.includes(key) ? current.filter((item) => item !== key) : [...current, key]);
}, [close, multiple, onChange]);
const commitMultiSelect = useCallback(() => {
onChange(options
.filter((option) => selectedKeys.includes(optionKey(option)))
.map((option) => option.key));
close();
}, [close, onChange, options, selectedKeys]);
return (_jsxs(View, { children: [_jsx(ModalPortal, { transparent: true, supportedOrientations: ['portrait'], visible: visible, onRequestClose: close, animationType: "none", children: _jsx(TouchableWithoutFeedback, { accessible: false, onPress: close, children: _jsx(View, { style: styles.overlay, children: _jsx(TouchableWithoutFeedback, { accessible: false, children: _jsxs(Dialog, { maxHeight: height * 0.75, children: [_jsx(ScrollView, { contentContainerStyle: styles.scrollContent, keyboardShouldPersistTaps: "always", children: options.map((option, index) => {
const key = optionKey(option);
const selected = selectedKeySet.has(key);
return (_jsx(TouchableOpacity, { testID: `select-modal-${option.label}`, activeOpacity: 0.7, accessibilityLabel: option.label, onPress: () => selectOption(option), children: _jsx(OptionButton, { selected: selected, style: [styles.option, index === options.length - 1 && styles.lastOption], children: _jsxs(View, { style: styles.optionContent, children: [_jsx(OptionLabel, { selected: selected, style: styles.optionText, children: option.label }), _jsx(View, { style: styles.indicator, children: selected && _jsx(Checkmark, { style: styles.checkmark, children: "\u2713" }) })] }) }) }, key));
}) }), multiple && (_jsx(Footer, { style: [styles.footer, styles.doneFooter], children: _jsx(TouchableOpacity, { activeOpacity: 0.7, onPress: commitMultiSelect, children: _jsx(FooterButton, { primary: true, children: _jsx(FooterButtonText, { primary: true, style: styles.buttonText, children: "Done" }) }) }) })), _jsx(Footer, { style: [styles.footer, multiple && styles.cancelFooter], children: _jsx(TouchableOpacity, { activeOpacity: 0.7, onPress: close, children: _jsx(FooterButton, { children: _jsx(FooterButtonText, { style: styles.buttonText, children: "Cancel" }) }) }) })] }) }) }) }) }), _jsx(TouchableOpacity, { activeOpacity: 0.7, onPress: open, children: _jsx(View, { style: styles.triggerContent, children: children }) })] }));
};
export default SelectModal;