Skip to content

Commit 1f83f1e

Browse files
committed
multiselect fixes
1 parent 5d9cf8d commit 1f83f1e

3 files changed

Lines changed: 189 additions & 35 deletions

File tree

examples/expo-example/components/ControlExamples/Select/Select.stories.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,3 +87,18 @@ export const ManyOptions: Story = {
8787
},
8888
},
8989
};
90+
91+
export const MultiSelect: Story = {
92+
args: {
93+
arrow: [manyArrows[0], manyArrows[1], manyArrows[2]],
94+
},
95+
96+
argTypes: {
97+
arrow: {
98+
options: manyArrows,
99+
control: {
100+
type: 'multi-select',
101+
},
102+
},
103+
},
104+
};
Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import { Text } from 'react-native';
22

33
export interface Props {
4-
arrow: string;
4+
arrow: string | string[];
55
}
66

7-
export const SelectExample = ({ arrow }: Props) => <Text>Selected: {arrow}</Text>;
7+
export const SelectExample = ({ arrow }: Props) => (
8+
<Text>Selected: {Array.isArray(arrow) ? arrow.join(', ') : arrow}</Text>
9+
);

packages/ondevice-controls/src/components/SelectModal.tsx

Lines changed: 170 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// NOTE This is adapted from react-native-modal-selector https://github.com/peacechen/react-native-modal-selector/blob/master/index.js
22

3+
import { useTheme } from '@storybook/react-native-theming';
34
import { ComponentType, ReactNode, useCallback, useMemo, useState } from 'react';
45

56
import {
@@ -21,7 +22,7 @@ import { ModalPortal } from './ModalPortal';
2122
const PADDING = 8;
2223
const BORDER_RADIUS = 5;
2324
const FONT_SIZE = 16;
24-
const HIGHLIGHT_COLOR = 'rgba(0,118,255,0.9)';
25+
const MODAL_HORIZONTAL_MARGIN = 24;
2526

2627
type SupportedOrientation =
2728
| 'portrait'
@@ -178,7 +179,7 @@ const styles = StyleSheet.create({
178179

179180
checkmark: {
180181
fontSize: FONT_SIZE,
181-
color: HIGHLIGHT_COLOR,
182+
color: '#333',
182183
},
183184

184185
optionRow: {
@@ -201,7 +202,7 @@ const styles = StyleSheet.create({
201202
doneButtonText: {
202203
textAlign: 'center',
203204
fontSize: FONT_SIZE,
204-
color: HIGHLIGHT_COLOR,
205+
color: '#333',
205206
},
206207
});
207208

@@ -260,10 +261,11 @@ export const SelectModal = ({
260261
selectedSeparator = ', ',
261262
maxSelectedItems,
262263
selectedItemIndicatorStyle,
263-
selectedItemIndicatorColor = HIGHLIGHT_COLOR,
264+
selectedItemIndicatorColor,
264265
doneText = 'Done',
265266
onDone,
266267
}: SelectModalProps) => {
268+
const theme = useTheme();
267269
const { height: windowHeight } = useWindowDimensions();
268270
const [modalVisible, setModalVisible] = useState(false);
269271
const [selected, setSelected] = useState<string | string[]>(
@@ -294,6 +296,98 @@ export const SelectModal = ({
294296
}
295297
}, [selectedItems, keyExtractor, multiselect, selected, data, labelExtractor]);
296298

299+
const themedStyles = useMemo(
300+
() => ({
301+
modalContent: {
302+
maxHeight: windowHeight * 0.75,
303+
marginHorizontal: MODAL_HORIZONTAL_MARGIN,
304+
backgroundColor: theme.background.content,
305+
borderColor: theme.appBorderColor,
306+
borderWidth: 1,
307+
borderRadius: 8,
308+
overflow: 'hidden' as const,
309+
boxShadow: `0px 8px 24px 0px ${theme.color.border}`,
310+
elevation: 10,
311+
} satisfies ViewStyle,
312+
optionContainer: {
313+
backgroundColor: theme.background.content,
314+
borderRadius: 0,
315+
borderBottomColor: theme.appBorderColor,
316+
borderBottomWidth: 1,
317+
marginBottom: 0,
318+
} satisfies ViewStyle,
319+
optionStyle: {
320+
minHeight: 34,
321+
borderBottomColor: theme.appBorderColor,
322+
} satisfies ViewStyle,
323+
selectedOptionStyle: {
324+
backgroundColor: theme.color.secondary,
325+
} satisfies ViewStyle,
326+
optionTextStyle: {
327+
color: theme.color.defaultText,
328+
fontSize: theme.typography.size.s2,
329+
} satisfies TextStyle,
330+
selectedOptionTextStyle: {
331+
color: theme.color.lightest,
332+
fontWeight: theme.typography.weight.bold,
333+
} satisfies TextStyle,
334+
cancelContainer: {
335+
marginTop: 0,
336+
padding: 8,
337+
backgroundColor: theme.barBg,
338+
} satisfies ViewStyle,
339+
doneContainer: {
340+
paddingBottom: 4,
341+
} satisfies ViewStyle,
342+
cancelAfterDoneContainer: {
343+
paddingTop: 4,
344+
} satisfies ViewStyle,
345+
actionButton: {
346+
minHeight: 32,
347+
borderRadius: theme.input.borderRadius,
348+
backgroundColor: theme.button.background,
349+
borderColor: theme.button.border,
350+
borderWidth: 1,
351+
alignItems: 'center' as const,
352+
justifyContent: 'center' as const,
353+
paddingHorizontal: 12,
354+
paddingVertical: 0,
355+
} satisfies ViewStyle,
356+
primaryActionButton: {
357+
backgroundColor: theme.color.secondary,
358+
borderColor: theme.color.secondary,
359+
} satisfies ViewStyle,
360+
actionText: {
361+
color: theme.input.color,
362+
fontSize: theme.typography.size.s1,
363+
fontWeight: theme.typography.weight.bold,
364+
} satisfies TextStyle,
365+
primaryActionText: {
366+
color: theme.color.lightest,
367+
} satisfies TextStyle,
368+
checkmark: {
369+
color: theme.color.lightest,
370+
} satisfies TextStyle,
371+
}),
372+
[
373+
theme.appBorderColor,
374+
theme.background.content,
375+
theme.barBg,
376+
theme.button.background,
377+
theme.button.border,
378+
theme.color.border,
379+
theme.color.defaultText,
380+
theme.color.lightest,
381+
theme.color.secondary,
382+
theme.input.borderRadius,
383+
theme.input.color,
384+
theme.typography.size.s1,
385+
theme.typography.size.s2,
386+
theme.typography.weight.bold,
387+
windowHeight,
388+
]
389+
);
390+
297391
const open = useCallback(() => {
298392
if (!disabled) {
299393
setModalVisible(true);
@@ -368,34 +462,52 @@ export const SelectModal = ({
368462
const optionKey = keyExtractor(option);
369463
const isSelected = selectedItemsMap[optionKey];
370464

371-
const content = (
372-
<>
373-
<Text style={[styles.optionTextStyle, optionTextStyleProp]} {...optionTextPassThruProps}>
374-
{optionLabel}
375-
</Text>
376-
<View style={[styles.selectedItemIndicator, selectedItemIndicatorStyle]}>
377-
{isSelected && (
378-
<Text style={[styles.checkmark, { color: selectedItemIndicatorColor }]}></Text>
379-
)}
380-
</View>
381-
</>
382-
);
383-
384465
return (
385466
<TouchableOpacity
386467
key={optionKey}
387468
testID={`${optionsTestIDPrefix}-${optionLabel}`}
388469
onPress={() => handleChange(option)}
389-
activeOpacity={touchableActiveOpacity}
470+
activeOpacity={multiselect ? 1 : touchableActiveOpacity}
390471
accessible={listItemAccessible}
391472
accessibilityLabel={option.accessibilityLabel}
392473
importantForAccessibility={isFirstItem ? 'yes' : 'no'}
393474
{...passThruProps}
394475
>
395476
<View
396-
style={[styles.optionStyle, optionStyleProp, isLastItem && { borderBottomWidth: 0 }]}
477+
style={[
478+
styles.optionStyle,
479+
themedStyles.optionStyle,
480+
optionStyleProp,
481+
isSelected && themedStyles.selectedOptionStyle,
482+
isLastItem && { borderBottomWidth: 0 },
483+
]}
397484
>
398-
<View style={styles.optionRow}>{content}</View>
485+
<View style={styles.optionRow}>
486+
<Text
487+
style={[
488+
styles.optionTextStyle,
489+
themedStyles.optionTextStyle,
490+
optionTextStyleProp,
491+
isSelected && themedStyles.selectedOptionTextStyle,
492+
]}
493+
{...optionTextPassThruProps}
494+
>
495+
{optionLabel}
496+
</Text>
497+
<View style={[styles.selectedItemIndicator, selectedItemIndicatorStyle]}>
498+
{isSelected && (
499+
<Text
500+
style={[
501+
styles.checkmark,
502+
themedStyles.checkmark,
503+
selectedItemIndicatorColor && { color: selectedItemIndicatorColor },
504+
]}
505+
>
506+
507+
</Text>
508+
)}
509+
</View>
510+
</View>
399511
</View>
400512
</TouchableOpacity>
401513
);
@@ -405,6 +517,7 @@ export const SelectModal = ({
405517
labelExtractor,
406518
handleChange,
407519
touchableActiveOpacity,
520+
multiselect,
408521
listItemAccessible,
409522
passThruProps,
410523
optionStyleProp,
@@ -414,6 +527,7 @@ export const SelectModal = ({
414527
selectedItemsMap,
415528
selectedItemIndicatorStyle,
416529
selectedItemIndicatorColor,
530+
themedStyles,
417531
]
418532
);
419533

@@ -461,15 +575,13 @@ export const SelectModal = ({
461575
...(scrollViewPassThruProps?.horizontal && { flexDirection: 'row' as const }),
462576
};
463577

464-
const modalContentStyle = {
465-
maxHeight: windowHeight * 0.75,
466-
} satisfies ViewStyle;
467-
468578
return (
469579
<OverlayComponent key={key} {...overlayProps}>
470580
<View style={[styles.overlayStyle, overlayStyleProp]}>
471-
<View style={modalContentStyle}>
472-
<View style={[styles.optionContainer, optionContainerStyle]}>
581+
<View style={themedStyles.modalContent}>
582+
<View
583+
style={[styles.optionContainer, themedStyles.optionContainer, optionContainerStyle]}
584+
>
473585
{header}
474586
{listType === 'FLATLIST' ? (
475587
<FlatList
@@ -500,27 +612,52 @@ export const SelectModal = ({
500612
</View>
501613

502614
{multiselect && (
503-
<View style={[styles.doneContainer]}>
615+
<View
616+
style={[
617+
styles.doneContainer,
618+
themedStyles.cancelContainer,
619+
themedStyles.doneContainer,
620+
]}
621+
>
504622
<TouchableOpacity
505-
style={styles.doneButton}
623+
style={[
624+
styles.doneButton,
625+
themedStyles.actionButton,
626+
themedStyles.primaryActionButton,
627+
]}
506628
onPress={handleDone}
507629
activeOpacity={touchableActiveOpacity}
508630
>
509-
<Text style={styles.doneButtonText}>{doneText}</Text>
631+
<Text
632+
style={[
633+
styles.doneButtonText,
634+
themedStyles.actionText,
635+
themedStyles.primaryActionText,
636+
]}
637+
>
638+
{doneText}
639+
</Text>
510640
</TouchableOpacity>
511641
</View>
512642
)}
513643

514-
<View style={[styles.cancelContainer, cancelContainerStyle]}>
644+
<View
645+
style={[
646+
styles.cancelContainer,
647+
themedStyles.cancelContainer,
648+
multiselect && themedStyles.cancelAfterDoneContainer,
649+
cancelContainerStyle,
650+
]}
651+
>
515652
<TouchableOpacity
516653
onPress={close}
517654
activeOpacity={touchableActiveOpacity}
518655
accessible={cancelButtonAccessible}
519656
accessibilityLabel={cancelButtonAccessibilityLabel}
520657
>
521-
<View style={[styles.cancelStyle, cancelStyleProp]}>
658+
<View style={[styles.cancelStyle, themedStyles.actionButton, cancelStyleProp]}>
522659
<Text
523-
style={[styles.cancelTextStyle, cancelTextStyleProp]}
660+
style={[styles.cancelTextStyle, themedStyles.actionText, cancelTextStyleProp]}
524661
{...cancelTextPassThruProps}
525662
>
526663
{cancelText}
@@ -560,7 +697,7 @@ export const SelectModal = ({
560697
multiselect,
561698
handleDone,
562699
doneText,
563-
windowHeight,
700+
themedStyles,
564701
]);
565702

566703
const renderChildren = useCallback(() => {

0 commit comments

Comments
 (0)