Skip to content

Commit 6cff036

Browse files
Support modal mode on picker (#882)
* feat: support mode modal for picker component (#881) * feat: add react-native select dropdown and needed interface * feat: add modal picker component * chore: add picker example about modal mode * chore: clean up * Tweaks to modal dropdown picker --------- Co-authored-by: sieu-db <[email protected]>
1 parent 30996f9 commit 6cff036

File tree

7 files changed

+259
-19
lines changed

7 files changed

+259
-19
lines changed

example/src/PickerExample.tsx

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,65 @@ function PickerExample() {
5757
/>
5858
</Picker>
5959

60+
<Section style={{}} title="Picker - Modal">
61+
<></>
62+
</Section>
63+
<Picker
64+
label="Make"
65+
placeholder="Select a make..."
66+
options={OPTIONS}
67+
value={value1}
68+
mode="dropdown-modal"
69+
dropDownBorderColor="blue"
70+
dropDownTextColor="red"
71+
onValueChange={(value) => setValue(value.toString())}
72+
style={{ marginBottom: 20 }}
73+
/>
74+
75+
<Section style={{}} title="Picker - Modal (customized item)">
76+
<></>
77+
</Section>
78+
<Picker
79+
label="Make"
80+
placeholder="Select a make..."
81+
options={OPTIONS}
82+
value={value1}
83+
mode="dropdown-modal"
84+
onValueChange={(value) => setValue(value.toString())}
85+
style={{ marginBottom: 20 }}
86+
selectedIconColor="white"
87+
>
88+
<PickerItem
89+
style={{ color: "red", fontWeight: 600 }}
90+
selectedTextColor="white"
91+
selectedBackgroundColor="black"
92+
selectedTextSize={22}
93+
/>
94+
</Picker>
95+
<Section
96+
style={{}}
97+
title="Picker - Modal (custom transparent dropdown overlay)"
98+
>
99+
<></>
100+
</Section>
101+
<Picker
102+
label="Make"
103+
placeholder="Select a make..."
104+
options={OPTIONS}
105+
value={value1}
106+
mode="dropdown-modal"
107+
onValueChange={(value) => setValue(value.toString())}
108+
style={{ marginBottom: 20 }}
109+
selectedIconColor="white"
110+
dropdownOverlayColor="transparent"
111+
>
112+
<PickerItem
113+
style={{ color: "red", fontWeight: 600 }}
114+
selectedTextColor="white"
115+
selectedBackgroundColor="black"
116+
selectedTextSize={22}
117+
/>
118+
</Picker>
60119
<Section style={{}} title="Multiselect Picker">
61120
<></>
62121
</Section>

packages/core/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
"react-native-confirmation-code-field": "^7.3.1",
6161
"react-native-deck-swiper": "^2.0.12",
6262
"react-native-dropdown-picker": "^5.4.7-beta.1",
63+
"react-native-select-dropdown": "4.0.1",
6364
"react-native-gesture-handler": "~2.14.0",
6465
"react-native-keyboard-aware-scroll-view": "^0.9.5",
6566
"react-native-markdown-display": "^7.0.0-alpha.2",

packages/core/src/components/Picker/PickerCommon.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ import { StyleProp, ViewStyle, TextStyle } from "react-native";
22
import { IconSlot } from "../../interfaces/Icon";
33
import { isObject } from "lodash";
44
import { Theme } from "../../styles/DefaultTheme";
5+
import React from "react";
6+
import { flattenReactFragments } from "../../utilities";
7+
import PickerItem, { PickerItemProps } from "./dropdown/PickerItem";
58

69
export interface PickerOption {
710
value: string | number;
@@ -40,6 +43,10 @@ export interface MultiSelectPickerProps {
4043
onValueChange: (value: (string | number)[]) => void;
4144
}
4245

46+
export interface DropDownModalPickerProps {
47+
dropdownOverlayColor?: string;
48+
}
49+
4350
export interface CommonDropDownPickerProps extends CommonPickerProps {
4451
selectedIconName?: string;
4552
selectedIconColor?: string;
@@ -84,3 +91,18 @@ export function normalizeToPickerOptions(
8491
'Picker options must be either an array of strings, numbers, or an array of { "label": string | number; "value": string | number; } objects.'
8592
);
8693
}
94+
95+
export function usePickerItemProps(
96+
childrenProp: React.ReactNode
97+
): PickerItemProps {
98+
return React.useMemo(() => {
99+
const children = flattenReactFragments(
100+
React.Children.toArray(childrenProp) as React.ReactElement[]
101+
);
102+
103+
// Only the props of the first PickerItem are used, any others are ignored
104+
const firstPickerItem = children.find((child) => child.type === PickerItem);
105+
106+
return firstPickerItem?.props || {};
107+
}, [childrenProp]);
108+
}
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
import * as React from "react";
2+
import { View, Text, Keyboard } from "react-native";
3+
import { extractStyles, useDeepCompareMemo } from "../../../utilities";
4+
import {
5+
CommonDropDownPickerProps,
6+
DropDownModalPickerProps,
7+
SinglePickerProps,
8+
normalizeToPickerOptions,
9+
usePickerItemProps,
10+
} from "../PickerCommon";
11+
import PickerInputContainer from "../PickerInputContainer";
12+
import ModalPickerComponent from "react-native-select-dropdown";
13+
import { withTheme } from "../../../theming";
14+
import { useOnUpdate } from "../../../hooks";
15+
16+
const ModalPicker: React.FC<
17+
React.PropsWithChildren<
18+
CommonDropDownPickerProps & SinglePickerProps & DropDownModalPickerProps
19+
>
20+
> = ({
21+
theme,
22+
options: optionsProp = [],
23+
onValueChange,
24+
Icon,
25+
placeholder,
26+
value,
27+
autoDismissKeyboard = true,
28+
selectedIconName = "Feather/check",
29+
selectedIconColor = theme.colors.strong,
30+
selectedIconSize = 20,
31+
dropDownBackgroundColor = theme.colors.background,
32+
dropDownBorderColor = theme.colors.divider,
33+
dropDownTextColor = theme.colors.strong,
34+
dropDownBorderWidth = 1,
35+
dropDownBorderRadius = 8,
36+
children: childrenProp,
37+
disabled,
38+
dropdownOverlayColor,
39+
...rest
40+
}) => {
41+
const dropdownRef = React.useRef<ModalPickerComponent>();
42+
43+
const [internalValue, setInternalValue] = React.useState<
44+
string | number | undefined
45+
>(value);
46+
47+
const pickerItemProps = usePickerItemProps(childrenProp);
48+
49+
const { viewStyles: pickerItemViewStyles, textStyles: pickerItemTextStyles } =
50+
extractStyles(pickerItemProps.style);
51+
52+
const options = useDeepCompareMemo(
53+
() =>
54+
normalizeToPickerOptions(optionsProp).map((option) => ({
55+
label: option.label.toString(),
56+
value: option.value,
57+
})),
58+
[optionsProp]
59+
);
60+
61+
useOnUpdate(() => {
62+
onValueChange?.(internalValue ?? "");
63+
// onValueChange excluded to prevent running on every re-render when using an anoymous function, which is the common case
64+
}, [internalValue]);
65+
66+
return (
67+
<PickerInputContainer
68+
testID="dropdown-modal-picker"
69+
Icon={Icon}
70+
placeholder={placeholder}
71+
selectedValue={value}
72+
options={options}
73+
onPress={() => {
74+
dropdownRef.current?.openDropdown();
75+
if (autoDismissKeyboard) {
76+
Keyboard.dismiss();
77+
}
78+
}}
79+
disabled={disabled}
80+
{...rest}
81+
>
82+
<ModalPickerComponent
83+
ref={dropdownRef as React.LegacyRef<ModalPickerComponent>}
84+
data={options}
85+
defaultValue={internalValue}
86+
onSelect={(selectedItem) => {
87+
setInternalValue(selectedItem.value);
88+
}}
89+
renderButton={() => {
90+
return <></>;
91+
}}
92+
renderItem={(item, _, isSelected) => {
93+
const backgroundColor = isSelected
94+
? pickerItemProps.selectedBackgroundColor ??
95+
pickerItemViewStyles.backgroundColor
96+
: pickerItemViewStyles.backgroundColor;
97+
98+
const textColor = isSelected
99+
? pickerItemProps.selectedTextColor ?? dropDownTextColor
100+
: dropDownTextColor;
101+
102+
const textSize = isSelected
103+
? pickerItemProps.selectedTextSize ?? 14
104+
: 14;
105+
106+
return (
107+
<View
108+
style={[
109+
{
110+
padding: 10,
111+
flexDirection: "row",
112+
alignItems: "center",
113+
backgroundColor: backgroundColor,
114+
},
115+
pickerItemViewStyles,
116+
]}
117+
>
118+
<Text
119+
style={[
120+
{
121+
color: textColor,
122+
fontSize: textSize,
123+
flex: 1,
124+
},
125+
pickerItemTextStyles,
126+
]}
127+
>
128+
{item.label}
129+
</Text>
130+
{isSelected ? (
131+
<Icon
132+
name={selectedIconName}
133+
size={selectedIconSize}
134+
color={selectedIconColor}
135+
/>
136+
) : null}
137+
</View>
138+
);
139+
}}
140+
showsVerticalScrollIndicator={false}
141+
dropdownOverlayColor={dropdownOverlayColor}
142+
dropdownStyle={{
143+
borderColor: dropDownBorderColor,
144+
borderWidth: dropDownBorderWidth,
145+
borderRadius: dropDownBorderRadius,
146+
backgroundColor: dropDownBackgroundColor,
147+
}}
148+
disableAutoScroll
149+
statusBarTranslucent
150+
/>
151+
</PickerInputContainer>
152+
);
153+
};
154+
155+
export default withTheme(ModalPicker);

packages/core/src/components/Picker/dropdown/DropDownPicker.tsx

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,16 @@
11
import * as React from "react";
22
import { Keyboard } from "react-native";
3-
import {
4-
extractStyles,
5-
flattenReactFragments,
6-
useDeepCompareMemo,
7-
} from "../../../utilities";
3+
import { extractStyles, useDeepCompareMemo } from "../../../utilities";
84
import {
95
CommonDropDownPickerProps,
106
MultiSelectPickerProps,
117
SinglePickerProps,
128
normalizeToPickerOptions,
9+
usePickerItemProps,
1310
} from "../PickerCommon";
1411
import PickerInputContainer from "../PickerInputContainer";
1512
import DropDownPickerComponent from "react-native-dropdown-picker";
1613
import { withTheme } from "../../../theming";
17-
import PickerItem, { PickerItemProps } from "./PickerItem";
1814
import { useOnUpdate } from "../../../hooks";
1915

2016
const DropDownPicker: React.FC<
@@ -48,16 +44,7 @@ const DropDownPicker: React.FC<
4844

4945
const isMultiSelect = Array.isArray(value);
5046

51-
const pickerItemProps: PickerItemProps = React.useMemo(() => {
52-
const children = flattenReactFragments(
53-
React.Children.toArray(childrenProp) as React.ReactElement[]
54-
);
55-
56-
// Only the props of the first PickerItem are used, any others are ignored
57-
const firstPickerItem = children.find((child) => child.type === PickerItem);
58-
59-
return firstPickerItem?.props || {};
60-
}, [childrenProp]);
47+
const pickerItemProps = usePickerItemProps(childrenProp);
6148

6249
const { viewStyles: pickerItemViewStyles, textStyles: pickerItemTextStyles } =
6350
extractStyles(pickerItemProps.style);

packages/core/src/components/Picker/index.tsx

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,19 @@
11
import React from "react";
2-
import { CommonDropDownPickerProps, SinglePickerProps } from "./PickerCommon";
2+
import {
3+
CommonDropDownPickerProps,
4+
DropDownModalPickerProps,
5+
SinglePickerProps,
6+
} from "./PickerCommon";
37
import NativePicker from "./NativePicker";
48
import DropDownPicker from "./dropdown/DropDownPicker";
9+
import DropDownModalPicker from "./dropdown/DropDownModalPicker";
510
import { withTheme } from "../../theming";
611

7-
interface PickerProps extends CommonDropDownPickerProps, SinglePickerProps {
8-
mode?: "native" | "dropdown";
12+
interface PickerProps
13+
extends CommonDropDownPickerProps,
14+
SinglePickerProps,
15+
DropDownModalPickerProps {
16+
mode?: "native" | "dropdown" | "dropdown-modal";
917
}
1018

1119
const SinglePicker: React.FC<React.PropsWithChildren<PickerProps>> = ({
@@ -19,6 +27,9 @@ const SinglePicker: React.FC<React.PropsWithChildren<PickerProps>> = ({
1927
case "dropdown":
2028
//@ts-ignore
2129
return <DropDownPicker {...rest} />;
30+
case "dropdown-modal":
31+
//@ts-ignore
32+
return <DropDownModalPicker {...rest} />;
2233
}
2334
};
2435

yarn.lock

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13022,6 +13022,11 @@ react-native-screens@~3.29.0:
1302213022
react-freeze "^1.0.0"
1302313023
warn-once "^0.1.0"
1302413024

13025+
13026+
version "4.0.1"
13027+
resolved "https://registry.yarnpkg.com/react-native-select-dropdown/-/react-native-select-dropdown-4.0.1.tgz#5789bdf60742f8ca881391e6f47a5b285ee407d8"
13028+
integrity sha512-t4se17kALFcPb9wMbxig5dS1BE3pWRC6HPuFlM0J2Y6yhB1GsLqboy6an6R9rML8pRuGIJIxL29cbwEvPQwKxQ==
13029+
1302513030
react-native-shadow-2@^7.0.7:
1302613031
version "7.0.7"
1302713032
resolved "https://registry.yarnpkg.com/react-native-shadow-2/-/react-native-shadow-2-7.0.7.tgz#f83ac2dda5ac202281d9a06cdb239d9c8829e7d2"

0 commit comments

Comments
 (0)