@patternfly/react-core 4.246.0 → 4.247.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 +11 -0
- package/dist/esm/components/DualListSelector/DualListSelectorPane.d.ts +2 -0
- package/dist/esm/components/DualListSelector/DualListSelectorPane.d.ts.map +1 -1
- package/dist/esm/components/DualListSelector/DualListSelectorPane.js +4 -5
- package/dist/esm/components/DualListSelector/DualListSelectorPane.js.map +1 -1
- package/dist/esm/components/SearchInput/SearchInput.d.ts +2 -0
- package/dist/esm/components/SearchInput/SearchInput.d.ts.map +1 -1
- package/dist/esm/components/SearchInput/SearchInput.js +2 -2
- package/dist/esm/components/SearchInput/SearchInput.js.map +1 -1
- package/dist/js/components/DualListSelector/DualListSelectorPane.d.ts +2 -0
- package/dist/js/components/DualListSelector/DualListSelectorPane.d.ts.map +1 -1
- package/dist/js/components/DualListSelector/DualListSelectorPane.js +4 -5
- package/dist/js/components/DualListSelector/DualListSelectorPane.js.map +1 -1
- package/dist/js/components/SearchInput/SearchInput.d.ts +2 -0
- package/dist/js/components/SearchInput/SearchInput.d.ts.map +1 -1
- package/dist/js/components/SearchInput/SearchInput.js +2 -2
- package/dist/js/components/SearchInput/SearchInput.js.map +1 -1
- package/dist/umd/react-core.min.css +3370 -3370
- package/dist/umd/react-core.min.js +2 -2
- package/package.json +2 -2
- package/src/components/Dropdown/examples/Dropdown.md +79 -55
- package/src/components/DualListSelector/DualListSelectorPane.tsx +11 -7
- package/src/components/DualListSelector/__tests__/__snapshots__/DualListSelector.test.tsx.snap +70 -10
- package/src/components/SearchInput/SearchInput.tsx +15 -0
- package/src/demos/ComposableMenu/ComposableMenu.md +7 -2
- package/src/demos/ComposableMenu/examples/ComposableMultipleTypeaheadSelect.tsx +220 -0
- package/src/components/Dropdown/examples/DropdownSplitButtonIndeterminate.tsx +0 -78
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@patternfly/react-core",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.247.0",
|
|
4
4
|
"description": "This library provides a set of common React components for use with the PatternFly reference implementation.",
|
|
5
5
|
"main": "dist/js/index.js",
|
|
6
6
|
"module": "dist/esm/index.js",
|
|
@@ -70,5 +70,5 @@
|
|
|
70
70
|
"react": "^16.8 || ^17 || ^18",
|
|
71
71
|
"react-dom": "^16.8 || ^17 || ^18"
|
|
72
72
|
},
|
|
73
|
-
"gitHead": "
|
|
73
|
+
"gitHead": "064d341a881a7ab291add565ed1929cf915ab049"
|
|
74
74
|
}
|
|
@@ -18,133 +18,157 @@ import avatarImg from '../../Avatar/examples/avatarImg.svg';
|
|
|
18
18
|
|
|
19
19
|
## Examples
|
|
20
20
|
|
|
21
|
-
### Basic
|
|
21
|
+
### Basic dropdowns
|
|
22
|
+
|
|
23
|
+
Basic dropdowns present users with a menu of items upon clicking a dropdown toggle. The `isOpen` property is used to determine whether the menu is opened or not.
|
|
24
|
+
|
|
25
|
+
All of the items in a dropdown list are created as `<DropdownItem>` components. The following example shows a few different states of dropdown items. As shown in this example, dropdown items may:
|
|
26
|
+
- Use the `tooltip` property to enable tooltip support.
|
|
27
|
+
- Use the `isDisabled` property to disable an item.
|
|
28
|
+
- Use the `isAriaDisabled` property to style an item as disabled, while still allowing the item to receive keyboard focus and display a tooltip.
|
|
29
|
+
|
|
30
|
+
This example also uses a `<DropdownSeparator>`, which adds a horizontal line that splits the menu into 2 sections.
|
|
22
31
|
|
|
23
32
|
```ts file='./DropdownBasic.tsx'
|
|
24
33
|
```
|
|
25
34
|
|
|
26
|
-
###
|
|
35
|
+
### Autofocus on dropdown expansion
|
|
36
|
+
|
|
37
|
+
A `<DropdownItem>` may use the `autoFocus` property so that it automatically receives focus when the menu is expanded. `autoFocus` cannot be applied to disabled dropdown items.
|
|
27
38
|
|
|
28
39
|
```ts file="./DropdownInitialSelection.tsx"
|
|
29
40
|
```
|
|
30
41
|
|
|
31
|
-
### With groups
|
|
42
|
+
### With groups of items
|
|
43
|
+
|
|
44
|
+
Use the `<DropdownGroup>` component to group sets of related dropdown items together. Use the `isGrouped` property on the `Dropdown` component to indicate that a dropdown contains grouped items.
|
|
32
45
|
|
|
33
46
|
```ts file="./DropdownGroups.tsx"
|
|
34
47
|
```
|
|
35
48
|
|
|
36
|
-
###
|
|
49
|
+
### With item descriptions
|
|
37
50
|
|
|
38
|
-
|
|
51
|
+
A `<DropdownItem>` may use the `description` property to provide users with more context. This description will appear in the menu below the dropdown item it corresponds to.
|
|
52
|
+
|
|
53
|
+
```ts file="./DropdownDescriptions.tsx"
|
|
39
54
|
```
|
|
40
55
|
|
|
41
|
-
###
|
|
56
|
+
### With custom content
|
|
42
57
|
|
|
43
|
-
|
|
58
|
+
You may replace standard dropdown items with custom content. If so, you must style the menu contents and manage focus within your custom implementation.
|
|
59
|
+
|
|
60
|
+
```ts file="./DropdownBasicPanel.tsx"
|
|
44
61
|
```
|
|
45
62
|
|
|
46
|
-
###
|
|
63
|
+
### With router link items
|
|
47
64
|
|
|
48
|
-
|
|
65
|
+
`<DropdownItem>` may use the `component` property to include router links.
|
|
66
|
+
|
|
67
|
+
```ts file="./DropdownRouterLink.tsx"
|
|
49
68
|
```
|
|
50
69
|
|
|
51
|
-
###
|
|
70
|
+
### Toggle styling
|
|
52
71
|
|
|
53
|
-
|
|
72
|
+
You can adjust the styling of a `<DropdownToggle>` using the `toggleVariant` property. `toggleVariant` may be set to 'primary', 'secondary', or 'default'.
|
|
73
|
+
|
|
74
|
+
```ts file="./DropdownPrimaryToggle.tsx"
|
|
54
75
|
```
|
|
55
76
|
|
|
56
|
-
###
|
|
77
|
+
### Plain text toggle
|
|
57
78
|
|
|
58
|
-
|
|
79
|
+
A toggle may be styled as plain text using the `isText` and `isPlain`properties. The following example applies these properties to a `<Dropdown>` component.
|
|
80
|
+
|
|
81
|
+
```ts file="./DropdownPlainTextToggle.tsx"
|
|
59
82
|
```
|
|
60
83
|
|
|
61
|
-
###
|
|
84
|
+
### Disabled toggles
|
|
62
85
|
|
|
63
|
-
|
|
86
|
+
A `<DropdownToggle>` may use the `isDisabled` property to disable the expansion of the dropdown menu.
|
|
87
|
+
|
|
88
|
+
```ts file="./DropdownDisabled.tsx"
|
|
64
89
|
```
|
|
65
90
|
|
|
66
|
-
###
|
|
91
|
+
### Icon toggle
|
|
67
92
|
|
|
68
|
-
|
|
93
|
+
`<DropdownToggle>` may use a recognizable icon instead of a text label if text would take up too much room. Set the `toggleIndicator` property to `{null}` to remove the default dropdown toggle caret.
|
|
94
|
+
|
|
95
|
+
```ts file="./DropdownIconOnly.tsx"
|
|
69
96
|
```
|
|
70
97
|
|
|
71
|
-
###
|
|
98
|
+
### Kebab toggle
|
|
99
|
+
|
|
100
|
+
Use `<KebabToggle>` to replace the default text label and caret with a kebab icon. A kebab toggle can be used to indicate that a dropdown connects to a menu.
|
|
72
101
|
|
|
73
102
|
```ts file="./DropdownKebab.tsx"
|
|
74
103
|
```
|
|
75
104
|
|
|
76
|
-
###
|
|
105
|
+
### Toggle with image
|
|
77
106
|
|
|
78
|
-
|
|
107
|
+
The `icon` property can be used to display an image in the `DropdownToggle` and can be used alongside a text label.
|
|
108
|
+
|
|
109
|
+
```ts file="./DropdownImageAndText.tsx"
|
|
79
110
|
```
|
|
80
111
|
|
|
81
|
-
###
|
|
112
|
+
### Toggle with badge
|
|
82
113
|
|
|
83
|
-
|
|
114
|
+
You may use a `<BadgeToggle>` to display the number of dropdown menu items.
|
|
115
|
+
|
|
116
|
+
```ts file="./DropdownBadge.tsx"
|
|
84
117
|
```
|
|
85
118
|
|
|
86
119
|
### Split button
|
|
87
120
|
|
|
88
|
-
|
|
89
|
-
```
|
|
121
|
+
To combine an action button or other control with a dropdown menu, use a split button. A `<DropdownToggle>` can be rendered as a split button by using the `splitButtonItems` property. Elements to be displayed before the toggle button must be included in `splitButtonItems`.
|
|
90
122
|
|
|
91
|
-
|
|
123
|
+
The following example shows a split button with a `<DropdownToggleCheckbox>`, which is primarily used for bulk select operations on large data sets.
|
|
92
124
|
|
|
93
|
-
```ts file="./
|
|
125
|
+
```ts file="./DropdownSplitButton.tsx"
|
|
94
126
|
```
|
|
95
127
|
|
|
96
|
-
### Split button
|
|
97
|
-
|
|
98
|
-
```ts file="./DropdownSplitButtonIndeterminate.tsx"
|
|
99
|
-
```
|
|
128
|
+
### Split button text label
|
|
100
129
|
|
|
101
|
-
|
|
130
|
+
Text labels may optionally be used alongside actions within split buttons.
|
|
102
131
|
|
|
103
|
-
```ts file="./
|
|
132
|
+
```ts file="./DropdownSplitButtonText.tsx"
|
|
104
133
|
```
|
|
105
134
|
|
|
106
|
-
### Split button
|
|
135
|
+
### Split button toggle variants
|
|
107
136
|
|
|
108
|
-
|
|
109
|
-
```
|
|
137
|
+
Use the `toggleVariant` and `splitButtonVariant` properties to adjust the styling of split button toggles.
|
|
110
138
|
|
|
111
|
-
|
|
139
|
+
The following example uses `toggleVariant="primary"` to apply primary visual styling and `splitButtonVariant="action"` to include an action.
|
|
112
140
|
|
|
113
141
|
```ts file="./DropdownSplitButtonActionPrimary.tsx"
|
|
114
142
|
```
|
|
115
143
|
|
|
116
|
-
###
|
|
144
|
+
### Dropdown alignment
|
|
117
145
|
|
|
118
|
-
|
|
119
|
-
```
|
|
146
|
+
The alignment of a dropdown menu may be changed to better fit the size of a user's browser window. A `<Dropdown>` may use the `alignments` property to indicate how the menu will align at different screen sizes.
|
|
120
147
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
```ts file="./DropdownBasicPanel.tsx"
|
|
148
|
+
```ts file="./DropdownAlignmentOnBreakpoints.tsx"
|
|
124
149
|
```
|
|
125
150
|
|
|
126
|
-
###
|
|
151
|
+
### Right-positioned menu
|
|
127
152
|
|
|
128
|
-
|
|
153
|
+
The following example demonstrates a `<Dropdown>` with a `position` equal to 'right'. By default, a dropdown menu is left-positioned, but a right-positioned menu may work better depending on where your dropdown is placed on a page.
|
|
154
|
+
|
|
155
|
+
```ts file="./DropdownPositionRight.tsx"
|
|
129
156
|
```
|
|
130
157
|
|
|
131
|
-
###
|
|
158
|
+
### Upwards directional menu
|
|
132
159
|
|
|
133
|
-
|
|
160
|
+
The following example demonstrates a `<Dropdown>` with a `direction` equal to 'up'. This will alter the default menu behavior so that the dropdown menu is displayed above the dropdown toggle.
|
|
161
|
+
|
|
162
|
+
```ts file="./DropdownDirectionUp.tsx"
|
|
134
163
|
```
|
|
135
164
|
|
|
136
|
-
###
|
|
165
|
+
### Using document parent element
|
|
137
166
|
|
|
138
|
-
Avoid passing in `document.body`
|
|
167
|
+
Avoid passing in `document.body` to a `<Dropdown>` `menuAppendTo` property. Doing so can cause accessibility issues, such as being unable to navigate `<Dropdown>` options using keyboards or screen readers.
|
|
139
168
|
|
|
140
|
-
Instead append to
|
|
169
|
+
Instead, append to ‘parent’ to avoid sacrificing accessibility.
|
|
141
170
|
|
|
142
|
-
|
|
171
|
+
The following example demonstrates both methods of appending. After making a selection, both dropdowns retain focus on their respective `<Dropdown>`, but the options for the `document.body` variant cannot be navigated via Voice Over.
|
|
143
172
|
|
|
144
173
|
```ts file="./DropdownAppendBodyVsParent.tsx"
|
|
145
174
|
```
|
|
146
|
-
|
|
147
|
-
### Dropdown with descriptions
|
|
148
|
-
|
|
149
|
-
```ts file="./DropdownDescriptions.tsx"
|
|
150
|
-
```
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import styles from '@patternfly/react-styles/css/components/DualListSelector/dual-list-selector';
|
|
3
3
|
import { css } from '@patternfly/react-styles';
|
|
4
|
-
import formStyles from '@patternfly/react-styles/css/components/FormControl/form-control';
|
|
5
4
|
import { DualListSelectorTree, DualListSelectorTreeItemData } from './DualListSelectorTree';
|
|
6
5
|
import { getUniqueId } from '../../helpers';
|
|
7
6
|
import { DualListSelectorListWrapper } from './DualListSelectorListWrapper';
|
|
8
7
|
import { DualListSelectorContext, DualListSelectorPaneContext } from './DualListSelectorContext';
|
|
9
8
|
import { DualListSelectorList } from './DualListSelectorList';
|
|
9
|
+
import { SearchInput } from '../SearchInput';
|
|
10
10
|
|
|
11
11
|
/** Acts as the container for a list of options that are either available or chosen,
|
|
12
12
|
* depending on the pane type (available or chosen). A search input and other actions,
|
|
@@ -57,6 +57,8 @@ export interface DualListSelectorPaneProps {
|
|
|
57
57
|
onSearch?: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
|
58
58
|
/** @hide A callback for when the search input value for changes. To be used when isSearchable is true. */
|
|
59
59
|
onSearchInputChanged?: (value: string, event: React.FormEvent<HTMLInputElement>) => void;
|
|
60
|
+
/** @hide Callback for search input clear button */
|
|
61
|
+
onSearchInputClear?: (event: React.SyntheticEvent<HTMLButtonElement>) => void;
|
|
60
62
|
/** @hide Filter function for custom filtering based on search string. To be used when isSearchable is true. */
|
|
61
63
|
filterOption?: (option: React.ReactNode, input: string) => boolean;
|
|
62
64
|
/** @hide Accessible label for the search input. To be used when isSearchable is true. */
|
|
@@ -81,6 +83,7 @@ export const DualListSelectorPane: React.FunctionComponent<DualListSelectorPaneP
|
|
|
81
83
|
searchInputAriaLabel = '',
|
|
82
84
|
onFilterUpdate,
|
|
83
85
|
onSearchInputChanged,
|
|
86
|
+
onSearchInputClear,
|
|
84
87
|
filterOption,
|
|
85
88
|
id = getUniqueId('dual-list-selector-pane'),
|
|
86
89
|
isDisabled = false,
|
|
@@ -90,8 +93,7 @@ export const DualListSelectorPane: React.FunctionComponent<DualListSelectorPaneP
|
|
|
90
93
|
const { isTree } = React.useContext(DualListSelectorContext);
|
|
91
94
|
|
|
92
95
|
// only called when search input is dynamically built
|
|
93
|
-
const onChange = (e: React.
|
|
94
|
-
const newValue = e.target.value;
|
|
96
|
+
const onChange = (newValue: string, e: React.FormEvent<HTMLInputElement>) => {
|
|
95
97
|
let filtered: React.ReactNode[];
|
|
96
98
|
if (isTree) {
|
|
97
99
|
filtered = options
|
|
@@ -160,12 +162,14 @@ export const DualListSelectorPane: React.FunctionComponent<DualListSelectorPaneP
|
|
|
160
162
|
{searchInput ? (
|
|
161
163
|
searchInput
|
|
162
164
|
) : (
|
|
163
|
-
<
|
|
164
|
-
className={css(formStyles.formControl, formStyles.modifiers.search)}
|
|
165
|
-
type="search"
|
|
165
|
+
<SearchInput
|
|
166
166
|
onChange={isDisabled ? undefined : onChange}
|
|
167
|
+
onClear={
|
|
168
|
+
onSearchInputClear ? onSearchInputClear : e => onChange('', e as React.FormEvent<HTMLInputElement>)
|
|
169
|
+
}
|
|
170
|
+
isDisabled={isDisabled}
|
|
167
171
|
aria-label={searchInputAriaLabel}
|
|
168
|
-
|
|
172
|
+
type="search"
|
|
169
173
|
/>
|
|
170
174
|
)}
|
|
171
175
|
</div>
|
package/src/components/DualListSelector/__tests__/__snapshots__/DualListSelector.test.tsx.snap
CHANGED
|
@@ -1135,11 +1135,41 @@ exports[`DualListSelector with search inputs 1`] = `
|
|
|
1135
1135
|
<div
|
|
1136
1136
|
class="pf-c-dual-list-selector__tools-filter"
|
|
1137
1137
|
>
|
|
1138
|
-
<
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1138
|
+
<div
|
|
1139
|
+
class="pf-c-text-input-group"
|
|
1140
|
+
>
|
|
1141
|
+
<div
|
|
1142
|
+
class="pf-c-text-input-group__main pf-m-icon"
|
|
1143
|
+
>
|
|
1144
|
+
<span
|
|
1145
|
+
class="pf-c-text-input-group__text"
|
|
1146
|
+
>
|
|
1147
|
+
<span
|
|
1148
|
+
class="pf-c-text-input-group__icon"
|
|
1149
|
+
>
|
|
1150
|
+
<svg
|
|
1151
|
+
aria-hidden="true"
|
|
1152
|
+
fill="currentColor"
|
|
1153
|
+
height="1em"
|
|
1154
|
+
role="img"
|
|
1155
|
+
style="vertical-align: -0.125em;"
|
|
1156
|
+
viewBox="0 0 512 512"
|
|
1157
|
+
width="1em"
|
|
1158
|
+
>
|
|
1159
|
+
<path
|
|
1160
|
+
d="M505 442.7L405.3 343c-4.5-4.5-10.6-7-17-7H372c27.6-35.3 44-79.7 44-128C416 93.1 322.9 0 208 0S0 93.1 0 208s93.1 208 208 208c48.3 0 92.7-16.4 128-44v16.3c0 6.4 2.5 12.5 7 17l99.7 99.7c9.4 9.4 24.6 9.4 33.9 0l28.3-28.3c9.4-9.4 9.4-24.6.1-34zM208 336c-70.7 0-128-57.2-128-128 0-70.7 57.2-128 128-128 70.7 0 128 57.2 128 128 0 70.7-57.2 128-128 128z"
|
|
1161
|
+
/>
|
|
1162
|
+
</svg>
|
|
1163
|
+
</span>
|
|
1164
|
+
<input
|
|
1165
|
+
aria-label="Available search input"
|
|
1166
|
+
class="pf-c-text-input-group__text-input"
|
|
1167
|
+
type="search"
|
|
1168
|
+
value=""
|
|
1169
|
+
/>
|
|
1170
|
+
</span>
|
|
1171
|
+
</div>
|
|
1172
|
+
</div>
|
|
1143
1173
|
</div>
|
|
1144
1174
|
</div>
|
|
1145
1175
|
<div
|
|
@@ -1360,11 +1390,41 @@ exports[`DualListSelector with search inputs 1`] = `
|
|
|
1360
1390
|
<div
|
|
1361
1391
|
class="pf-c-dual-list-selector__tools-filter"
|
|
1362
1392
|
>
|
|
1363
|
-
<
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1393
|
+
<div
|
|
1394
|
+
class="pf-c-text-input-group"
|
|
1395
|
+
>
|
|
1396
|
+
<div
|
|
1397
|
+
class="pf-c-text-input-group__main pf-m-icon"
|
|
1398
|
+
>
|
|
1399
|
+
<span
|
|
1400
|
+
class="pf-c-text-input-group__text"
|
|
1401
|
+
>
|
|
1402
|
+
<span
|
|
1403
|
+
class="pf-c-text-input-group__icon"
|
|
1404
|
+
>
|
|
1405
|
+
<svg
|
|
1406
|
+
aria-hidden="true"
|
|
1407
|
+
fill="currentColor"
|
|
1408
|
+
height="1em"
|
|
1409
|
+
role="img"
|
|
1410
|
+
style="vertical-align: -0.125em;"
|
|
1411
|
+
viewBox="0 0 512 512"
|
|
1412
|
+
width="1em"
|
|
1413
|
+
>
|
|
1414
|
+
<path
|
|
1415
|
+
d="M505 442.7L405.3 343c-4.5-4.5-10.6-7-17-7H372c27.6-35.3 44-79.7 44-128C416 93.1 322.9 0 208 0S0 93.1 0 208s93.1 208 208 208c48.3 0 92.7-16.4 128-44v16.3c0 6.4 2.5 12.5 7 17l99.7 99.7c9.4 9.4 24.6 9.4 33.9 0l28.3-28.3c9.4-9.4 9.4-24.6.1-34zM208 336c-70.7 0-128-57.2-128-128 0-70.7 57.2-128 128-128 70.7 0 128 57.2 128 128 0 70.7-57.2 128-128 128z"
|
|
1416
|
+
/>
|
|
1417
|
+
</svg>
|
|
1418
|
+
</span>
|
|
1419
|
+
<input
|
|
1420
|
+
aria-label="Chosen search input"
|
|
1421
|
+
class="pf-c-text-input-group__text-input"
|
|
1422
|
+
type="search"
|
|
1423
|
+
value=""
|
|
1424
|
+
/>
|
|
1425
|
+
</span>
|
|
1426
|
+
</div>
|
|
1427
|
+
</div>
|
|
1368
1428
|
</div>
|
|
1369
1429
|
</div>
|
|
1370
1430
|
<div
|
|
@@ -70,6 +70,19 @@ export interface SearchInputProps extends Omit<React.HTMLProps<HTMLDivElement>,
|
|
|
70
70
|
hasWordsAttrLabel?: React.ReactNode;
|
|
71
71
|
/** A suggestion for autocompleting. */
|
|
72
72
|
hint?: string;
|
|
73
|
+
/** Type of the input */
|
|
74
|
+
type?:
|
|
75
|
+
| 'text'
|
|
76
|
+
| 'date'
|
|
77
|
+
| 'datetime-local'
|
|
78
|
+
| 'email'
|
|
79
|
+
| 'month'
|
|
80
|
+
| 'number'
|
|
81
|
+
| 'password'
|
|
82
|
+
| 'search'
|
|
83
|
+
| 'tel'
|
|
84
|
+
| 'time'
|
|
85
|
+
| 'url';
|
|
73
86
|
/** @hide A reference object to attach to the input box. */
|
|
74
87
|
innerRef?: React.RefObject<any>;
|
|
75
88
|
/** A flag for controlling the open state of a custom advanced search implementation. */
|
|
@@ -147,6 +160,7 @@ const SearchInputBase: React.FunctionComponent<SearchInputProps> = ({
|
|
|
147
160
|
isDisabled = false,
|
|
148
161
|
appendTo,
|
|
149
162
|
removeFindDomNode = false,
|
|
163
|
+
type = 'text',
|
|
150
164
|
...props
|
|
151
165
|
}: SearchInputProps) => {
|
|
152
166
|
const [isSearchMenuOpen, setIsSearchMenuOpen] = React.useState(false);
|
|
@@ -263,6 +277,7 @@ const SearchInputBase: React.FunctionComponent<SearchInputProps> = ({
|
|
|
263
277
|
aria-label={ariaLabel}
|
|
264
278
|
onKeyDown={onEnter}
|
|
265
279
|
onChange={onChangeHandler}
|
|
280
|
+
type={type}
|
|
266
281
|
/>
|
|
267
282
|
{renderUtilities && (
|
|
268
283
|
<TextInputGroupUtilities>
|
|
@@ -19,8 +19,8 @@ import ThIcon from '@patternfly/react-icons/dist/esm/icons/th-icon';
|
|
|
19
19
|
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
|
-
import avatarImg from './examples/avatarImg.svg';
|
|
23
22
|
import TimesIcon from '@patternfly/react-icons/dist/esm/icons/times-icon';
|
|
23
|
+
import avatarImg from './examples/avatarImg.svg';
|
|
24
24
|
import { css } from '@patternfly/react-styles';
|
|
25
25
|
import styles from '@patternfly/react-styles/css/components/Menu/menu';
|
|
26
26
|
|
|
@@ -50,7 +50,12 @@ Composable menus currently require consumer keyboard handling and use of our und
|
|
|
50
50
|
|
|
51
51
|
### Composable typeahead select
|
|
52
52
|
|
|
53
|
-
```ts file="./examples/ComposableTypeaheadSelect.tsx"
|
|
53
|
+
```ts isBeta file="./examples/ComposableTypeaheadSelect.tsx"
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Composable multiple typeahead select
|
|
57
|
+
|
|
58
|
+
```ts isBeta file="./examples/ComposableMultipleTypeaheadSelect.tsx"
|
|
54
59
|
```
|
|
55
60
|
|
|
56
61
|
### Composable drilldown menu
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Menu,
|
|
4
|
+
MenuContent,
|
|
5
|
+
MenuList,
|
|
6
|
+
MenuItem,
|
|
7
|
+
MenuItemProps,
|
|
8
|
+
MenuToggle,
|
|
9
|
+
MenuToggleElement,
|
|
10
|
+
Popper,
|
|
11
|
+
TextInputGroup,
|
|
12
|
+
TextInputGroupMain,
|
|
13
|
+
TextInputGroupUtilities,
|
|
14
|
+
ChipGroup,
|
|
15
|
+
Chip,
|
|
16
|
+
Button
|
|
17
|
+
} from '@patternfly/react-core';
|
|
18
|
+
import TimesIcon from '@patternfly/react-icons/dist/esm/icons/times-icon';
|
|
19
|
+
|
|
20
|
+
const intitalMenuItems = [
|
|
21
|
+
{ itemId: 'Option 1', children: 'Option 1' },
|
|
22
|
+
{ itemId: 'Option 2', children: 'Option 2' },
|
|
23
|
+
{ itemId: 'Option 3', children: 'Option 3' }
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
export const ComposableMultipleTypeaheadSelect: 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 [selected, setSelected] = React.useState<string[]>([]);
|
|
32
|
+
const containerRef = React.useRef<HTMLDivElement>();
|
|
33
|
+
const menuToggleRef = React.useRef<MenuToggleElement>({} as MenuToggleElement);
|
|
34
|
+
const textInputRef = React.useRef<HTMLInputElement>();
|
|
35
|
+
const menuRef = React.useRef<HTMLDivElement>();
|
|
36
|
+
|
|
37
|
+
React.useEffect(() => {
|
|
38
|
+
let newMenuItems: MenuItemProps[] = intitalMenuItems;
|
|
39
|
+
|
|
40
|
+
// Filter menu items based on the text input value when one exists
|
|
41
|
+
if (inputValue) {
|
|
42
|
+
newMenuItems = intitalMenuItems.filter(menuItem =>
|
|
43
|
+
String(menuItem.children)
|
|
44
|
+
.toLowerCase()
|
|
45
|
+
.includes(inputValue.toLowerCase())
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
// When no options are found after filtering, display 'No results found'
|
|
49
|
+
if (!newMenuItems.length) {
|
|
50
|
+
newMenuItems = [{ isDisabled: true, children: 'No results found' }];
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
setMenuItems(newMenuItems);
|
|
55
|
+
}, [inputValue]);
|
|
56
|
+
|
|
57
|
+
const handleMenuArrowKeys = (key: string) => {
|
|
58
|
+
let indexToFocus;
|
|
59
|
+
|
|
60
|
+
if (isMenuOpen) {
|
|
61
|
+
if (key === 'ArrowUp') {
|
|
62
|
+
// When no index is set or at the first index, focus to the last, otherwise decrement focus index
|
|
63
|
+
if (focusedItemIndex === null || focusedItemIndex === 0) {
|
|
64
|
+
indexToFocus = menuItems.length - 1;
|
|
65
|
+
} else {
|
|
66
|
+
indexToFocus = focusedItemIndex - 1;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (key === 'ArrowDown') {
|
|
71
|
+
// When no index is set or at the last index, focus to the first, otherwise increment focus index
|
|
72
|
+
if (focusedItemIndex === null || focusedItemIndex === menuItems.length - 1) {
|
|
73
|
+
indexToFocus = 0;
|
|
74
|
+
} else {
|
|
75
|
+
indexToFocus = focusedItemIndex + 1;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
setFocusedItemIndex(indexToFocus);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const onInputKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
|
|
84
|
+
const enabledMenuItems = menuItems.filter(menuItem => !menuItem.isDisabled);
|
|
85
|
+
const [firstMenuItem] = enabledMenuItems;
|
|
86
|
+
const focusedItem = focusedItemIndex ? enabledMenuItems[focusedItemIndex] : firstMenuItem;
|
|
87
|
+
|
|
88
|
+
switch (event.key) {
|
|
89
|
+
// Select the first available option
|
|
90
|
+
case 'Enter':
|
|
91
|
+
if (!isMenuOpen) {
|
|
92
|
+
setIsMenuOpen(prevIsOpen => !prevIsOpen);
|
|
93
|
+
} else {
|
|
94
|
+
onMenuSelect(focusedItem.itemId as string);
|
|
95
|
+
}
|
|
96
|
+
break;
|
|
97
|
+
case 'Tab':
|
|
98
|
+
case 'Escape':
|
|
99
|
+
setIsMenuOpen(false);
|
|
100
|
+
break;
|
|
101
|
+
case 'ArrowUp':
|
|
102
|
+
case 'ArrowDown':
|
|
103
|
+
handleMenuArrowKeys(event.key);
|
|
104
|
+
break;
|
|
105
|
+
default:
|
|
106
|
+
!isMenuOpen && setIsMenuOpen(true);
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
// Close the menu when a click occurs outside of the menu toggle content
|
|
111
|
+
const onDocumentClick = (event: MouseEvent) => {
|
|
112
|
+
if (
|
|
113
|
+
isMenuOpen &&
|
|
114
|
+
!menuToggleRef.current?.contains(event?.target as HTMLElement) &&
|
|
115
|
+
!menuRef?.current?.contains(event.target as Node)
|
|
116
|
+
) {
|
|
117
|
+
setIsMenuOpen(false);
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const toggleMenuOpen = () => {
|
|
122
|
+
setIsMenuOpen(!isMenuOpen);
|
|
123
|
+
textInputRef.current?.focus();
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const onTextInputChange = (value: string) => {
|
|
127
|
+
setInputValue(value);
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const onMenuSelect = (itemId: string) => {
|
|
131
|
+
if (itemId) {
|
|
132
|
+
setSelected(
|
|
133
|
+
selected.includes(itemId) ? selected.filter(selection => selection !== itemId) : [...selected, itemId]
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const toggle = (
|
|
139
|
+
<MenuToggle
|
|
140
|
+
variant="typeahead"
|
|
141
|
+
onClick={toggleMenuOpen}
|
|
142
|
+
innerRef={menuToggleRef}
|
|
143
|
+
isExpanded={isMenuOpen}
|
|
144
|
+
isFullWidth
|
|
145
|
+
>
|
|
146
|
+
<TextInputGroup isPlain>
|
|
147
|
+
<TextInputGroupMain
|
|
148
|
+
value={inputValue}
|
|
149
|
+
onClick={toggleMenuOpen}
|
|
150
|
+
onChange={onTextInputChange}
|
|
151
|
+
onKeyDown={onInputKeyDown}
|
|
152
|
+
id="typeahead-select-input"
|
|
153
|
+
autoComplete="off"
|
|
154
|
+
innerRef={textInputRef}
|
|
155
|
+
>
|
|
156
|
+
<ChipGroup>
|
|
157
|
+
{selected.map((selection, index) => (
|
|
158
|
+
<Chip
|
|
159
|
+
key={index}
|
|
160
|
+
onClick={ev => {
|
|
161
|
+
ev.stopPropagation();
|
|
162
|
+
onMenuSelect(selection);
|
|
163
|
+
}}
|
|
164
|
+
>
|
|
165
|
+
{selection}
|
|
166
|
+
</Chip>
|
|
167
|
+
))}
|
|
168
|
+
</ChipGroup>
|
|
169
|
+
</TextInputGroupMain>
|
|
170
|
+
<TextInputGroupUtilities>
|
|
171
|
+
{selected.length > 0 && (
|
|
172
|
+
<Button
|
|
173
|
+
variant="plain"
|
|
174
|
+
onClick={() => {
|
|
175
|
+
setInputValue('');
|
|
176
|
+
setSelected([]);
|
|
177
|
+
}}
|
|
178
|
+
aria-label="Clear input value"
|
|
179
|
+
>
|
|
180
|
+
<TimesIcon aria-hidden />
|
|
181
|
+
</Button>
|
|
182
|
+
)}
|
|
183
|
+
</TextInputGroupUtilities>
|
|
184
|
+
</TextInputGroup>
|
|
185
|
+
</MenuToggle>
|
|
186
|
+
);
|
|
187
|
+
const menu = (
|
|
188
|
+
<Menu
|
|
189
|
+
ref={menuRef as React.Ref<HTMLDivElement>}
|
|
190
|
+
id="multiple-typeahead-select-menu"
|
|
191
|
+
onSelect={(_ev, itemId) => onMenuSelect(itemId?.toString() as string)}
|
|
192
|
+
selected={selected}
|
|
193
|
+
>
|
|
194
|
+
<MenuContent>
|
|
195
|
+
<MenuList>
|
|
196
|
+
{menuItems.map((itemProps, index) => (
|
|
197
|
+
<MenuItem
|
|
198
|
+
key={itemProps.itemId || itemProps.children}
|
|
199
|
+
isFocused={focusedItemIndex === index}
|
|
200
|
+
className={itemProps.className}
|
|
201
|
+
{...itemProps}
|
|
202
|
+
ref={null}
|
|
203
|
+
/>
|
|
204
|
+
))}
|
|
205
|
+
</MenuList>
|
|
206
|
+
</MenuContent>
|
|
207
|
+
</Menu>
|
|
208
|
+
);
|
|
209
|
+
return (
|
|
210
|
+
<div ref={containerRef as React.Ref<HTMLDivElement>}>
|
|
211
|
+
<Popper
|
|
212
|
+
trigger={toggle}
|
|
213
|
+
popper={menu}
|
|
214
|
+
appendTo={containerRef.current}
|
|
215
|
+
isVisible={isMenuOpen}
|
|
216
|
+
onDocumentClick={onDocumentClick}
|
|
217
|
+
/>
|
|
218
|
+
</div>
|
|
219
|
+
);
|
|
220
|
+
};
|