@widergy/mobile-ui 1.47.0 → 1.48.1
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.
- package/CHANGELOG.md +14 -0
- package/README.md +1 -0
- package/lib/components/CaptionLabel/README.md +30 -8
- package/lib/components/CaptionLabel/index.js +2 -1
- package/lib/components/CaptionLabel/propTypes.js +1 -0
- package/lib/components/Checkbox/README.md +93 -25
- package/lib/components/Checkbox/index.js +14 -1
- package/lib/components/Checkbox/propTypes.js +1 -0
- package/lib/components/Label/index.js +2 -1
- package/lib/components/Label/propTypes.js +1 -0
- package/lib/components/RadioGroup/components/RadioButton/index.js +29 -18
- package/lib/components/RadioGroup/index.js +19 -5
- package/lib/components/Touchable/index.js +3 -1
- package/lib/components/Touchable/propTypes.js +1 -0
- package/lib/components/UTBadge/index.js +3 -1
- package/lib/components/UTBaseInputField/README.md +41 -19
- package/lib/components/UTBaseInputField/components/ActionAdornment/index.js +10 -3
- package/lib/components/UTBaseInputField/components/BadgeAdornment/index.js +6 -1
- package/lib/components/UTBaseInputField/components/IconAdornment/index.js +8 -1
- package/lib/components/UTBaseInputField/components/PrefixAdornment/index.js +8 -2
- package/lib/components/UTBaseInputField/components/SuffixAdornment/index.js +6 -1
- package/lib/components/UTBaseInputField/components/TooltipAdornment/index.js +16 -3
- package/lib/components/UTBaseInputField/index.js +15 -4
- package/lib/components/UTBottomSheet/README.md +94 -23
- package/lib/components/UTBottomSheet/index.js +27 -4
- package/lib/components/UTButton/index.js +18 -4
- package/lib/components/UTButton/proptypes.js +1 -0
- package/lib/components/UTCheckBox/README.md +47 -0
- package/lib/components/UTCheckBox/index.js +24 -3
- package/lib/components/UTCheckBox/proptypes.js +1 -0
- package/lib/components/UTCheckList/README.MD +63 -0
- package/lib/components/UTCheckList/index.js +25 -2
- package/lib/components/UTCheckList/proptypes.js +1 -0
- package/lib/components/UTDetailDrawer/README.md +60 -10
- package/lib/components/UTDetailDrawer/index.js +11 -1
- package/lib/components/UTDetailDrawer/propTypes.js +1 -0
- package/lib/components/UTFieldLabel/README.md +99 -0
- package/lib/components/UTFieldLabel/index.js +19 -2
- package/lib/components/UTIcon/README.md +25 -2
- package/lib/components/UTIcon/index.js +3 -1
- package/lib/components/UTLabel/README.md +26 -0
- package/lib/components/UTLabel/constants.js +0 -10
- package/lib/components/UTLabel/index.js +2 -0
- package/lib/components/UTLabel/proptypes.js +1 -0
- package/lib/components/UTLabel/utils.js +4 -22
- package/lib/components/UTMenu/README.md +275 -0
- package/lib/components/UTMenu/components/ListView/index.js +5 -3
- package/lib/components/UTMenu/components/ListView/proptypes.js +2 -1
- package/lib/components/UTMenu/components/MenuOption/index.js +5 -3
- package/lib/components/UTMenu/index.js +18 -3
- package/lib/components/UTMenu/proptypes.js +2 -1
- package/lib/components/UTModal/README.md +193 -0
- package/lib/components/UTModal/index.js +22 -2
- package/lib/components/UTModal/proptypes.js +1 -0
- package/lib/components/UTPhoneInput/index.js +25 -2
- package/lib/components/UTRoundView/README.md +158 -0
- package/lib/components/UTRoundView/index.js +12 -1
- package/lib/components/UTRoundView/propTypes.js +4 -2
- package/lib/components/UTSearchField/README.md +64 -14
- package/lib/components/UTSearchField/index.js +3 -1
- package/lib/components/UTSearchField/proptypes.js +2 -1
- package/lib/components/UTSelect/versions/V0/README.md +216 -0
- package/lib/components/UTSelect/versions/V0/componentes/MultipleItem/index.js +4 -2
- package/lib/components/UTSelect/versions/V0/index.js +5 -2
- package/lib/components/UTSelect/versions/V0/proptypes.js +2 -1
- package/lib/components/UTSelect/versions/V1/README.md +94 -0
- package/lib/components/UTSelect/versions/V1/index.js +28 -6
- package/lib/components/UTSelect/versions/V1/proptypes.js +1 -0
- package/lib/components/UTSelectableCard/README.md +85 -0
- package/lib/components/UTSelectableCard/index.js +52 -4
- package/lib/components/UTTextInput/versions/V0/components/BaseInput/index.js +5 -1
- package/lib/components/UTTextInput/versions/V0/components/InputLabel/index.js +4 -1
- package/lib/components/UTTextInput/versions/V0/flavors/FilledInput/index.js +9 -1
- package/lib/components/UTTextInput/versions/V0/flavors/OutlinedInput/index.js +9 -1
- package/lib/components/UTTextInput/versions/V0/flavors/StandardInput/index.js +9 -1
- package/lib/components/UTTextInput/versions/V1/components/TextInputField/index.js +3 -0
- package/lib/components/UTTextInput/versions/V1/index.js +20 -3
- package/lib/components/UTTooltip/README.md +99 -0
- package/lib/components/UTTooltip/index.js +2 -0
- package/lib/components/UTTooltip/proptypes.js +2 -1
- package/lib/components/UTValidation/index.js +26 -4
- package/lib/constants/testIds.js +44 -0
- package/package.json +1 -1
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
| Name | Type | Default | Description |
|
|
10
10
|
| -------------- | ------ | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
11
11
|
| colorTheme | string | 'dark' | Color theme to style the `UTLabel`. It must be defined in the project theme. [Example](#colortheme). |
|
|
12
|
+
| dataTestId | string | | Test ID for automated testing. |
|
|
12
13
|
| field | any | {} | Field configuration for advanced customization. |
|
|
13
14
|
| markdownStyles | object | {} | Custom styles for rendering markdown content. This allows overriding default markdown styles like lists, links, etc. |
|
|
14
15
|
| numberOfLines | number | | Limits the number of lines displayed in the label. When set, `ellipsizeMode` is automatically set to 'tail'. |
|
|
@@ -20,6 +21,20 @@
|
|
|
20
21
|
| weight | string | 'regular' | Defines the font weight to be used. For a list of available options, check [here](#weights). |
|
|
21
22
|
| withMarkdown | bool | false | Indicates whether or not to render text inside as markdown. |
|
|
22
23
|
|
|
24
|
+
## Test IDs
|
|
25
|
+
|
|
26
|
+
When `dataTestId` is provided, the component applies the test ID to its element:
|
|
27
|
+
|
|
28
|
+
| Element | Test ID | Condition |
|
|
29
|
+
| ------- | --------------- | --------------------------------- |
|
|
30
|
+
| Label | `${dataTestId}` | Always when `dataTestId` provided |
|
|
31
|
+
|
|
32
|
+
### Test ID Details
|
|
33
|
+
|
|
34
|
+
- **Label**: The main text element that displays the content
|
|
35
|
+
|
|
36
|
+
The UTLabel component applies the test ID directly to its root text element, making it easy to identify and interact with in automated tests.
|
|
37
|
+
|
|
23
38
|
## Color Themes
|
|
24
39
|
|
|
25
40
|
The `color` prop must be one of these values:
|
|
@@ -97,3 +112,14 @@ For all others: `shade05`
|
|
|
97
112
|
```jsx
|
|
98
113
|
<UTLabel withMarkdown>{`This is **bold** and this is *italic* text.`}</UTLabel>
|
|
99
114
|
```
|
|
115
|
+
|
|
116
|
+
### Example with Test IDs
|
|
117
|
+
|
|
118
|
+
```jsx
|
|
119
|
+
<UTLabel dataTestId="userWelcome" variant="title2" colorTheme="accent">
|
|
120
|
+
Welcome back, John!
|
|
121
|
+
</UTLabel>
|
|
122
|
+
|
|
123
|
+
// Generated test IDs:
|
|
124
|
+
// userWelcome (label element)
|
|
125
|
+
```
|
|
@@ -35,13 +35,3 @@ export const WEIGHTS = {
|
|
|
35
35
|
extrabold: 800,
|
|
36
36
|
black: 900
|
|
37
37
|
};
|
|
38
|
-
|
|
39
|
-
export const MARKDOWN_FORMATTING = {
|
|
40
|
-
BR_SEQUENCE_REGEX: /(<br\s*\/?>)+/gi,
|
|
41
|
-
BR_TAG_REGEX: /<br\s*\/?>/gi,
|
|
42
|
-
CARRIAGE_RETURN_REGEX: /\r\n|\r/g,
|
|
43
|
-
HR_TAG_REGEX: /<\s*hr\s*\/?>/gi,
|
|
44
|
-
LINE_BREAK_MARK: '\n\u200b',
|
|
45
|
-
LINE_BREAKS_REGEX: /\n+/g,
|
|
46
|
-
NORMALIZED_HR: '\n\n---'
|
|
47
|
-
};
|
|
@@ -11,6 +11,7 @@ import { DEFAULT_PROPS, propTypes } from './proptypes';
|
|
|
11
11
|
const UTLabel = ({
|
|
12
12
|
children,
|
|
13
13
|
colorTheme,
|
|
14
|
+
dataTestId,
|
|
14
15
|
field,
|
|
15
16
|
markdownStyles,
|
|
16
17
|
numberOfLines,
|
|
@@ -38,6 +39,7 @@ const UTLabel = ({
|
|
|
38
39
|
{...additionalProps}
|
|
39
40
|
onLinkPress={onLinkPress}
|
|
40
41
|
onTextLayout={onTextLayout}
|
|
42
|
+
testID={dataTestId}
|
|
41
43
|
style={
|
|
42
44
|
withMarkdown
|
|
43
45
|
? { ...markdownStyles, body: labelStyles, paragraph: { marginTop: 0, marginBottom: 0 } }
|
|
@@ -1,23 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const {
|
|
4
|
-
BR_SEQUENCE_REGEX,
|
|
5
|
-
BR_TAG_REGEX,
|
|
6
|
-
CARRIAGE_RETURN_REGEX,
|
|
7
|
-
HR_TAG_REGEX,
|
|
8
|
-
LINE_BREAK_MARK,
|
|
9
|
-
LINE_BREAKS_REGEX,
|
|
10
|
-
NORMALIZED_HR
|
|
11
|
-
} = MARKDOWN_FORMATTING;
|
|
12
|
-
|
|
13
|
-
export const markdownFormat = content => {
|
|
14
|
-
return content
|
|
1
|
+
export const markdownFormat = content =>
|
|
2
|
+
content
|
|
15
3
|
?.toString()
|
|
16
|
-
.replace(
|
|
17
|
-
.replace(
|
|
18
|
-
.replace(BR_SEQUENCE_REGEX, match => {
|
|
19
|
-
const count = match.match(BR_TAG_REGEX)?.length || 0;
|
|
20
|
-
return LINE_BREAK_MARK.repeat(count);
|
|
21
|
-
})
|
|
22
|
-
.replace(HR_TAG_REGEX, NORMALIZED_HR);
|
|
23
|
-
};
|
|
4
|
+
.replace(/(<\s*br\s*\/?>|\n)/gi, '\n\n')
|
|
5
|
+
.replace(/(<\s*hr\s*\/?>)/gi, '\n\n---') || null;
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
# UTMenu
|
|
2
|
+
|
|
3
|
+
> **⚠️ AI-Generated Documentation Disclaimer**
|
|
4
|
+
> This documentation has been generated with AI assistance and has not been thoroughly reviewed by a developer. Please verify implementation details and usage examples before relying on them in production code.
|
|
5
|
+
|
|
6
|
+
A flexible dropdown menu component that provides a modal overlay with selectable options. Features customizable positioning, search functionality, Google attribution support, and comprehensive test ID support.
|
|
7
|
+
|
|
8
|
+
## Props
|
|
9
|
+
|
|
10
|
+
| NAME | TYPE | REQUIRED | DESCRIPTION |
|
|
11
|
+
| ----------------------- | --------- | -------- | ---------------------------------------------------------------------------------------------------------- |
|
|
12
|
+
| options | array | Yes | Array of option objects. Each option must have `id`, `label`, and optionally `value`, `action` properties. |
|
|
13
|
+
| children | node | Yes | The trigger element that will open the menu when pressed. |
|
|
14
|
+
| onPress | func | No | Callback function called when an option is selected. Receives the selected option. |
|
|
15
|
+
| selectedOption | object | No | Currently selected option object (for single selection) or array of values (for multiple). |
|
|
16
|
+
| onOpen | func | No | Callback function called when the menu opens. |
|
|
17
|
+
| onClose | func | No | Callback function called when the menu closes. |
|
|
18
|
+
| disabled | bool | No | Whether the menu trigger is disabled. |
|
|
19
|
+
| fullWidth | bool | No | Whether the menu should match the width of the trigger element. |
|
|
20
|
+
| verticalOffset | number | No | Vertical offset for menu positioning relative to trigger. Default: 0. |
|
|
21
|
+
| horizontalOffset | number | No | Horizontal offset for menu positioning relative to trigger. Default: 0. |
|
|
22
|
+
| avoidOverlappingAnchor | bool | No | Whether to avoid overlapping the trigger element. Default: true. |
|
|
23
|
+
| withoutOpacity | bool | No | Whether to disable opacity change on trigger press. |
|
|
24
|
+
| isMultiple | bool | No | Enables multiple selection mode. |
|
|
25
|
+
| maxHeight | number | No | Maximum height for the options list. |
|
|
26
|
+
| withAutocomplete | bool | No | Enables search/filter functionality with text input. |
|
|
27
|
+
| autoCompletePlaceholder | string | No | Placeholder text for the search input. |
|
|
28
|
+
| onQueryChange | func | No | Callback function called when search query changes. |
|
|
29
|
+
| filterOptions | bool | No | Whether to filter options based on search query. Default: true. |
|
|
30
|
+
| MenuOptionComponent | component | No | Custom component to render menu options. Default: MenuOption. |
|
|
31
|
+
| ItemSeparatorComponent | component | No | Component to render between menu options. |
|
|
32
|
+
| ListEmptyComponent | component | No | Component to render when no options are available. |
|
|
33
|
+
| withGoogleAttribution | bool | No | Whether to show Google attribution (for location services). Default: false. |
|
|
34
|
+
| styles | object | No | Custom styles for menu components. |
|
|
35
|
+
| dataTestId | string | No | Unique identifier for testing purposes. Creates hierarchical test IDs for child elements. |
|
|
36
|
+
|
|
37
|
+
## Option Object Structure
|
|
38
|
+
|
|
39
|
+
Each option in the `options` array should have the following structure:
|
|
40
|
+
|
|
41
|
+
```js
|
|
42
|
+
{
|
|
43
|
+
id: string | number, // Unique identifier for the option
|
|
44
|
+
label: string, // Display text for the option
|
|
45
|
+
value?: any, // Optional value (defaults to id if not provided)
|
|
46
|
+
action?: function // Optional custom action function
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Usage Examples
|
|
51
|
+
|
|
52
|
+
### Basic Menu
|
|
53
|
+
|
|
54
|
+
```jsx
|
|
55
|
+
import React, { useState } from 'react';
|
|
56
|
+
import { Text, View } from 'react-native';
|
|
57
|
+
import UTMenu from '@widergy/mobile-ui/lib/components/UTMenu';
|
|
58
|
+
|
|
59
|
+
const BasicExample = () => {
|
|
60
|
+
const [selectedOption, setSelectedOption] = useState(null);
|
|
61
|
+
|
|
62
|
+
const options = [
|
|
63
|
+
{ id: '1', label: 'Option 1' },
|
|
64
|
+
{ id: '2', label: 'Option 2' },
|
|
65
|
+
{ id: '3', label: 'Option 3' }
|
|
66
|
+
];
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<UTMenu options={options} onPress={setSelectedOption} selectedOption={selectedOption?.id}>
|
|
70
|
+
<View style={{ padding: 10, backgroundColor: '#f0f0f0' }}>
|
|
71
|
+
<Text>{selectedOption?.label || 'Select an option'}</Text>
|
|
72
|
+
</View>
|
|
73
|
+
</UTMenu>
|
|
74
|
+
);
|
|
75
|
+
};
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Menu with Search
|
|
79
|
+
|
|
80
|
+
```jsx
|
|
81
|
+
const SearchableMenu = () => {
|
|
82
|
+
const [selected, setSelected] = useState(null);
|
|
83
|
+
const [query, setQuery] = useState('');
|
|
84
|
+
|
|
85
|
+
const options = [
|
|
86
|
+
{ id: 'apple', label: 'Apple' },
|
|
87
|
+
{ id: 'banana', label: 'Banana' },
|
|
88
|
+
{ id: 'cherry', label: 'Cherry' },
|
|
89
|
+
{ id: 'date', label: 'Date' },
|
|
90
|
+
{ id: 'elderberry', label: 'Elderberry' }
|
|
91
|
+
];
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<UTMenu
|
|
95
|
+
options={options}
|
|
96
|
+
onPress={setSelected}
|
|
97
|
+
selectedOption={selected?.id}
|
|
98
|
+
withAutocomplete
|
|
99
|
+
autoCompletePlaceholder="Search fruits..."
|
|
100
|
+
onQueryChange={setQuery}
|
|
101
|
+
>
|
|
102
|
+
<View style={{ padding: 12, borderWidth: 1, borderColor: '#ccc' }}>
|
|
103
|
+
<Text>{selected?.label || 'Choose a fruit'}</Text>
|
|
104
|
+
</View>
|
|
105
|
+
</UTMenu>
|
|
106
|
+
);
|
|
107
|
+
};
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Multiple Selection Menu
|
|
111
|
+
|
|
112
|
+
```jsx
|
|
113
|
+
const MultipleSelectionMenu = () => {
|
|
114
|
+
const [selectedValues, setSelectedValues] = useState([]);
|
|
115
|
+
|
|
116
|
+
const skillOptions = [
|
|
117
|
+
{ id: 'react', label: 'React', value: 'react' },
|
|
118
|
+
{ id: 'vue', label: 'Vue.js', value: 'vue' },
|
|
119
|
+
{ id: 'angular', label: 'Angular', value: 'angular' },
|
|
120
|
+
{ id: 'svelte', label: 'Svelte', value: 'svelte' }
|
|
121
|
+
];
|
|
122
|
+
|
|
123
|
+
return (
|
|
124
|
+
<UTMenu
|
|
125
|
+
options={skillOptions}
|
|
126
|
+
onPress={option => {
|
|
127
|
+
const isSelected = selectedValues.includes(option.value);
|
|
128
|
+
if (isSelected) {
|
|
129
|
+
setSelectedValues(prev => prev.filter(val => val !== option.value));
|
|
130
|
+
} else {
|
|
131
|
+
setSelectedValues(prev => [...prev, option.value]);
|
|
132
|
+
}
|
|
133
|
+
}}
|
|
134
|
+
selectedOption={selectedValues}
|
|
135
|
+
isMultiple
|
|
136
|
+
>
|
|
137
|
+
<View style={{ padding: 12, borderWidth: 1 }}>
|
|
138
|
+
<Text>{selectedValues.length > 0 ? `Selected: ${selectedValues.join(', ')}` : 'Select skills'}</Text>
|
|
139
|
+
</View>
|
|
140
|
+
</UTMenu>
|
|
141
|
+
);
|
|
142
|
+
};
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Custom Positioning
|
|
146
|
+
|
|
147
|
+
```jsx
|
|
148
|
+
const CustomPositionMenu = () => {
|
|
149
|
+
return (
|
|
150
|
+
<UTMenu
|
|
151
|
+
options={options}
|
|
152
|
+
onPress={handlePress}
|
|
153
|
+
verticalOffset={-5}
|
|
154
|
+
horizontalOffset={10}
|
|
155
|
+
fullWidth={false}
|
|
156
|
+
avoidOverlappingAnchor={false}
|
|
157
|
+
>
|
|
158
|
+
<YourTriggerComponent />
|
|
159
|
+
</UTMenu>
|
|
160
|
+
);
|
|
161
|
+
};
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Testing
|
|
165
|
+
|
|
166
|
+
The `UTMenu` component supports comprehensive test ID assignment through the `dataTestId` prop. When provided, it creates a hierarchical structure of test IDs for all interactive and meaningful elements.
|
|
167
|
+
|
|
168
|
+
### Test ID Structure
|
|
169
|
+
|
|
170
|
+
When you provide `dataTestId="provided.testId"`, the following test IDs are automatically generated:
|
|
171
|
+
|
|
172
|
+
| Element | Test ID | When Available |
|
|
173
|
+
| ------------------- | ------------------------------------------- | ------------------------------------ |
|
|
174
|
+
| Menu anchor/trigger | `provided.testId.anchor` | Always (when dataTestId is provided) |
|
|
175
|
+
| Modal overlay | `provided.testId.modal` | When menu is open |
|
|
176
|
+
| Search input | `provided.testId.searchInput` | When withAutocomplete is true |
|
|
177
|
+
| Options list | `provided.testId.list` | When menu is open |
|
|
178
|
+
| Individual option | `provided.testId.list.option.{index}` | For each option when menu is open |
|
|
179
|
+
| Option label | `provided.testId.list.option.{index}.label` | For each option when menu is open |
|
|
180
|
+
| Attribution text | `provided.testId.attribution.text` | When withGoogleAttribution is true |
|
|
181
|
+
| Attribution logo | `provided.testId.attribution.logo` | When withGoogleAttribution is true |
|
|
182
|
+
|
|
183
|
+
**Note:** The search input test ID (`provided.testId.searchInput`) creates additional hierarchical test IDs based on the UTTextInput component's own test ID structure.
|
|
184
|
+
|
|
185
|
+
### Test ID Examples
|
|
186
|
+
|
|
187
|
+
```jsx
|
|
188
|
+
// Basic menu with test IDs
|
|
189
|
+
<UTMenu
|
|
190
|
+
dataTestId="actionMenu"
|
|
191
|
+
options={actionOptions}
|
|
192
|
+
onPress={handleAction}
|
|
193
|
+
>
|
|
194
|
+
<ActionButton />
|
|
195
|
+
</UTMenu>
|
|
196
|
+
|
|
197
|
+
// Creates test IDs:
|
|
198
|
+
// - actionMenu.anchor (trigger button)
|
|
199
|
+
// - actionMenu.modal (when open)
|
|
200
|
+
// - actionMenu.list (options container when open)
|
|
201
|
+
// - actionMenu.list.option.0 (first option when open)
|
|
202
|
+
// - actionMenu.list.option.0.label (first option text when open)
|
|
203
|
+
// - actionMenu.list.option.1 (second option when open)
|
|
204
|
+
// - actionMenu.list.option.1.label (second option text when open)
|
|
205
|
+
|
|
206
|
+
// Searchable menu with test IDs
|
|
207
|
+
<UTMenu
|
|
208
|
+
dataTestId="fruitSelector"
|
|
209
|
+
options={fruitOptions}
|
|
210
|
+
onPress={handleFruitSelection}
|
|
211
|
+
withAutocomplete
|
|
212
|
+
autoCompletePlaceholder="Search fruits..."
|
|
213
|
+
>
|
|
214
|
+
<FruitSelectorTrigger />
|
|
215
|
+
</UTMenu>
|
|
216
|
+
|
|
217
|
+
// Creates test IDs:
|
|
218
|
+
// - fruitSelector.anchor (trigger)
|
|
219
|
+
// - fruitSelector.modal (when open)
|
|
220
|
+
// - fruitSelector.searchInput (search input + UTTextInput sub-elements)
|
|
221
|
+
// - fruitSelector.list (options container)
|
|
222
|
+
// - fruitSelector.list.option.0 (first fruit option)
|
|
223
|
+
// - fruitSelector.list.option.0.label (first fruit option text)
|
|
224
|
+
// - fruitSelector.list.option.1 (second fruit option)
|
|
225
|
+
// - fruitSelector.list.option.1.label (second fruit option text)
|
|
226
|
+
|
|
227
|
+
// Menu with Google attribution and test IDs
|
|
228
|
+
<UTMenu
|
|
229
|
+
dataTestId="locationMenu"
|
|
230
|
+
options={locationOptions}
|
|
231
|
+
onPress={handleLocationSelect}
|
|
232
|
+
withGoogleAttribution
|
|
233
|
+
>
|
|
234
|
+
<LocationTrigger />
|
|
235
|
+
</UTMenu>
|
|
236
|
+
|
|
237
|
+
// Creates test IDs:
|
|
238
|
+
// - locationMenu.anchor (trigger)
|
|
239
|
+
// - locationMenu.modal (when open)
|
|
240
|
+
// - locationMenu.list (options container)
|
|
241
|
+
// - locationMenu.list.option.0 (first location option)
|
|
242
|
+
// - locationMenu.list.option.1 (second location option)
|
|
243
|
+
// - locationMenu.attribution.text (attribution text)
|
|
244
|
+
// - locationMenu.attribution.logo (Google logo)
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
## Custom Menu Option Components
|
|
248
|
+
|
|
249
|
+
You can provide a custom `MenuOptionComponent` that will receive the following props:
|
|
250
|
+
|
|
251
|
+
```jsx
|
|
252
|
+
const CustomMenuOption = ({ onPress, label, selected, item, styles, dataTestId }) => {
|
|
253
|
+
return (
|
|
254
|
+
<TouchableOpacity
|
|
255
|
+
onPress={() => onPress(item)}
|
|
256
|
+
style={[customStyles.option, selected && customStyles.selected]}
|
|
257
|
+
testID={dataTestId}
|
|
258
|
+
>
|
|
259
|
+
<Text testID={dataTestId ? `${dataTestId}.label` : undefined}>{label}</Text>
|
|
260
|
+
</TouchableOpacity>
|
|
261
|
+
);
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
// Usage
|
|
265
|
+
<UTMenu
|
|
266
|
+
MenuOptionComponent={CustomMenuOption}
|
|
267
|
+
options={options}
|
|
268
|
+
onPress={handlePress}
|
|
269
|
+
dataTestId="customMenu"
|
|
270
|
+
>
|
|
271
|
+
<TriggerComponent />
|
|
272
|
+
</UTMenu>;
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
**Note:** When implementing custom menu option components, make sure to handle the `dataTestId` prop properly to maintain the hierarchical test ID structure.
|
|
@@ -31,8 +31,8 @@ class ListView extends PureComponent {
|
|
|
31
31
|
return handleOptionPress(item.action || (() => onPress(item)), item?.value);
|
|
32
32
|
};
|
|
33
33
|
|
|
34
|
-
renderItem = ({ item }) => {
|
|
35
|
-
const { MenuOptionComponent, propStyles } = this.props;
|
|
34
|
+
renderItem = ({ item, index }) => {
|
|
35
|
+
const { MenuOptionComponent, propStyles, dataTestId } = this.props;
|
|
36
36
|
|
|
37
37
|
return (
|
|
38
38
|
<MenuOptionComponent
|
|
@@ -41,12 +41,13 @@ class ListView extends PureComponent {
|
|
|
41
41
|
onPress={this.handleOnPress}
|
|
42
42
|
styles={propStyles}
|
|
43
43
|
item={item}
|
|
44
|
+
dataTestId={dataTestId ? `${dataTestId}.option.${index}` : undefined}
|
|
44
45
|
/>
|
|
45
46
|
);
|
|
46
47
|
};
|
|
47
48
|
|
|
48
49
|
render() {
|
|
49
|
-
const { filteredOptions, ItemSeparatorComponent, ListEmptyComponent } = this.props;
|
|
50
|
+
const { filteredOptions, ItemSeparatorComponent, ListEmptyComponent, dataTestId } = this.props;
|
|
50
51
|
const { style } = this.state;
|
|
51
52
|
|
|
52
53
|
return (
|
|
@@ -58,6 +59,7 @@ class ListView extends PureComponent {
|
|
|
58
59
|
renderItem={this.renderItem}
|
|
59
60
|
ItemSeparatorComponent={ItemSeparatorComponent}
|
|
60
61
|
ListEmptyComponent={ListEmptyComponent}
|
|
62
|
+
testID={dataTestId}
|
|
61
63
|
/>
|
|
62
64
|
);
|
|
63
65
|
}
|
|
@@ -5,7 +5,7 @@ import { TouchableOpacity, Text } from 'react-native';
|
|
|
5
5
|
|
|
6
6
|
import styles from './styles';
|
|
7
7
|
|
|
8
|
-
const MenuOption = ({ onPress, label, selected, styles: propStyles, item }) => (
|
|
8
|
+
const MenuOption = ({ onPress, label, selected, styles: propStyles, item, dataTestId }) => (
|
|
9
9
|
<TouchableOpacity
|
|
10
10
|
onPress={() => onPress(item)}
|
|
11
11
|
style={[
|
|
@@ -14,8 +14,9 @@ const MenuOption = ({ onPress, label, selected, styles: propStyles, item }) => (
|
|
|
14
14
|
selected && styles.selected,
|
|
15
15
|
selected && propStyles?.menuOptionSelected
|
|
16
16
|
]}
|
|
17
|
+
testID={dataTestId}
|
|
17
18
|
>
|
|
18
|
-
<Text>{label}</Text>
|
|
19
|
+
<Text testID={dataTestId ? `${dataTestId}.label` : undefined}>{label}</Text>
|
|
19
20
|
</TouchableOpacity>
|
|
20
21
|
);
|
|
21
22
|
|
|
@@ -25,7 +26,8 @@ MenuOption.propTypes = {
|
|
|
25
26
|
selected: bool,
|
|
26
27
|
styles: ViewPropTypes.style,
|
|
27
28
|
// eslint-disable-next-line react/forbid-prop-types
|
|
28
|
-
item: shape(any)
|
|
29
|
+
item: shape(any),
|
|
30
|
+
dataTestId: string
|
|
29
31
|
};
|
|
30
32
|
|
|
31
33
|
export default memo(MenuOption);
|
|
@@ -13,6 +13,7 @@ import useKeyboardHeight from '../../hooks/useKeyboardHeight';
|
|
|
13
13
|
import UTTextInput from '../UTTextInput';
|
|
14
14
|
import UTLabel from '../UTLabel';
|
|
15
15
|
import Surface from '../Surface';
|
|
16
|
+
import { TEST_ID_CONSTANTS } from '../../constants/testIds';
|
|
16
17
|
|
|
17
18
|
import MenuOption from './components/MenuOption';
|
|
18
19
|
import ListView from './components/ListView';
|
|
@@ -21,6 +22,8 @@ import styles from './styles';
|
|
|
21
22
|
import GoogleLogo from './assets/google_on_white.png';
|
|
22
23
|
import { ATTRIBUTION } from './constants';
|
|
23
24
|
|
|
25
|
+
const { anchor, modal, searchInput, list, attribution, text, logo } = TEST_ID_CONSTANTS;
|
|
26
|
+
|
|
24
27
|
const UTMenu = ({
|
|
25
28
|
autoCompletePlaceholder,
|
|
26
29
|
avoidOverlappingAnchor = true,
|
|
@@ -44,7 +47,8 @@ const UTMenu = ({
|
|
|
44
47
|
verticalOffset = 0,
|
|
45
48
|
withAutocomplete,
|
|
46
49
|
withoutOpacity,
|
|
47
|
-
withGoogleAttribution = false
|
|
50
|
+
withGoogleAttribution = false,
|
|
51
|
+
dataTestId
|
|
48
52
|
}) => {
|
|
49
53
|
const [isOpen, setIsOpen] = useState(false);
|
|
50
54
|
const anchorRef = useRef(null);
|
|
@@ -138,6 +142,7 @@ const UTMenu = ({
|
|
|
138
142
|
activeOpacity={withoutOpacity && 1}
|
|
139
143
|
disabled={disabled}
|
|
140
144
|
onPress={isOpen ? closeMenu : openMenu}
|
|
145
|
+
testID={dataTestId ? `${dataTestId}.${anchor}` : undefined}
|
|
141
146
|
>
|
|
142
147
|
<View onLayout={handleAnchorLayout} pointerEvents="box-only" ref={anchorRef}>
|
|
143
148
|
{children}
|
|
@@ -149,6 +154,7 @@ const UTMenu = ({
|
|
|
149
154
|
onShow={focusSearchInput}
|
|
150
155
|
transparent
|
|
151
156
|
visible={isOpen}
|
|
157
|
+
testID={dataTestId ? `${dataTestId}.${modal}` : undefined}
|
|
152
158
|
>
|
|
153
159
|
<TouchableOpacity onPress={closeMenu} style={styles.overlayTouchable}>
|
|
154
160
|
<View style={styles.overlay} />
|
|
@@ -170,6 +176,7 @@ const UTMenu = ({
|
|
|
170
176
|
}
|
|
171
177
|
placeholder={autoCompletePlaceholder}
|
|
172
178
|
value={query}
|
|
179
|
+
dataTestId={dataTestId ? `${dataTestId}.${searchInput}` : undefined}
|
|
173
180
|
/>
|
|
174
181
|
</View>
|
|
175
182
|
)}
|
|
@@ -187,13 +194,21 @@ const UTMenu = ({
|
|
|
187
194
|
selectedOption={selectedOption}
|
|
188
195
|
usableWindowHeight={usableWindowHeight}
|
|
189
196
|
windowHeight={windowHeight}
|
|
197
|
+
dataTestId={dataTestId ? `${dataTestId}.${list}` : undefined}
|
|
190
198
|
/>
|
|
191
199
|
{withGoogleAttribution && (
|
|
192
200
|
<View style={styles.attribution}>
|
|
193
|
-
<UTLabel
|
|
201
|
+
<UTLabel
|
|
202
|
+
colorTheme="gray"
|
|
203
|
+
variant="small"
|
|
204
|
+
dataTestId={dataTestId ? `${dataTestId}.${attribution}.${text}` : undefined}
|
|
205
|
+
>
|
|
194
206
|
{ATTRIBUTION}
|
|
195
207
|
</UTLabel>
|
|
196
|
-
<Image
|
|
208
|
+
<Image
|
|
209
|
+
source={GoogleLogo}
|
|
210
|
+
testID={dataTestId ? `${dataTestId}.${attribution}.${logo}` : undefined}
|
|
211
|
+
/>
|
|
197
212
|
</View>
|
|
198
213
|
)}
|
|
199
214
|
</KeyboardAvoidingView>
|