@jobber/components-native 0.30.0 → 0.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/README.md +3 -0
  2. package/dist/src/Select/Select.js +79 -0
  3. package/dist/src/Select/Select.style.js +45 -0
  4. package/dist/src/Select/components/SelectDefaultPicker/SelectDefaultPicker.ios.js +30 -0
  5. package/dist/src/Select/components/SelectDefaultPicker/SelectDefaultPicker.js +16 -0
  6. package/dist/src/Select/components/SelectDefaultPicker/SelectDefaultPicker.style.js +29 -0
  7. package/dist/src/Select/components/SelectDefaultPicker/index.js +1 -0
  8. package/dist/src/Select/components/SelectDefaultPicker/messages.js +8 -0
  9. package/dist/src/Select/components/SelectIOSPicker/SelectIOSPicker.js +2 -0
  10. package/dist/src/Select/components/SelectIOSPicker/index.js +1 -0
  11. package/dist/src/Select/components/SelectInternalPicker/SelectInternalPicker.js +14 -0
  12. package/dist/src/Select/components/SelectInternalPicker/index.js +1 -0
  13. package/dist/src/Select/components/SelectInternalPicker/utils.js +13 -0
  14. package/dist/src/Select/components/SelectPressable/SelectPressable.js +15 -0
  15. package/dist/src/Select/components/SelectPressable/SelectPressable.style.js +7 -0
  16. package/dist/src/Select/components/SelectPressable/index.js +1 -0
  17. package/dist/src/Select/index.js +1 -0
  18. package/dist/src/Select/messages.js +13 -0
  19. package/dist/src/Select/types.js +1 -0
  20. package/dist/src/index.js +1 -0
  21. package/dist/tsconfig.tsbuildinfo +1 -1
  22. package/dist/types/src/Select/Select.d.ts +69 -0
  23. package/dist/types/src/Select/Select.style.d.ts +56 -0
  24. package/dist/types/src/Select/components/SelectDefaultPicker/SelectDefaultPicker.d.ts +5 -0
  25. package/dist/types/src/Select/components/SelectDefaultPicker/SelectDefaultPicker.ios.d.ts +5 -0
  26. package/dist/types/src/Select/components/SelectDefaultPicker/SelectDefaultPicker.style.d.ts +30 -0
  27. package/dist/types/src/Select/components/SelectDefaultPicker/index.d.ts +1 -0
  28. package/dist/types/src/Select/components/SelectDefaultPicker/messages.d.ts +7 -0
  29. package/dist/types/src/Select/components/SelectIOSPicker/SelectIOSPicker.d.ts +10 -0
  30. package/dist/types/src/Select/components/SelectIOSPicker/index.d.ts +1 -0
  31. package/dist/types/src/Select/components/SelectInternalPicker/SelectInternalPicker.d.ts +3 -0
  32. package/dist/types/src/Select/components/SelectInternalPicker/index.d.ts +1 -0
  33. package/dist/types/src/Select/components/SelectInternalPicker/utils.d.ts +3 -0
  34. package/dist/types/src/Select/components/SelectPressable/SelectPressable.d.ts +11 -0
  35. package/dist/types/src/Select/components/SelectPressable/SelectPressable.style.d.ts +5 -0
  36. package/dist/types/src/Select/components/SelectPressable/index.d.ts +1 -0
  37. package/dist/types/src/Select/index.d.ts +1 -0
  38. package/dist/types/src/Select/messages.d.ts +12 -0
  39. package/dist/types/src/Select/types.d.ts +38 -0
  40. package/dist/types/src/index.d.ts +1 -0
  41. package/ios/ComponentsNative-Bridging-Header.h +2 -0
  42. package/ios/ComponentsNative.xcodeproj/project.pbxproj +303 -0
  43. package/ios/Picker/ATLFallBackPickerView.swift +13 -0
  44. package/ios/Picker/ATLPickerOption.swift +44 -0
  45. package/ios/Picker/ATLPickerView.swift +61 -0
  46. package/ios/Picker/RCTATLPickerManager.m +26 -0
  47. package/ios/Picker/RCTATLPickerManager.swift +25 -0
  48. package/jobber-components-native.podspec +35 -0
  49. package/package.json +18 -3
  50. package/src/Select/Select.style.ts +51 -0
  51. package/src/Select/Select.test.tsx +323 -0
  52. package/src/Select/Select.tsx +240 -0
  53. package/src/Select/components/SelectDefaultPicker/SelectDefaultPicker.ios.tsx +64 -0
  54. package/src/Select/components/SelectDefaultPicker/SelectDefaultPicker.style.ts +30 -0
  55. package/src/Select/components/SelectDefaultPicker/SelectDefaultPicker.test.tsx +76 -0
  56. package/src/Select/components/SelectDefaultPicker/SelectDefaultPicker.tsx +45 -0
  57. package/src/Select/components/SelectDefaultPicker/index.ts +1 -0
  58. package/src/Select/components/SelectDefaultPicker/messages.ts +9 -0
  59. package/src/Select/components/SelectIOSPicker/SelectIOSPicker.tsx +16 -0
  60. package/src/Select/components/SelectIOSPicker/index.ts +1 -0
  61. package/src/Select/components/SelectInternalPicker/SelectInternalPicker.test.tsx +100 -0
  62. package/src/Select/components/SelectInternalPicker/SelectInternalPicker.tsx +33 -0
  63. package/src/Select/components/SelectInternalPicker/index.ts +1 -0
  64. package/src/Select/components/SelectInternalPicker/utils.ts +20 -0
  65. package/src/Select/components/SelectPressable/SelectPressable.style.ts +8 -0
  66. package/src/Select/components/SelectPressable/SelectPressable.tsx +32 -0
  67. package/src/Select/components/SelectPressable/index.ts +1 -0
  68. package/src/Select/index.ts +1 -0
  69. package/src/Select/messages.ts +14 -0
  70. package/src/Select/types.ts +46 -0
  71. package/src/index.ts +1 -0
@@ -0,0 +1,240 @@
1
+ import React, { ReactElement } from "react";
2
+ import { View } from "react-native";
3
+ import { useIntl } from "react-intl";
4
+ import { RegisterOptions } from "react-hook-form";
5
+ import { styles } from "./Select.style";
6
+ import { SelectInternalPicker } from "./components/SelectInternalPicker";
7
+ import { messages } from "./messages";
8
+ import { InputFieldWrapper } from "../InputFieldWrapper";
9
+ import { Icon } from "../Icon";
10
+ import { TextVariation } from "../Typography";
11
+ import { Text } from "../Text";
12
+ import { useFormController } from "../hooks";
13
+
14
+ export interface SelectOption {
15
+ /**
16
+ * Text that shows up as the option
17
+ */
18
+ readonly children: string;
19
+
20
+ /**
21
+ * The value that gets returned when an option is selected
22
+ */
23
+ readonly value: string;
24
+ }
25
+
26
+ export interface SelectProps {
27
+ /**
28
+ * Current value of the component
29
+ */
30
+ readonly value?: string;
31
+
32
+ /**
33
+ * Default value for when the component is uncontrolled
34
+ */
35
+ readonly defaultValue?: string;
36
+
37
+ /**
38
+ * Adds a first option to let users select a "no value".
39
+ * Placeholder item selected by default until a selection is made.
40
+ */
41
+ readonly placeholder?: string;
42
+
43
+ /**
44
+ * Label text shown above the selection.
45
+ */
46
+ readonly label?: string;
47
+
48
+ /**
49
+ * Help text shown below the control.
50
+ */
51
+ readonly assistiveText?: string;
52
+
53
+ /**
54
+ * Callback that provides the new value when the selection changes
55
+ */
56
+ readonly onChange?: (newValue?: string) => void;
57
+
58
+ /**
59
+ * Disables input selection
60
+ */
61
+ readonly disabled?: boolean;
62
+
63
+ /**
64
+ * Indicates the current selection is invalid
65
+ */
66
+ readonly invalid?: boolean;
67
+
68
+ /**
69
+ * VoiceOver will read this string when a user selects the element
70
+ */
71
+ readonly accessibilityLabel?: string;
72
+
73
+ /**
74
+ * Helps users understand what will happen when they perform an action
75
+ */
76
+ readonly accessibilityHint?: string;
77
+
78
+ /**
79
+ * Name of the input.
80
+ */
81
+ readonly name?: string;
82
+
83
+ /**
84
+ * The options to select from
85
+ */
86
+ readonly children: ReactElement<SelectOption>[];
87
+
88
+ /**
89
+ * The validations that will mark this component as invalid
90
+ */
91
+ readonly validations?: RegisterOptions;
92
+ }
93
+
94
+ export function Select({
95
+ value,
96
+ defaultValue,
97
+ onChange,
98
+ children,
99
+ placeholder,
100
+ label,
101
+ assistiveText,
102
+ disabled,
103
+ invalid,
104
+ validations,
105
+ accessibilityLabel,
106
+ name,
107
+ }: SelectProps): JSX.Element {
108
+ const { field, error } = useFormController({
109
+ name,
110
+ validations,
111
+ value: value ?? defaultValue,
112
+ });
113
+
114
+ const { formatMessage } = useIntl();
115
+ const internalValue = value ?? field.value;
116
+ const textVariation = getTextVariation({
117
+ disabled,
118
+ invalid: invalid || !!error,
119
+ });
120
+ const valueTextVariation = disabled ? "disabled" : undefined;
121
+ const hasValue = internalValue && internalValue?.length > 0;
122
+
123
+ return (
124
+ <InputFieldWrapper
125
+ error={error}
126
+ invalid={invalid || !!error}
127
+ hasValue={hasValue}
128
+ styleOverride={{
129
+ container: { borderBottomWidth: undefined },
130
+ }}
131
+ >
132
+ <View
133
+ testID="ATL-Select"
134
+ accessible={true}
135
+ accessibilityLabel={getA11yLabel()}
136
+ accessibilityValue={{ text: getValue() }}
137
+ accessibilityHint={formatMessage(messages.a11yHint)}
138
+ accessibilityRole="button"
139
+ accessibilityState={{ disabled: disabled }}
140
+ >
141
+ <SelectInternalPicker
142
+ disabled={disabled}
143
+ options={getOptions()}
144
+ onChange={handleChange}
145
+ >
146
+ <View
147
+ style={[styles.container, (invalid || !!error) && styles.invalid]}
148
+ >
149
+ {label && (
150
+ <Text
151
+ level="textSupporting"
152
+ variation={textVariation}
153
+ hideFromScreenReader={true}
154
+ >
155
+ {label}
156
+ </Text>
157
+ )}
158
+
159
+ <View style={styles.input}>
160
+ <View style={styles.value}>
161
+ <Text
162
+ variation={disabled ? "disabled" : "base"}
163
+ hideFromScreenReader={true}
164
+ >
165
+ {getValue()}
166
+ </Text>
167
+ </View>
168
+ <View style={styles.icon}>
169
+ <Icon name="arrowDown" color={valueTextVariation} />
170
+ </View>
171
+ </View>
172
+ </View>
173
+ </SelectInternalPicker>
174
+
175
+ {assistiveText && (
176
+ <Text
177
+ level="textSupporting"
178
+ variation={textVariation}
179
+ hideFromScreenReader={true}
180
+ >
181
+ {assistiveText}
182
+ </Text>
183
+ )}
184
+ </View>
185
+ </InputFieldWrapper>
186
+ );
187
+
188
+ function getA11yLabel(): string | undefined {
189
+ let text = [accessibilityLabel || label, assistiveText];
190
+ text = text.filter(Boolean);
191
+ return text.join(", ");
192
+ }
193
+
194
+ function handleChange(val: string) {
195
+ onChange?.(val);
196
+ // need this onBlur for validations to occur
197
+ field.onBlur();
198
+ field.onChange(val);
199
+ }
200
+
201
+ function getOptions() {
202
+ const options = children.map(({ props }) => ({
203
+ label: props.children,
204
+ value: props.value,
205
+ isActive: props.value === internalValue,
206
+ }));
207
+
208
+ if (!internalValue || placeholder) {
209
+ options.unshift({
210
+ label: placeholder || formatMessage(messages.emptyValue),
211
+ value: "",
212
+ isActive: !internalValue,
213
+ });
214
+ }
215
+
216
+ return options;
217
+ }
218
+
219
+ function getValue() {
220
+ const options = getOptions();
221
+
222
+ const activeValue = options.find(option => option.isActive);
223
+ return (
224
+ activeValue?.label || placeholder || formatMessage(messages.emptyValue)
225
+ );
226
+ }
227
+ }
228
+
229
+ function getTextVariation({
230
+ invalid,
231
+ disabled,
232
+ }: Pick<SelectProps, "invalid" | "disabled">): TextVariation {
233
+ if (invalid) return "error";
234
+ if (disabled) return "disabled";
235
+ return "subdued";
236
+ }
237
+
238
+ export function Option({ children }: SelectOption): JSX.Element {
239
+ return <>{children}</>;
240
+ }
@@ -0,0 +1,64 @@
1
+ import React, { useState } from "react";
2
+ import {
3
+ // Need to use iOS style button
4
+ Button,
5
+ Modal,
6
+ TouchableOpacity,
7
+ View,
8
+ } from "react-native";
9
+ import { Picker } from "@react-native-picker/picker";
10
+ import { SafeAreaView } from "react-native-safe-area-context";
11
+ import { useIntl } from "react-intl";
12
+ import { styles } from "./SelectDefaultPicker.style";
13
+ import { messages } from "./messages";
14
+ import { SelectInternalPickerProps } from "../../types";
15
+ import { SelectPressable } from "../SelectPressable/SelectPressable";
16
+
17
+ type SelectDefaultPickerProps = SelectInternalPickerProps;
18
+
19
+ export function SelectDefaultPicker({
20
+ children,
21
+ options,
22
+ onChange,
23
+ }: SelectDefaultPickerProps): JSX.Element {
24
+ const [show, setShow] = useState(false);
25
+ const { formatMessage } = useIntl();
26
+ const selectedLanguage = options.find(option => option.isActive);
27
+
28
+ return (
29
+ <>
30
+ <SelectPressable onPress={showPicker}>{children}</SelectPressable>
31
+ <Modal
32
+ visible={show}
33
+ transparent
34
+ animationType="slide"
35
+ onRequestClose={hidePicker}
36
+ >
37
+ <TouchableOpacity style={styles.overlay} onPress={hidePicker} />
38
+ <View style={styles.actionBar}>
39
+ <Button title={formatMessage(messages.done)} onPress={hidePicker} />
40
+ </View>
41
+ <View style={styles.pickerContainer} testID="select-wheel-picker">
42
+ <SafeAreaView edges={["bottom"]}>
43
+ <Picker
44
+ selectedValue={selectedLanguage?.value}
45
+ onValueChange={onChange}
46
+ >
47
+ {options.map(({ label, value }, i) => (
48
+ <Picker.Item key={i} label={label} value={value} />
49
+ ))}
50
+ </Picker>
51
+ </SafeAreaView>
52
+ </View>
53
+ </Modal>
54
+ </>
55
+ );
56
+
57
+ function showPicker() {
58
+ setShow(true);
59
+ }
60
+
61
+ function hidePicker() {
62
+ setShow(false);
63
+ }
64
+ }
@@ -0,0 +1,30 @@
1
+ import { StyleSheet } from "react-native";
2
+
3
+ export const styles = StyleSheet.create({
4
+ overlay: { flex: 1 },
5
+ actionBar: {
6
+ flexDirection: "row",
7
+ height: 45,
8
+ justifyContent: "flex-end",
9
+ alignItems: "center",
10
+ paddingHorizontal: 10,
11
+ backgroundColor: "#f8f8f8",
12
+ borderTopWidth: 1,
13
+ borderTopColor: "#dedede",
14
+ zIndex: 2,
15
+ },
16
+ pickerContainer: {
17
+ justifyContent: "center",
18
+ backgroundColor: "#d0d4da",
19
+ },
20
+ androidPickerContainer: {
21
+ position: "absolute",
22
+ top: 0,
23
+ left: 0,
24
+ width: "100%",
25
+ height: "100%",
26
+ color: "transparent",
27
+ backgroundColor: "transparent",
28
+ opacity: 0,
29
+ },
30
+ });
@@ -0,0 +1,76 @@
1
+ import React from "react";
2
+ import { cleanup, fireEvent, render } from "@testing-library/react-native";
3
+ import { AccessibilityInfo, View } from "react-native";
4
+ import { SelectDefaultPicker } from "./SelectDefaultPicker";
5
+ import { messages } from "./messages";
6
+ import { Text } from "../../../Text";
7
+
8
+ const A11yInfoSpy = jest.spyOn(AccessibilityInfo, "isScreenReaderEnabled");
9
+ const handleChange = jest.fn();
10
+
11
+ function MockPicker() {
12
+ return React.createElement("MockPicker");
13
+ }
14
+ const MockPickerItem = () => <></>;
15
+ jest.mock("@react-native-picker/picker", () => ({ Picker: MockPicker }));
16
+ MockPicker.Item = MockPickerItem;
17
+
18
+ beforeEach(() => {
19
+ A11yInfoSpy.mockImplementation(() => Promise.resolve(false));
20
+ });
21
+
22
+ afterEach(() => {
23
+ cleanup();
24
+ jest.resetAllMocks();
25
+ });
26
+
27
+ const childText = "Click me";
28
+ function setup() {
29
+ return render(
30
+ <View testID="SelectDefaultPicker">
31
+ <SelectDefaultPicker
32
+ onChange={handleChange}
33
+ options={[
34
+ { value: "1", label: "First option" },
35
+ { value: "2", label: "Second option" },
36
+ ]}
37
+ >
38
+ <Text>{childText}</Text>
39
+ </SelectDefaultPicker>
40
+ </View>,
41
+ );
42
+ }
43
+
44
+ describe("SelectDefaultPicker", () => {
45
+ it("opens the picker", () => {
46
+ const screen = setup();
47
+
48
+ fireEvent.press(screen.getByText(childText));
49
+ expect(
50
+ screen.getByTestId("SelectDefaultPicker").findByType(MockPicker),
51
+ ).toBeDefined();
52
+ });
53
+
54
+ it("closes the picker", () => {
55
+ const screen = setup();
56
+
57
+ fireEvent.press(screen.getByText(childText));
58
+ fireEvent.press(screen.getByText(messages.done.defaultMessage));
59
+ expect(
60
+ screen.getByTestId("SelectDefaultPicker").findAllByType(MockPicker),
61
+ ).toEqual([]);
62
+ });
63
+
64
+ it("fires the onChange", () => {
65
+ const screen = setup();
66
+
67
+ fireEvent.press(screen.getByText(childText));
68
+ const menu = screen
69
+ .getByTestId("SelectDefaultPicker")
70
+ .findByType(MockPicker);
71
+
72
+ expect(menu).toBeDefined();
73
+ fireEvent(menu, "onValueChange", "2");
74
+ expect(handleChange).toHaveBeenCalledWith("2");
75
+ });
76
+ });
@@ -0,0 +1,45 @@
1
+ import React, { useRef } from "react";
2
+ import { Picker } from "@react-native-picker/picker";
3
+ import { styles } from "./SelectDefaultPicker.style";
4
+ import { SelectInternalPickerProps } from "../../types";
5
+ import { SelectPressable } from "../SelectPressable";
6
+ import { tokens } from "../../../utils/design";
7
+
8
+ type SelectDefaultPickerProps = SelectInternalPickerProps;
9
+
10
+ export function SelectDefaultPicker({
11
+ children,
12
+ options,
13
+ disabled,
14
+ onChange,
15
+ }: SelectDefaultPickerProps): JSX.Element {
16
+ const selectedItem = options.find(option => option.isActive);
17
+ const pickerRef = useRef<Picker<string>>(null);
18
+
19
+ return (
20
+ <SelectPressable onPress={pickerRef.current?.focus}>
21
+ {children}
22
+ <Picker
23
+ ref={pickerRef}
24
+ selectedValue={selectedItem?.value}
25
+ onValueChange={onChange}
26
+ mode="dropdown"
27
+ enabled={!disabled}
28
+ style={styles.androidPickerContainer}
29
+ >
30
+ {options.map(({ label, value, isActive }, i) => (
31
+ <Picker.Item
32
+ key={i}
33
+ label={label}
34
+ value={value}
35
+ color={isSelected(isActive)}
36
+ />
37
+ ))}
38
+ </Picker>
39
+ </SelectPressable>
40
+ );
41
+
42
+ function isSelected(isActive: boolean | undefined): string {
43
+ return isActive ? tokens["color-interactive"] : tokens["color-text"];
44
+ }
45
+ }
@@ -0,0 +1 @@
1
+ export * from "./SelectDefaultPicker";
@@ -0,0 +1,9 @@
1
+ import { defineMessages } from "react-intl";
2
+
3
+ export const messages = defineMessages({
4
+ done: {
5
+ id: "done",
6
+ defaultMessage: "Done",
7
+ description: "Done action",
8
+ },
9
+ });
@@ -0,0 +1,16 @@
1
+ import { HostComponent, requireNativeComponent } from "react-native";
2
+ import {
3
+ SelectInternalPickerProps,
4
+ SelectOnOptionPressEvent,
5
+ } from "../../types";
6
+
7
+ interface NativeSelectIOSPickerProps
8
+ extends Pick<SelectInternalPickerProps, "options" | "children"> {
9
+ /**
10
+ * Callback when one of the option is pressed
11
+ */
12
+ readonly onOptionPress: (event: SelectOnOptionPressEvent) => void;
13
+ }
14
+
15
+ export const SelectIOSPicker: HostComponent<NativeSelectIOSPickerProps> =
16
+ requireNativeComponent?.<NativeSelectIOSPickerProps>("RCTATLPicker");
@@ -0,0 +1 @@
1
+ export * from "./SelectIOSPicker";
@@ -0,0 +1,100 @@
1
+ import React, { ElementType } from "react";
2
+ import { cleanup, fireEvent, render } from "@testing-library/react-native";
3
+ import { View } from "react-native";
4
+ import { SelectInternalPicker } from ".";
5
+ import { SelectInternalPickerProps } from "../../types";
6
+ import { Text } from "../../../Text";
7
+
8
+ let Platform: { OS: "ios" | "android"; Version: string | number };
9
+ beforeEach(() => {
10
+ Platform = require("react-native").Platform;
11
+ Object.defineProperty(Platform, "Version", { get: () => undefined });
12
+ });
13
+
14
+ function MockPicker() {
15
+ return React.createElement("MockPicker");
16
+ }
17
+ const MockPickerItem = () => <></>;
18
+ jest.mock("@react-native-picker/picker", () => ({ Picker: MockPicker }));
19
+ MockPicker.Item = MockPickerItem;
20
+
21
+ afterEach(() => {
22
+ cleanup();
23
+ jest.resetAllMocks();
24
+ });
25
+
26
+ const RCTPicker = "RCTATLPicker" as ElementType;
27
+ const childText = "Click me";
28
+ const handleChange = jest.fn();
29
+
30
+ function setup(props?: Partial<SelectInternalPickerProps>) {
31
+ return render(
32
+ <View testID="SelectInternalPicker">
33
+ <SelectInternalPicker
34
+ onChange={handleChange}
35
+ disabled={props?.disabled}
36
+ options={[
37
+ { value: "1", label: "First option" },
38
+ { value: "2", label: "Second option" },
39
+ ]}
40
+ >
41
+ <Text>{childText}</Text>
42
+ </SelectInternalPicker>
43
+ </View>,
44
+ );
45
+ }
46
+
47
+ describe("SelectInternalPicker", () => {
48
+ it("fires the onChange", () => {
49
+ const screen = setup();
50
+ fireEvent.press(screen.getByText(childText));
51
+ const menu = screen
52
+ .getByTestId("SelectInternalPicker")
53
+ .findByType(MockPicker);
54
+
55
+ expect(menu).toBeDefined();
56
+ fireEvent(menu, "onChange", "1");
57
+ expect(handleChange).toHaveBeenCalledWith("1");
58
+ });
59
+
60
+ describe("iOS 13 and below fallback", () => {
61
+ it("renders the iOS 14 native menu picker", () => {
62
+ Platform.OS = "ios";
63
+ Object.defineProperty(Platform, "Version", { get: () => "14.1" });
64
+
65
+ const screen = setup();
66
+
67
+ const menu = screen
68
+ .getByTestId("SelectInternalPicker")
69
+ .findByType(RCTPicker);
70
+
71
+ expect(menu).toBeDefined();
72
+ });
73
+
74
+ it.each<[typeof Platform.OS, typeof Platform.Version]>([
75
+ ["ios", "13.1"],
76
+ ["android", 100],
77
+ ])("renders the menu picker on %s", (os, version) => {
78
+ Platform.OS = os;
79
+ Object.defineProperty(Platform, "Version", { get: () => version });
80
+ const screen = setup();
81
+
82
+ fireEvent.press(screen.getByText(childText));
83
+ expect(
84
+ screen.getByTestId("SelectInternalPicker").findByType(MockPicker),
85
+ ).toBeDefined();
86
+ });
87
+ });
88
+
89
+ describe("Disabled", () => {
90
+ it("should not render the picker", () => {
91
+ const screen = setup({ disabled: true });
92
+
93
+ const menu = screen
94
+ .getByTestId("SelectInternalPicker")
95
+ .findAllByType(RCTPicker);
96
+
97
+ expect(menu).toEqual([]);
98
+ });
99
+ });
100
+ });
@@ -0,0 +1,33 @@
1
+ import React from "react";
2
+ import { handlePress, isIOS14AndUp } from "./utils";
3
+ import { SelectInternalPickerProps } from "../../types";
4
+ import { SelectIOSPicker } from "../SelectIOSPicker";
5
+ import { SelectPressable } from "../SelectPressable";
6
+ import { SelectDefaultPicker } from "../SelectDefaultPicker";
7
+
8
+ export function SelectInternalPicker({
9
+ children,
10
+ options,
11
+ disabled,
12
+ onChange,
13
+ }: SelectInternalPickerProps): JSX.Element {
14
+ if (disabled) return <>{children}</>;
15
+ if (isIOS14AndUp()) {
16
+ return (
17
+ <SelectPressable>
18
+ <SelectIOSPicker
19
+ options={options}
20
+ onOptionPress={handlePress(onChange)}
21
+ >
22
+ {children}
23
+ </SelectIOSPicker>
24
+ </SelectPressable>
25
+ );
26
+ }
27
+
28
+ return (
29
+ <SelectDefaultPicker options={options} onChange={onChange}>
30
+ {children}
31
+ </SelectDefaultPicker>
32
+ );
33
+ }
@@ -0,0 +1 @@
1
+ export * from "./SelectInternalPicker";
@@ -0,0 +1,20 @@
1
+ import { Platform } from "react-native";
2
+ import {
3
+ SelectInternalPickerProps,
4
+ SelectOnOptionPressEvent,
5
+ } from "../../types";
6
+
7
+ export function handlePress(onChange: SelectInternalPickerProps["onChange"]) {
8
+ return ({ nativeEvent: { event } }: SelectOnOptionPressEvent): void => {
9
+ return onChange(event);
10
+ };
11
+ }
12
+
13
+ export function isIOS14AndUp(): boolean {
14
+ if (Platform.OS === "ios") {
15
+ const majorVersionIOS = parseInt(Platform.Version, 10);
16
+ return majorVersionIOS >= 14;
17
+ }
18
+
19
+ return false;
20
+ }
@@ -0,0 +1,8 @@
1
+ import { StyleSheet } from "react-native";
2
+ import { tokens } from "../../../utils/design";
3
+
4
+ export const styles = StyleSheet.create({
5
+ pressed: {
6
+ opacity: tokens["opacity-pressed"],
7
+ },
8
+ });
@@ -0,0 +1,32 @@
1
+ import React from "react";
2
+ import { Keyboard, Pressable, PressableProps } from "react-native";
3
+ import { styles } from "./SelectPressable.style";
4
+ import { SelectInternalPickerProps } from "../../types";
5
+ import { useIsScreenReaderEnabled } from "../../../hooks";
6
+
7
+ type SelectPressableProps = Pick<SelectInternalPickerProps, "children"> &
8
+ Pick<PressableProps, "onPress">;
9
+
10
+ /**
11
+ * Switches between Pressable with pressed styling and a fragment when a
12
+ * screen-reader is being used to avoid screen-readers from ignoring the press
13
+ * on the MenuView
14
+ */
15
+ export function SelectPressable({
16
+ children,
17
+ onPress,
18
+ }: SelectPressableProps): JSX.Element {
19
+ const isScreenReaderEnabled = useIsScreenReaderEnabled();
20
+
21
+ if (isScreenReaderEnabled) return <>{children}</>;
22
+
23
+ return (
24
+ <Pressable
25
+ style={({ pressed }) => [pressed && styles.pressed]}
26
+ onPressIn={Keyboard.dismiss}
27
+ onPress={onPress}
28
+ >
29
+ {children}
30
+ </Pressable>
31
+ );
32
+ }
@@ -0,0 +1 @@
1
+ export * from "./SelectPressable";