UNPKG

@storybook/addon-ondevice-controls

Version:

Display storybook controls on your device.

147 lines (146 loc) 6.78 kB
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;