@patternfly/react-core 4.227.7 → 4.228.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 +27 -0
- package/dist/esm/components/Menu/MenuItem.d.ts +3 -1
- package/dist/esm/components/Menu/MenuItem.d.ts.map +1 -1
- package/dist/esm/components/Menu/MenuItem.js +2 -2
- package/dist/esm/components/Menu/MenuItem.js.map +1 -1
- package/dist/esm/components/MenuToggle/MenuToggle.d.ts +6 -12
- package/dist/esm/components/MenuToggle/MenuToggle.d.ts.map +1 -1
- package/dist/esm/components/MenuToggle/MenuToggle.js +12 -8
- package/dist/esm/components/MenuToggle/MenuToggle.js.map +1 -1
- package/dist/esm/components/Tabs/OverflowTab.d.ts +16 -0
- package/dist/esm/components/Tabs/OverflowTab.d.ts.map +1 -0
- package/dist/esm/components/Tabs/OverflowTab.js +74 -0
- package/dist/esm/components/Tabs/OverflowTab.js.map +1 -0
- package/dist/esm/components/Tabs/Tabs.d.ts +12 -0
- package/dist/esm/components/Tabs/Tabs.d.ts.map +1 -1
- package/dist/esm/components/Tabs/Tabs.js +36 -13
- package/dist/esm/components/Tabs/Tabs.js.map +1 -1
- package/dist/esm/components/TextInputGroup/TextInputGroup.d.ts +2 -0
- package/dist/esm/components/TextInputGroup/TextInputGroup.d.ts.map +1 -1
- package/dist/esm/components/TextInputGroup/TextInputGroup.js +2 -2
- package/dist/esm/components/TextInputGroup/TextInputGroup.js.map +1 -1
- package/dist/esm/components/TextInputGroup/TextInputGroupMain.d.ts +1 -1
- package/dist/esm/components/TextInputGroup/TextInputGroupMain.d.ts.map +1 -1
- package/dist/esm/components/TreeView/TreeView.d.ts +2 -0
- package/dist/esm/components/TreeView/TreeView.d.ts.map +1 -1
- package/dist/esm/components/TreeView/TreeView.js +3 -3
- package/dist/esm/components/TreeView/TreeView.js.map +1 -1
- package/dist/esm/components/TreeView/TreeViewListItem.d.ts +2 -0
- package/dist/esm/components/TreeView/TreeViewListItem.d.ts.map +1 -1
- package/dist/esm/components/TreeView/TreeViewListItem.js +23 -14
- package/dist/esm/components/TreeView/TreeViewListItem.js.map +1 -1
- package/dist/js/components/Menu/MenuItem.d.ts +3 -1
- package/dist/js/components/Menu/MenuItem.d.ts.map +1 -1
- package/dist/js/components/Menu/MenuItem.js +2 -2
- package/dist/js/components/Menu/MenuItem.js.map +1 -1
- package/dist/js/components/MenuToggle/MenuToggle.d.ts +6 -12
- package/dist/js/components/MenuToggle/MenuToggle.d.ts.map +1 -1
- package/dist/js/components/MenuToggle/MenuToggle.js +12 -8
- package/dist/js/components/MenuToggle/MenuToggle.js.map +1 -1
- package/dist/js/components/Tabs/OverflowTab.d.ts +16 -0
- package/dist/js/components/Tabs/OverflowTab.d.ts.map +1 -0
- package/dist/js/components/Tabs/OverflowTab.js +78 -0
- package/dist/js/components/Tabs/OverflowTab.js.map +1 -0
- package/dist/js/components/Tabs/Tabs.d.ts +12 -0
- package/dist/js/components/Tabs/Tabs.d.ts.map +1 -1
- package/dist/js/components/Tabs/Tabs.js +36 -13
- package/dist/js/components/Tabs/Tabs.js.map +1 -1
- package/dist/js/components/TextInputGroup/TextInputGroup.d.ts +2 -0
- package/dist/js/components/TextInputGroup/TextInputGroup.d.ts.map +1 -1
- package/dist/js/components/TextInputGroup/TextInputGroup.js +2 -2
- package/dist/js/components/TextInputGroup/TextInputGroup.js.map +1 -1
- package/dist/js/components/TextInputGroup/TextInputGroupMain.d.ts +1 -1
- package/dist/js/components/TextInputGroup/TextInputGroupMain.d.ts.map +1 -1
- package/dist/js/components/TreeView/TreeView.d.ts +2 -0
- package/dist/js/components/TreeView/TreeView.d.ts.map +1 -1
- package/dist/js/components/TreeView/TreeView.js +3 -3
- package/dist/js/components/TreeView/TreeView.js.map +1 -1
- package/dist/js/components/TreeView/TreeViewListItem.d.ts +2 -0
- package/dist/js/components/TreeView/TreeViewListItem.d.ts.map +1 -1
- package/dist/js/components/TreeView/TreeViewListItem.js +23 -14
- package/dist/js/components/TreeView/TreeViewListItem.js.map +1 -1
- package/dist/styles/base-no-reset.css +9 -0
- package/dist/styles/base.css +9 -0
- package/dist/umd/react-core.css +21 -31
- package/dist/umd/react-core.js +149 -48
- package/dist/umd/react-core.min.css +21 -31
- package/dist/umd/react-core.min.js +5 -5
- package/package.json +6 -6
- package/src/components/Menu/MenuItem.tsx +4 -0
- package/src/components/MenuToggle/MenuToggle.tsx +63 -25
- package/src/components/Page/examples/Page.md +10 -615
- package/src/components/Page/examples/PageCenteredSection.tsx +76 -0
- package/src/components/Page/examples/PageGroupSection.tsx +106 -0
- package/src/components/Page/examples/PageHorizontalNav.tsx +43 -0
- package/src/components/Page/examples/PageMainSectionPadding.tsx +72 -0
- package/src/components/Page/examples/PageTertiaryNav.tsx +42 -0
- package/src/components/Page/examples/PageUncontrolledNav.tsx +53 -0
- package/src/components/Page/examples/PageVerticalNav.tsx +65 -0
- package/src/components/Page/examples/PageVerticalNavUsingPageHeaderComponent.tsx +44 -0
- package/src/components/Page/examples/PageWithOrWithoutFill.tsx +67 -0
- package/src/components/Switch/examples/Switch.md +6 -153
- package/src/components/Switch/examples/SwitchBasic.tsx +20 -0
- package/src/components/Switch/examples/SwitchCheckedWithLabel.tsx +22 -0
- package/src/components/Switch/examples/SwitchDisabled.tsx +28 -0
- package/src/components/Switch/examples/SwitchReversed.tsx +21 -0
- package/src/components/Switch/examples/SwitchUncontrolled.tsx +14 -0
- package/src/components/Switch/examples/SwitchWithoutLabel.tsx +12 -0
- package/src/components/Tabs/OverflowTab.tsx +141 -0
- package/src/components/Tabs/Tabs.tsx +72 -25
- package/src/components/Tabs/examples/Tabs.md +6 -1
- package/src/components/Tabs/examples/TabsHorizontalOverflow.tsx +67 -0
- package/src/components/TextInputGroup/TextInputGroup.tsx +9 -1
- package/src/components/TextInputGroup/TextInputGroupMain.tsx +1 -1
- package/src/components/TreeView/TreeView.tsx +5 -0
- package/src/components/TreeView/TreeViewListItem.tsx +31 -17
- package/src/components/TreeView/__tests__/__snapshots__/TreeView.test.tsx.snap +299 -299
- package/src/components/TreeView/examples/TreeView.md +125 -7
- package/src/demos/ComposableMenu/ComposableMenu.md +8 -0
- package/src/demos/ComposableMenu/examples/ComposableTypeaheadSelect.tsx +196 -0
|
@@ -88,7 +88,9 @@ class DefaultTreeView extends React.Component {
|
|
|
88
88
|
{
|
|
89
89
|
name: 'Sources',
|
|
90
90
|
id: 'example1-Sources',
|
|
91
|
-
children: [
|
|
91
|
+
children: [
|
|
92
|
+
{ name: 'Application 4', id: 'example1-App4', children: [{ name: 'Settings', id: 'example1-App4Settings' }] }
|
|
93
|
+
]
|
|
92
94
|
},
|
|
93
95
|
{
|
|
94
96
|
name: 'Really really really long folder name that overflows the container it is in',
|
|
@@ -109,6 +111,106 @@ class DefaultTreeView extends React.Component {
|
|
|
109
111
|
}
|
|
110
112
|
```
|
|
111
113
|
|
|
114
|
+
### With separate selection and expansion
|
|
115
|
+
|
|
116
|
+
The `hasSelectableNodes` modifier will separate the expansion and selection behaviors, allowing a parent node to be selected or deselected with toggling its expansion.
|
|
117
|
+
|
|
118
|
+
```js
|
|
119
|
+
import React from 'react';
|
|
120
|
+
import { TreeView, Button } from '@patternfly/react-core';
|
|
121
|
+
|
|
122
|
+
class SelectableNodesTreeView extends React.Component {
|
|
123
|
+
constructor(props) {
|
|
124
|
+
super(props);
|
|
125
|
+
|
|
126
|
+
this.state = { activeItems: {} };
|
|
127
|
+
|
|
128
|
+
this.onSelect = (evt, treeViewItem) => {
|
|
129
|
+
this.setState({
|
|
130
|
+
activeItems: [treeViewItem]
|
|
131
|
+
});
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
render() {
|
|
136
|
+
const { activeItems, allExpanded } = this.state;
|
|
137
|
+
|
|
138
|
+
const options = [
|
|
139
|
+
{
|
|
140
|
+
name: 'Application launcher',
|
|
141
|
+
id: 'SelNodesTreeView-AppLaunch',
|
|
142
|
+
children: [
|
|
143
|
+
{
|
|
144
|
+
name: 'Application 1',
|
|
145
|
+
id: 'SelNodesTreeView-App1',
|
|
146
|
+
children: [
|
|
147
|
+
{ name: 'Settings', id: 'SelNodesTreeView-App1Settings' },
|
|
148
|
+
{ name: 'Current', id: 'SelNodesTreeView-App1Current' }
|
|
149
|
+
]
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
name: 'Application 2',
|
|
153
|
+
id: 'SelNodesTreeView-App2',
|
|
154
|
+
children: [
|
|
155
|
+
{ name: 'Settings', id: 'SelNodesTreeView-App2Settings' },
|
|
156
|
+
{
|
|
157
|
+
name: 'Loader',
|
|
158
|
+
id: 'SelNodesTreeView-App2Loader',
|
|
159
|
+
children: [
|
|
160
|
+
{ name: 'Loading App 1', id: 'SelNodesTreeView-LoadApp1' },
|
|
161
|
+
{ name: 'Loading App 2', id: 'SelNodesTreeView-LoadApp2' },
|
|
162
|
+
{ name: 'Loading App 3', id: 'SelNodesTreeView-LoadApp3' }
|
|
163
|
+
]
|
|
164
|
+
}
|
|
165
|
+
]
|
|
166
|
+
}
|
|
167
|
+
],
|
|
168
|
+
defaultExpanded: true
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
name: 'Cost management',
|
|
172
|
+
id: 'SelNodesTreeView-Cost',
|
|
173
|
+
children: [
|
|
174
|
+
{
|
|
175
|
+
name: 'Application 3',
|
|
176
|
+
id: 'SelNodesTreeView-App3',
|
|
177
|
+
children: [
|
|
178
|
+
{ name: 'Settings', id: 'SelNodesTreeView-App3Settings' },
|
|
179
|
+
{ name: 'Current', id: 'SelNodesTreeView-App3Current' }
|
|
180
|
+
]
|
|
181
|
+
}
|
|
182
|
+
]
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
name: 'Sources',
|
|
186
|
+
id: 'SelNodesTreeView-Sources',
|
|
187
|
+
children: [
|
|
188
|
+
{
|
|
189
|
+
name: 'Application 4',
|
|
190
|
+
id: 'SelNodesTreeView-App4',
|
|
191
|
+
children: [{ name: 'Settings', id: 'SelNodesTreeView-App4Settings' }]
|
|
192
|
+
}
|
|
193
|
+
]
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
name: 'Really really really long folder name that overflows the container it is in',
|
|
197
|
+
id: 'SelNodesTreeView-Long',
|
|
198
|
+
children: [{ name: 'Application 5', id: 'SelNodesTreeView-App5' }]
|
|
199
|
+
}
|
|
200
|
+
];
|
|
201
|
+
return (
|
|
202
|
+
<TreeView
|
|
203
|
+
hasSelectableNodes
|
|
204
|
+
data={options}
|
|
205
|
+
activeItems={activeItems}
|
|
206
|
+
onSelect={this.onSelect}
|
|
207
|
+
allExpanded={allExpanded}
|
|
208
|
+
/>
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
112
214
|
### With search
|
|
113
215
|
|
|
114
216
|
```js
|
|
@@ -168,7 +270,13 @@ class SearchTreeView extends React.Component {
|
|
|
168
270
|
{
|
|
169
271
|
name: 'Sources',
|
|
170
272
|
id: 'example2-Sources',
|
|
171
|
-
children: [
|
|
273
|
+
children: [
|
|
274
|
+
{
|
|
275
|
+
name: 'Application 4',
|
|
276
|
+
id: 'example2-App4',
|
|
277
|
+
children: [{ name: 'Settingexample2-s', id: 'example2-App4Settings' }]
|
|
278
|
+
}
|
|
279
|
+
]
|
|
172
280
|
},
|
|
173
281
|
{
|
|
174
282
|
name: 'Really really really long folder name that overflows the container it is in',
|
|
@@ -357,7 +465,9 @@ class CheckboxTreeView extends React.Component {
|
|
|
357
465
|
name: 'Really really really long folder name that overflows the container it is in',
|
|
358
466
|
id: 'example3-Long',
|
|
359
467
|
checkProps: { 'aria-label': 'long-check', checked: false },
|
|
360
|
-
children: [
|
|
468
|
+
children: [
|
|
469
|
+
{ name: 'Application 5', id: 'example3-App5', checkProps: { 'aria-label': 'app-5-check', checked: false } }
|
|
470
|
+
]
|
|
361
471
|
}
|
|
362
472
|
];
|
|
363
473
|
|
|
@@ -522,7 +632,9 @@ class IconTreeView extends React.Component {
|
|
|
522
632
|
{
|
|
523
633
|
name: 'Sources',
|
|
524
634
|
id: 'example4-Sources',
|
|
525
|
-
children: [
|
|
635
|
+
children: [
|
|
636
|
+
{ name: 'Application 4', id: 'example4-App4', children: [{ name: 'Settings', id: 'example4-App4Settings' }] }
|
|
637
|
+
]
|
|
526
638
|
},
|
|
527
639
|
{
|
|
528
640
|
name: 'Really really really long folder name that overflows the container it is in',
|
|
@@ -613,7 +725,9 @@ class BadgesTreeView extends React.Component {
|
|
|
613
725
|
{
|
|
614
726
|
name: 'Sources',
|
|
615
727
|
id: 'example5-Sources',
|
|
616
|
-
children: [
|
|
728
|
+
children: [
|
|
729
|
+
{ name: 'Application 4', id: 'example5-App4', children: [{ name: 'Settings', id: 'example5-App4Settings' }] }
|
|
730
|
+
]
|
|
617
731
|
},
|
|
618
732
|
{
|
|
619
733
|
name: 'Really really really long folder name that overflows the container it is in',
|
|
@@ -842,7 +956,9 @@ class IconTreeView extends React.Component {
|
|
|
842
956
|
{
|
|
843
957
|
name: 'Sources',
|
|
844
958
|
id: 'example7-Sources',
|
|
845
|
-
children: [
|
|
959
|
+
children: [
|
|
960
|
+
{ name: 'Application 4', id: 'example7-App4', children: [{ name: 'Settings', id: 'example7-App4Settings' }] }
|
|
961
|
+
]
|
|
846
962
|
},
|
|
847
963
|
{
|
|
848
964
|
name: 'Really really really long folder name that overflows the container it is in',
|
|
@@ -911,7 +1027,9 @@ const GuidesTreeView: React.FunctionComponent = () => {
|
|
|
911
1027
|
{
|
|
912
1028
|
name: 'Sources',
|
|
913
1029
|
id: 'example8-Sources',
|
|
914
|
-
children: [
|
|
1030
|
+
children: [
|
|
1031
|
+
{ name: 'Application 4', id: 'example8-App4', children: [{ name: 'Settings', id: 'example8-App4Settings' }] }
|
|
1032
|
+
]
|
|
915
1033
|
},
|
|
916
1034
|
{
|
|
917
1035
|
name: 'Really really really long folder name that overflows the container it is in',
|
|
@@ -20,6 +20,9 @@ import pfIcon from './examples/pf-logo-small.svg';
|
|
|
20
20
|
import SearchIcon from '@patternfly/react-icons/dist/esm/icons/search-icon';
|
|
21
21
|
import CaretDownIcon from '@patternfly/react-icons/dist/esm/icons/caret-down-icon';
|
|
22
22
|
import avatarImg from './examples/avatarImg.svg';
|
|
23
|
+
import TimesIcon from '@patternfly/react-icons/dist/esm/icons/times-icon';
|
|
24
|
+
import { css } from '@patternfly/react-styles';
|
|
25
|
+
import styles from '@patternfly/react-styles/css/components/Menu/menu';
|
|
23
26
|
|
|
24
27
|
## Demos
|
|
25
28
|
|
|
@@ -45,6 +48,11 @@ Composable menus currently require consumer keyboard handling and use of our und
|
|
|
45
48
|
```ts isBeta file="./examples/ComposableSimpleCheckboxSelect.tsx"
|
|
46
49
|
```
|
|
47
50
|
|
|
51
|
+
### Composable typeahead select
|
|
52
|
+
|
|
53
|
+
```ts file="./examples/ComposableTypeaheadSelect.tsx"
|
|
54
|
+
```
|
|
55
|
+
|
|
48
56
|
### Composable drilldown menu
|
|
49
57
|
|
|
50
58
|
```ts isBeta file="./examples/ComposableDrilldownMenu.tsx"
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
Menu,
|
|
5
|
+
MenuContent,
|
|
6
|
+
MenuList,
|
|
7
|
+
MenuItem,
|
|
8
|
+
Popper,
|
|
9
|
+
MenuToggle,
|
|
10
|
+
TextInputGroup,
|
|
11
|
+
MenuItemProps,
|
|
12
|
+
Button,
|
|
13
|
+
TextInputGroupUtilities,
|
|
14
|
+
TextInputGroupMain,
|
|
15
|
+
MenuToggleElement
|
|
16
|
+
} from '@patternfly/react-core';
|
|
17
|
+
import TimesIcon from '@patternfly/react-icons/dist/esm/icons/times-icon';
|
|
18
|
+
import TableIcon from '@patternfly/react-icons/dist/esm/icons/table-icon';
|
|
19
|
+
|
|
20
|
+
const intitalMenuItems: MenuItemProps[] = [
|
|
21
|
+
{ itemId: 'Option 1', children: 'Option 1' },
|
|
22
|
+
{ itemId: 'Option 2', children: 'Option 2' },
|
|
23
|
+
{ itemId: 'Option 3', children: 'Option 3', icon: <TableIcon aria-hidden /> }
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
export const ComposableTypeaheadSelect: React.FunctionComponent = () => {
|
|
27
|
+
const [isMenuOpen, setIsMenuOpen] = React.useState(false);
|
|
28
|
+
const [inputValue, setInputValue] = React.useState<string>('');
|
|
29
|
+
const [menuItems, setMenuItems] = React.useState<MenuItemProps[]>(intitalMenuItems);
|
|
30
|
+
const [focusedItemIndex, setFocusedItemIndex] = React.useState<number | null>(null);
|
|
31
|
+
const [isSelected, setIsSelected] = React.useState(false);
|
|
32
|
+
|
|
33
|
+
const menuToggleRef = React.useRef<MenuToggleElement>({} as MenuToggleElement);
|
|
34
|
+
const textInputRef = React.useRef<HTMLInputElement>();
|
|
35
|
+
|
|
36
|
+
React.useEffect(() => {
|
|
37
|
+
let newMenuItems: MenuItemProps[] = intitalMenuItems;
|
|
38
|
+
|
|
39
|
+
// Filter menu items based on the text input value when one exists
|
|
40
|
+
if (inputValue) {
|
|
41
|
+
newMenuItems = intitalMenuItems.filter(menuItem =>
|
|
42
|
+
String(menuItem.children)
|
|
43
|
+
.toLowerCase()
|
|
44
|
+
.includes(inputValue.toLowerCase())
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
// When no options are found after filtering, display 'No results found'
|
|
48
|
+
if (!newMenuItems.length) {
|
|
49
|
+
newMenuItems = [{ isDisabled: true, children: 'No results found' }];
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
setMenuItems(newMenuItems);
|
|
54
|
+
}, [inputValue]);
|
|
55
|
+
|
|
56
|
+
const focusOnInput = () => menuToggleRef.current?.querySelector('input')?.focus();
|
|
57
|
+
|
|
58
|
+
const onMenuSelect = (_event: React.MouseEvent<Element, MouseEvent>, itemId: string | number) => {
|
|
59
|
+
if (itemId) {
|
|
60
|
+
setInputValue(itemId.toString());
|
|
61
|
+
setIsSelected(true);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
setIsMenuOpen(false);
|
|
65
|
+
setFocusedItemIndex(null);
|
|
66
|
+
focusOnInput();
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const handleMenuArrowKeys = (key: string) => {
|
|
70
|
+
let indexToFocus;
|
|
71
|
+
|
|
72
|
+
if (isMenuOpen) {
|
|
73
|
+
if (key === 'ArrowUp') {
|
|
74
|
+
// When no index is set or at the first index, focus to the last, otherwise decrement focus index
|
|
75
|
+
if (focusedItemIndex === null || focusedItemIndex === 0) {
|
|
76
|
+
indexToFocus = menuItems.length - 1;
|
|
77
|
+
} else {
|
|
78
|
+
indexToFocus = focusedItemIndex - 1;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (key === 'ArrowDown') {
|
|
83
|
+
// When no index is set or at the last index, focus to the first, otherwise increment focus index
|
|
84
|
+
if (focusedItemIndex === null || focusedItemIndex === menuItems.length - 1) {
|
|
85
|
+
indexToFocus = 0;
|
|
86
|
+
} else {
|
|
87
|
+
indexToFocus = focusedItemIndex + 1;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
setFocusedItemIndex(indexToFocus);
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const onInputKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
|
|
96
|
+
const enabledMenuItems = menuItems.filter(menuItem => !menuItem.isDisabled);
|
|
97
|
+
const [firstMenuItem] = enabledMenuItems;
|
|
98
|
+
const focusedItem = focusedItemIndex ? enabledMenuItems[focusedItemIndex] : firstMenuItem;
|
|
99
|
+
|
|
100
|
+
switch (event.key) {
|
|
101
|
+
// Select the first available option
|
|
102
|
+
case 'Enter':
|
|
103
|
+
if (isMenuOpen) {
|
|
104
|
+
setInputValue(String(focusedItem.children));
|
|
105
|
+
setIsSelected(true);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
setIsMenuOpen(prevIsOpen => !prevIsOpen);
|
|
109
|
+
setFocusedItemIndex(null);
|
|
110
|
+
focusOnInput();
|
|
111
|
+
|
|
112
|
+
break;
|
|
113
|
+
case 'Tab':
|
|
114
|
+
case 'Escape':
|
|
115
|
+
setIsMenuOpen(false);
|
|
116
|
+
break;
|
|
117
|
+
case 'ArrowUp':
|
|
118
|
+
case 'ArrowDown':
|
|
119
|
+
handleMenuArrowKeys(event.key);
|
|
120
|
+
break;
|
|
121
|
+
default:
|
|
122
|
+
!isMenuOpen && setIsMenuOpen(true);
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
// Close the menu when a click occurs outside of the menu toggle content
|
|
127
|
+
const onDocumentClick = (event: MouseEvent) => {
|
|
128
|
+
if (!menuToggleRef.current?.contains(event?.target as HTMLElement)) {
|
|
129
|
+
setIsMenuOpen(false);
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const toggleMenuOpen = () => {
|
|
134
|
+
setIsMenuOpen(prevIsOpen => !prevIsOpen);
|
|
135
|
+
textInputRef.current?.focus();
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const onTextInputChange = (value: string) => {
|
|
139
|
+
setInputValue(value);
|
|
140
|
+
setIsSelected(false);
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
return (
|
|
144
|
+
<Popper
|
|
145
|
+
trigger={
|
|
146
|
+
<MenuToggle
|
|
147
|
+
variant="typeahead"
|
|
148
|
+
onClick={toggleMenuOpen}
|
|
149
|
+
innerRef={menuToggleRef}
|
|
150
|
+
isExpanded={isMenuOpen}
|
|
151
|
+
isFullWidth
|
|
152
|
+
>
|
|
153
|
+
<TextInputGroup isPlain>
|
|
154
|
+
<TextInputGroupMain
|
|
155
|
+
value={inputValue}
|
|
156
|
+
onClick={toggleMenuOpen}
|
|
157
|
+
onChange={onTextInputChange}
|
|
158
|
+
onKeyDown={onInputKeyDown}
|
|
159
|
+
id="typeahead-select-input"
|
|
160
|
+
autoComplete="off"
|
|
161
|
+
innerRef={textInputRef}
|
|
162
|
+
/>
|
|
163
|
+
|
|
164
|
+
<TextInputGroupUtilities>
|
|
165
|
+
{!!inputValue && (
|
|
166
|
+
<Button variant="plain" onClick={() => setInputValue('')} aria-label="Clear input value">
|
|
167
|
+
<TimesIcon aria-hidden />
|
|
168
|
+
</Button>
|
|
169
|
+
)}
|
|
170
|
+
</TextInputGroupUtilities>
|
|
171
|
+
</TextInputGroup>
|
|
172
|
+
</MenuToggle>
|
|
173
|
+
}
|
|
174
|
+
popper={
|
|
175
|
+
<Menu id="select-menu" onSelect={onMenuSelect} selected={isSelected && inputValue}>
|
|
176
|
+
<MenuContent>
|
|
177
|
+
<MenuList>
|
|
178
|
+
{menuItems.map((itemProps, index) => (
|
|
179
|
+
<MenuItem
|
|
180
|
+
key={itemProps.itemId || itemProps.children}
|
|
181
|
+
isFocused={focusedItemIndex === index}
|
|
182
|
+
className={itemProps.className}
|
|
183
|
+
onClick={() => setIsSelected(true)}
|
|
184
|
+
{...itemProps}
|
|
185
|
+
ref={null}
|
|
186
|
+
/>
|
|
187
|
+
))}
|
|
188
|
+
</MenuList>
|
|
189
|
+
</MenuContent>
|
|
190
|
+
</Menu>
|
|
191
|
+
}
|
|
192
|
+
isVisible={isMenuOpen}
|
|
193
|
+
onDocumentClick={onDocumentClick}
|
|
194
|
+
/>
|
|
195
|
+
);
|
|
196
|
+
};
|