@widergy/mobile-ui 1.46.1 → 1.48.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.
- package/CHANGELOG.md +14 -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/index.js +2 -0
- package/lib/components/UTLabel/proptypes.js +1 -0
- 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/UTTabs/README.md +27 -11
- package/lib/components/UTTabs/index.js +139 -24
- package/lib/components/UTTabs/styles.js +104 -58
- 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
|
@@ -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>
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# UTModal
|
|
2
|
+
|
|
3
|
+
## Description
|
|
4
|
+
|
|
5
|
+
> ⚠️ **AI-GENERATED DOCUMENTATION**
|
|
6
|
+
> This documentation was entirely generated by an AI assistant and has NOT been reviewed by human developers. The test ID implementation details, patterns, examples, and usage instructions should be thoroughly verified before use in production. Please validate all functionality and testing approaches with your development team.
|
|
7
|
+
|
|
8
|
+
`UTModal` is a customizable modal component that provides a flexible overlay interface for displaying content, dialogs, and forms. It supports features like background images, loading states, custom buttons, and keyboard handling.
|
|
9
|
+
|
|
10
|
+
## Props
|
|
11
|
+
|
|
12
|
+
| Name | Type | Default | Description |
|
|
13
|
+
| --------------------- | ------- | ------------ | ------------------------------------------------------------------------- |
|
|
14
|
+
| acceptButton | object | | Configuration for the accept/primary button. See Button Object section. |
|
|
15
|
+
| backgroundImg | number | | Background image source for the modal. |
|
|
16
|
+
| backgroundStyles | object | | Styles to apply to the background image. |
|
|
17
|
+
| cancelButton | object | | Configuration for the cancel/secondary button. See Button Object section. |
|
|
18
|
+
| children | node | | Content to display inside the modal. |
|
|
19
|
+
| closeButtonColorTheme | string | | Color theme for the close button. |
|
|
20
|
+
| dataTestId | string | 'modal' | Test ID for automated testing. Enables hierarchical test ID structure. |
|
|
21
|
+
| disableTouchable | bool | false | If true, disables the touchable overlay that dismisses the keyboard. |
|
|
22
|
+
| hideCloseButton | bool | false | If true, hides the close button. |
|
|
23
|
+
| hideSeparatorBar | bool | false | If true, hides the separator bar above buttons. |
|
|
24
|
+
| imageComponent | element | | Custom image component to display in the modal. |
|
|
25
|
+
| imageStyles | object | | Styles to apply to the image component container. |
|
|
26
|
+
| loading | bool | false | If true, shows a loading overlay. |
|
|
27
|
+
| loadingText | string | | Text to display during loading state. |
|
|
28
|
+
| modalBackgroundColor | string | | Custom background color for the modal. |
|
|
29
|
+
| modalStyles | object | | Custom styles to apply to the modal container. |
|
|
30
|
+
| onRequestClose | func | () => {} | Function called when the modal should be closed. |
|
|
31
|
+
| subtitle | string | | Subtitle text to display below the title. |
|
|
32
|
+
| subtitleProps | object | {} | Additional props to pass to the subtitle UTLabel component. |
|
|
33
|
+
| title | string | | Title text to display in the modal header. |
|
|
34
|
+
| visible | bool | **required** | Controls whether the modal is visible. |
|
|
35
|
+
| style | object | | Custom styles to apply to the modal. |
|
|
36
|
+
|
|
37
|
+
## Test IDs
|
|
38
|
+
|
|
39
|
+
When `dataTestId` is provided (defaults to 'modal'), the component creates a hierarchical test ID structure:
|
|
40
|
+
|
|
41
|
+
| Element | Test ID | Condition |
|
|
42
|
+
| ------------- | ---------------------------- | ------------------------------------ |
|
|
43
|
+
| Modal | `${dataTestId}` | Always (defaults to 'modal') |
|
|
44
|
+
| Title | `${dataTestId}.title` | When `title` prop is provided |
|
|
45
|
+
| Subtitle | `${dataTestId}.subtitle` | When `subtitle` prop is provided |
|
|
46
|
+
| Close Button | `${dataTestId}.closeButton` | When close button is visible |
|
|
47
|
+
| Cancel Button | `${dataTestId}.cancelButton` | When `cancelButton` prop is provided |
|
|
48
|
+
| Accept Button | `${dataTestId}.acceptButton` | When `acceptButton` prop is provided |
|
|
49
|
+
|
|
50
|
+
### Test ID Structure Details
|
|
51
|
+
|
|
52
|
+
- **Modal**: The main Modal component that controls visibility and overlay behavior
|
|
53
|
+
- **Title**: The main title text displayed in the modal header (handled by UTLabel)
|
|
54
|
+
- **Subtitle**: The subtitle text displayed below the title (handled by UTLabel)
|
|
55
|
+
- **Close Button**: The X button used to close the modal (handled by UTButton)
|
|
56
|
+
- **Cancel Button**: The secondary/cancel button (handled by UTButton)
|
|
57
|
+
- **Accept Button**: The primary/accept button (handled by UTButton)
|
|
58
|
+
|
|
59
|
+
Each button and label follows their respective component test ID patterns with their own hierarchical structure.
|
|
60
|
+
|
|
61
|
+
**Default Test ID**: Since only one modal should be visible at a time, the `dataTestId` prop defaults to 'modal'. This ensures that every UTModal instance has a consistent, predictable test ID structure available, while still allowing for custom test IDs when multiple modal types need to be distinguished.
|
|
62
|
+
|
|
63
|
+
### Button Object
|
|
64
|
+
|
|
65
|
+
The `acceptButton` and `cancelButton` props are objects with the following structure:
|
|
66
|
+
|
|
67
|
+
| Name | Type | Description |
|
|
68
|
+
| ---------- | ------ | -------------------------------------------- |
|
|
69
|
+
| colorTheme | string | Color theme for the button. |
|
|
70
|
+
| disabled | bool | Whether the button is disabled. |
|
|
71
|
+
| onPress | func | Function to call when the button is pressed. |
|
|
72
|
+
| text | string | Text to display on the button. |
|
|
73
|
+
|
|
74
|
+
## Usage
|
|
75
|
+
|
|
76
|
+
### Basic Example
|
|
77
|
+
|
|
78
|
+
```jsx
|
|
79
|
+
import React from 'react';
|
|
80
|
+
import { UTModal, UTLabel } from '@widergy/mobile-ui';
|
|
81
|
+
|
|
82
|
+
const Example = () => {
|
|
83
|
+
const [visible, setVisible] = React.useState(false);
|
|
84
|
+
|
|
85
|
+
const handleClose = () => setVisible(false);
|
|
86
|
+
const handleAccept = () => {
|
|
87
|
+
// Perform action
|
|
88
|
+
setVisible(false);
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
<UTModal
|
|
93
|
+
visible={visible}
|
|
94
|
+
title="Confirm Action"
|
|
95
|
+
subtitle="Are you sure you want to proceed?"
|
|
96
|
+
onRequestClose={handleClose}
|
|
97
|
+
acceptButton={{
|
|
98
|
+
text: 'Yes, Continue',
|
|
99
|
+
onPress: handleAccept
|
|
100
|
+
}}
|
|
101
|
+
cancelButton={{
|
|
102
|
+
text: 'Cancel',
|
|
103
|
+
onPress: handleClose
|
|
104
|
+
}}
|
|
105
|
+
>
|
|
106
|
+
<UTLabel>This action cannot be undone.</UTLabel>
|
|
107
|
+
</UTModal>
|
|
108
|
+
);
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
export default Example;
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### With Custom Test ID
|
|
115
|
+
|
|
116
|
+
```jsx
|
|
117
|
+
import React from 'react';
|
|
118
|
+
import { UTModal, UTLabel } from '@widergy/mobile-ui';
|
|
119
|
+
|
|
120
|
+
const TestableExample = () => {
|
|
121
|
+
const [visible, setVisible] = React.useState(false);
|
|
122
|
+
|
|
123
|
+
const handleClose = () => setVisible(false);
|
|
124
|
+
const handleDelete = () => {
|
|
125
|
+
// Perform delete action
|
|
126
|
+
setVisible(false);
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
return (
|
|
130
|
+
<UTModal
|
|
131
|
+
dataTestId="deleteConfirmModal"
|
|
132
|
+
visible={visible}
|
|
133
|
+
title="Delete Item"
|
|
134
|
+
subtitle="This action cannot be undone"
|
|
135
|
+
onRequestClose={handleClose}
|
|
136
|
+
acceptButton={{
|
|
137
|
+
text: 'Delete',
|
|
138
|
+
onPress: handleDelete,
|
|
139
|
+
colorTheme: 'danger'
|
|
140
|
+
}}
|
|
141
|
+
cancelButton={{
|
|
142
|
+
text: 'Cancel',
|
|
143
|
+
onPress: handleClose
|
|
144
|
+
}}
|
|
145
|
+
>
|
|
146
|
+
<UTLabel>Are you sure you want to delete this item?</UTLabel>
|
|
147
|
+
</UTModal>
|
|
148
|
+
);
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
// Generated test IDs:
|
|
152
|
+
// deleteConfirmModal (main modal)
|
|
153
|
+
// deleteConfirmModal.title (title text)
|
|
154
|
+
// deleteConfirmModal.subtitle (subtitle text)
|
|
155
|
+
// deleteConfirmModal.closeButton (close X button)
|
|
156
|
+
// deleteConfirmModal.cancelButton (cancel button)
|
|
157
|
+
// deleteConfirmModal.acceptButton (delete button)
|
|
158
|
+
|
|
159
|
+
export default TestableExample;
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### With Default Test ID
|
|
163
|
+
|
|
164
|
+
```jsx
|
|
165
|
+
import React from 'react';
|
|
166
|
+
import { UTModal, UTLabel } from '@widergy/mobile-ui';
|
|
167
|
+
|
|
168
|
+
const DefaultTestIdExample = () => {
|
|
169
|
+
const [visible, setVisible] = React.useState(false);
|
|
170
|
+
|
|
171
|
+
return (
|
|
172
|
+
<UTModal
|
|
173
|
+
visible={visible}
|
|
174
|
+
title="Default Modal"
|
|
175
|
+
onRequestClose={() => setVisible(false)}
|
|
176
|
+
acceptButton={{
|
|
177
|
+
text: 'OK',
|
|
178
|
+
onPress: () => setVisible(false)
|
|
179
|
+
}}
|
|
180
|
+
>
|
|
181
|
+
<UTLabel>This modal uses the default test ID 'modal'.</UTLabel>
|
|
182
|
+
</UTModal>
|
|
183
|
+
);
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
// Generated test IDs (using default):
|
|
187
|
+
// modal (main modal)
|
|
188
|
+
// modal.title (title text)
|
|
189
|
+
// modal.closeButton (close X button)
|
|
190
|
+
// modal.acceptButton (OK button)
|
|
191
|
+
|
|
192
|
+
export default DefaultTestIdExample;
|
|
193
|
+
```
|