@patternfly/react-core 4.237.10 → 4.238.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 +19 -0
- package/dist/esm/components/SearchInput/SearchInput.d.ts +10 -0
- package/dist/esm/components/SearchInput/SearchInput.d.ts.map +1 -1
- package/dist/esm/components/SearchInput/SearchInput.js +48 -6
- package/dist/esm/components/SearchInput/SearchInput.js.map +1 -1
- package/dist/js/components/SearchInput/SearchInput.d.ts +10 -0
- package/dist/js/components/SearchInput/SearchInput.d.ts.map +1 -1
- package/dist/js/components/SearchInput/SearchInput.js +48 -6
- package/dist/js/components/SearchInput/SearchInput.js.map +1 -1
- package/dist/umd/react-core.min.css +2488 -2488
- package/dist/umd/react-core.min.js +1 -1
- package/package.json +6 -6
- package/src/components/SearchInput/SearchInput.tsx +73 -7
- package/src/components/SearchInput/__tests__/SearchInput.test.tsx +82 -0
- package/src/components/SearchInput/examples/SearchInput.md +6 -1
- package/src/components/SearchInput/examples/SearchInputWithExpandable.tsx +25 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@patternfly/react-core",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.238.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",
|
|
@@ -44,16 +44,16 @@
|
|
|
44
44
|
"generate": "node scripts/copyStyles.js"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@patternfly/react-icons": "^4.
|
|
48
|
-
"@patternfly/react-styles": "^4.
|
|
49
|
-
"@patternfly/react-tokens": "^4.
|
|
47
|
+
"@patternfly/react-icons": "^4.89.0",
|
|
48
|
+
"@patternfly/react-styles": "^4.88.0",
|
|
49
|
+
"@patternfly/react-tokens": "^4.90.0",
|
|
50
50
|
"focus-trap": "6.9.2",
|
|
51
51
|
"react-dropzone": "9.0.0",
|
|
52
52
|
"tippy.js": "5.1.2",
|
|
53
53
|
"tslib": "^2.0.0"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
|
-
"@patternfly/patternfly": "4.215.
|
|
56
|
+
"@patternfly/patternfly": "4.215.1",
|
|
57
57
|
"@rollup/plugin-commonjs": "^21.0.0",
|
|
58
58
|
"@rollup/plugin-node-resolve": "^13.0.0",
|
|
59
59
|
"@rollup/plugin-replace": "^3.0.0",
|
|
@@ -70,5 +70,5 @@
|
|
|
70
70
|
"react": "^16.8.0 || ^17.0.0",
|
|
71
71
|
"react-dom": "^16.8.0 || ^17.0.0"
|
|
72
72
|
},
|
|
73
|
-
"gitHead": "
|
|
73
|
+
"gitHead": "844856ede5d24e9c60242140404ef95c4acc50b1"
|
|
74
74
|
}
|
|
@@ -21,6 +21,15 @@ export interface SearchAttribute {
|
|
|
21
21
|
display: React.ReactNode;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
export interface ExpandableInput {
|
|
25
|
+
/** Flag to indicate if the search input is expanded */
|
|
26
|
+
isExpanded: boolean;
|
|
27
|
+
/** Callback function to toggle the expandable search input */
|
|
28
|
+
onToggleExpand: (isExpanded: boolean, event: React.SyntheticEvent<HTMLButtonElement>) => void;
|
|
29
|
+
/** An accessible label for the expandable search input toggle */
|
|
30
|
+
toggleAriaLabel: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
24
33
|
export interface SearchInputProps extends Omit<React.HTMLProps<HTMLDivElement>, 'onChange' | 'results' | 'ref'> {
|
|
25
34
|
/** Additional classes added to the banner */
|
|
26
35
|
className?: string;
|
|
@@ -38,7 +47,8 @@ export interface SearchInputProps extends Omit<React.HTMLProps<HTMLDivElement>,
|
|
|
38
47
|
onChange?: (value: string, event: React.FormEvent<HTMLInputElement>) => void;
|
|
39
48
|
/** A suggestion for autocompleting */
|
|
40
49
|
hint?: string;
|
|
41
|
-
|
|
50
|
+
/** Object that makes the search input expandable/collapsible */
|
|
51
|
+
expandableInput?: ExpandableInput;
|
|
42
52
|
/** A callback for when the search button clicked changes */
|
|
43
53
|
onSearch?: (
|
|
44
54
|
value: string,
|
|
@@ -72,7 +82,6 @@ export interface SearchInputProps extends Omit<React.HTMLProps<HTMLDivElement>,
|
|
|
72
82
|
/** The number of search results returned. Either a total number of results,
|
|
73
83
|
* or a string representing the current result over the total number of results. i.e. "1 / 5" */
|
|
74
84
|
resultsCount?: number | string;
|
|
75
|
-
|
|
76
85
|
/** Array of attribute values used for dynamically generated advanced search */
|
|
77
86
|
attributes?: string[] | SearchAttribute[];
|
|
78
87
|
/* Additional elements added after the attributes in the form.
|
|
@@ -112,6 +121,7 @@ const SearchInputBase: React.FunctionComponent<SearchInputProps> = ({
|
|
|
112
121
|
onNextClick,
|
|
113
122
|
onPreviousClick,
|
|
114
123
|
innerRef,
|
|
124
|
+
expandableInput,
|
|
115
125
|
'aria-label': ariaLabel = 'Search input',
|
|
116
126
|
resetButtonLabel = 'Reset',
|
|
117
127
|
openMenuButtonAriaLabel = 'Open advanced search',
|
|
@@ -130,6 +140,23 @@ const SearchInputBase: React.FunctionComponent<SearchInputProps> = ({
|
|
|
130
140
|
const searchInputRef = React.useRef(null);
|
|
131
141
|
const ref = React.useRef(null);
|
|
132
142
|
const searchInputInputRef = innerRef || ref;
|
|
143
|
+
const searchInputExpandableToggleRef = React.useRef(null);
|
|
144
|
+
const [focusAfterExpandChange, setFocusAfterExpandChange] = React.useState(false);
|
|
145
|
+
|
|
146
|
+
const { isExpanded, onToggleExpand, toggleAriaLabel } = expandableInput || {};
|
|
147
|
+
|
|
148
|
+
React.useEffect(() => {
|
|
149
|
+
// this effect and the focusAfterExpandChange variable are needed to focus the input/toggle as needed when the
|
|
150
|
+
// expansion toggle is fired without focusing on mount
|
|
151
|
+
if (!focusAfterExpandChange) {
|
|
152
|
+
return;
|
|
153
|
+
} else if (isExpanded) {
|
|
154
|
+
searchInputInputRef?.current?.focus();
|
|
155
|
+
} else {
|
|
156
|
+
searchInputExpandableToggleRef?.current?.focus();
|
|
157
|
+
}
|
|
158
|
+
setFocusAfterExpandChange(false);
|
|
159
|
+
}, [focusAfterExpandChange, isExpanded, searchInputInputRef, searchInputExpandableToggleRef]);
|
|
133
160
|
|
|
134
161
|
React.useEffect(() => {
|
|
135
162
|
setSearchValue(value);
|
|
@@ -202,7 +229,16 @@ const SearchInputBase: React.FunctionComponent<SearchInputProps> = ({
|
|
|
202
229
|
}
|
|
203
230
|
};
|
|
204
231
|
|
|
205
|
-
const
|
|
232
|
+
const onExpandHandler = (event: React.SyntheticEvent<HTMLButtonElement>) => {
|
|
233
|
+
setSearchValue('');
|
|
234
|
+
onToggleExpand(isExpanded, event);
|
|
235
|
+
setFocusAfterExpandChange(true);
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
const renderUtilities =
|
|
239
|
+
value && (resultsCount || (!!onNextClick && !!onPreviousClick) || (!!onClear && !expandableInput));
|
|
240
|
+
|
|
241
|
+
const buildTextInputGroup = ({ ...searchInputProps } = {}) => (
|
|
206
242
|
<TextInputGroup isDisabled={isDisabled} {...searchInputProps}>
|
|
207
243
|
<TextInputGroupMain
|
|
208
244
|
hint={hint}
|
|
@@ -214,7 +250,7 @@ const SearchInputBase: React.FunctionComponent<SearchInputProps> = ({
|
|
|
214
250
|
onKeyDown={onEnter}
|
|
215
251
|
onChange={onChangeHandler}
|
|
216
252
|
/>
|
|
217
|
-
{
|
|
253
|
+
{renderUtilities && (
|
|
218
254
|
<TextInputGroupUtilities>
|
|
219
255
|
{resultsCount && <Badge isRead>{resultsCount}</Badge>}
|
|
220
256
|
{!!onNextClick && !!onPreviousClick && (
|
|
@@ -237,7 +273,7 @@ const SearchInputBase: React.FunctionComponent<SearchInputProps> = ({
|
|
|
237
273
|
</Button>
|
|
238
274
|
</div>
|
|
239
275
|
)}
|
|
240
|
-
{!!onClear && (
|
|
276
|
+
{!!onClear && !expandableInput && (
|
|
241
277
|
<Button
|
|
242
278
|
variant={ButtonVariant.plain}
|
|
243
279
|
isDisabled={isDisabled}
|
|
@@ -252,9 +288,35 @@ const SearchInputBase: React.FunctionComponent<SearchInputProps> = ({
|
|
|
252
288
|
</TextInputGroup>
|
|
253
289
|
);
|
|
254
290
|
|
|
291
|
+
const expandableToggle = (
|
|
292
|
+
<Button
|
|
293
|
+
variant={ButtonVariant.plain}
|
|
294
|
+
aria-label={toggleAriaLabel}
|
|
295
|
+
aria-expanded={isExpanded}
|
|
296
|
+
icon={isExpanded ? <TimesIcon /> : <SearchIcon />}
|
|
297
|
+
onClick={onExpandHandler}
|
|
298
|
+
ref={searchInputExpandableToggleRef}
|
|
299
|
+
/>
|
|
300
|
+
);
|
|
301
|
+
|
|
302
|
+
const buildExpandableSearchInput = ({ ...searchInputProps } = {}) => (
|
|
303
|
+
<InputGroup {...searchInputProps}>
|
|
304
|
+
{buildTextInputGroup()}
|
|
305
|
+
{expandableToggle}
|
|
306
|
+
</InputGroup>
|
|
307
|
+
);
|
|
308
|
+
|
|
309
|
+
const buildSearchTextInputGroup = ({ ...searchInputProps } = {}) => {
|
|
310
|
+
if (expandableInput) {
|
|
311
|
+
return buildExpandableSearchInput({ ...searchInputProps });
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return buildTextInputGroup({ ...searchInputProps });
|
|
315
|
+
};
|
|
316
|
+
|
|
255
317
|
const buildSearchTextInputGroupWithExtraButtons = ({ ...searchInputProps } = {}) => (
|
|
256
318
|
<InputGroup {...searchInputProps}>
|
|
257
|
-
{
|
|
319
|
+
{buildTextInputGroup()}
|
|
258
320
|
{(attributes.length > 0 || onToggleAdvancedSearch) && (
|
|
259
321
|
<Button
|
|
260
322
|
className={isSearchMenuOpen && 'pf-m-expanded'}
|
|
@@ -278,6 +340,7 @@ const SearchInputBase: React.FunctionComponent<SearchInputProps> = ({
|
|
|
278
340
|
<ArrowRightIcon />
|
|
279
341
|
</Button>
|
|
280
342
|
)}
|
|
343
|
+
{expandableInput && expandableToggle}
|
|
281
344
|
</InputGroup>
|
|
282
345
|
);
|
|
283
346
|
|
|
@@ -287,6 +350,10 @@ const SearchInputBase: React.FunctionComponent<SearchInputProps> = ({
|
|
|
287
350
|
innerRef: searchInputRef
|
|
288
351
|
};
|
|
289
352
|
|
|
353
|
+
if (!!expandableInput && !isExpanded) {
|
|
354
|
+
return <InputGroup {...searchInputProps}>{expandableToggle}</InputGroup>;
|
|
355
|
+
}
|
|
356
|
+
|
|
290
357
|
if (!!onSearch || attributes.length > 0 || !!onToggleAdvancedSearch) {
|
|
291
358
|
if (attributes.length > 0) {
|
|
292
359
|
const AdvancedSearch = (
|
|
@@ -333,7 +400,6 @@ const SearchInputBase: React.FunctionComponent<SearchInputProps> = ({
|
|
|
333
400
|
|
|
334
401
|
return appendTo !== 'inline' ? AdvancedSearchWithPopper : AdvancedSearchInline;
|
|
335
402
|
}
|
|
336
|
-
|
|
337
403
|
return buildSearchTextInputGroupWithExtraButtons({ ...searchInputProps });
|
|
338
404
|
}
|
|
339
405
|
return buildSearchTextInputGroup(searchInputProps);
|
|
@@ -167,3 +167,85 @@ describe('SearchInput', () => {
|
|
|
167
167
|
expect(document.body).toContainElement(screen.getByText('test'));
|
|
168
168
|
});
|
|
169
169
|
});
|
|
170
|
+
|
|
171
|
+
test('expandable toggle does not render by default', () => {
|
|
172
|
+
render(<SearchInput />);
|
|
173
|
+
|
|
174
|
+
expect(screen.queryByRole('button')).not.toBeInTheDocument();
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test('expandable toggle renders when a value is passed for expandableProps', () => {
|
|
178
|
+
render(
|
|
179
|
+
<SearchInput expandableInput={{ isExpanded: false, onToggleExpand: () => {}, toggleAriaLabel: 'Test label' }} />
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
expect(screen.getByRole('button')).toBeVisible();
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
test('expandable toggle is not aria-expanded when isExpanded is not true', () => {
|
|
186
|
+
render(
|
|
187
|
+
<SearchInput expandableInput={{ isExpanded: false, onToggleExpand: () => {}, toggleAriaLabel: 'Test label' }} />
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
expect(screen.getByRole('button', { expanded: false })).toBeVisible();
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
test('expandable toggle is aria-expanded when isExpanded is true', () => {
|
|
194
|
+
render(
|
|
195
|
+
<SearchInput expandableInput={{ isExpanded: true, onToggleExpand: () => {}, toggleAriaLabel: 'Test label' }} />
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
expect(screen.getByRole('button', { expanded: true })).toBeVisible();
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test('text input is not rendered when isExpanded is not true', () => {
|
|
202
|
+
render(
|
|
203
|
+
<SearchInput expandableInput={{ isExpanded: false, onToggleExpand: () => {}, toggleAriaLabel: 'Test label' }} />
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
expect(screen.queryByRole('textbox')).not.toBeInTheDocument();
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
test('text input is rendered when isExpanded is true', () => {
|
|
210
|
+
render(
|
|
211
|
+
<SearchInput expandableInput={{ isExpanded: true, onToggleExpand: () => {}, toggleAriaLabel: 'Test label' }} />
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
expect(screen.getByRole('textbox')).toBeVisible();
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
test('onToggleExpand is not called if the expandable toggle is not clicked', () => {
|
|
218
|
+
const mockOnToggleExpand = jest.fn();
|
|
219
|
+
|
|
220
|
+
render(
|
|
221
|
+
<SearchInput
|
|
222
|
+
expandableInput={{ isExpanded: true, onToggleExpand: mockOnToggleExpand, toggleAriaLabel: 'Test label' }}
|
|
223
|
+
/>
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
expect(mockOnToggleExpand).not.toHaveBeenCalled();
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
test('onToggleExpand is called if the expandable toggle is clicked', async () => {
|
|
230
|
+
const mockOnToggleExpand = jest.fn();
|
|
231
|
+
const user = userEvent.setup();
|
|
232
|
+
|
|
233
|
+
render(
|
|
234
|
+
<SearchInput
|
|
235
|
+
expandableInput={{ isExpanded: true, onToggleExpand: mockOnToggleExpand, toggleAriaLabel: 'Test label' }}
|
|
236
|
+
/>
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
await user.click(screen.getByRole('button'));
|
|
240
|
+
|
|
241
|
+
expect(mockOnToggleExpand).toHaveBeenCalledTimes(1);
|
|
242
|
+
expect(mockOnToggleExpand).toHaveBeenCalledWith(true, expect.objectContaining({ type: 'click' }));
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
test('toggleAriaLabel is applied to the expandable toggle', () => {
|
|
246
|
+
render(
|
|
247
|
+
<SearchInput expandableInput={{ isExpanded: true, onToggleExpand: () => {}, toggleAriaLabel: 'Test label' }} />
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
expect(screen.getByRole('button')).toHaveAccessibleName('Test label');
|
|
251
|
+
});
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
id: 'Search input'
|
|
3
3
|
section: components
|
|
4
4
|
cssPrefix: 'pf-c-search-input'
|
|
5
|
-
propComponents: ['SearchInput', 'SearchAttribute']
|
|
5
|
+
propComponents: ['SearchInput', 'SearchAttribute', 'ExpandableInput']
|
|
6
6
|
beta: true
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -35,6 +35,11 @@ import { ExternalLinkSquareAltIcon } from '@patternfly/react-icons';
|
|
|
35
35
|
```ts file='./SearchInputFocusSearch.tsx'
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
+
### With expandable button
|
|
39
|
+
|
|
40
|
+
```ts file='./SearchInputWithExpandable.tsx'
|
|
41
|
+
```
|
|
42
|
+
|
|
38
43
|
### Advanced
|
|
39
44
|
|
|
40
45
|
The search input component can be used to dynamically build a one to one attribute-value advanced search.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { SearchInput } from '@patternfly/react-core';
|
|
3
|
+
|
|
4
|
+
export const SearchInputWithExpandable: React.FunctionComponent = () => {
|
|
5
|
+
const [value, setValue] = React.useState('');
|
|
6
|
+
const [isExpanded, setIsExpanded] = React.useState(false);
|
|
7
|
+
|
|
8
|
+
const onChange = (value: string) => {
|
|
9
|
+
setValue(value);
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const onToggleExpand = (isExpanded: boolean) => {
|
|
13
|
+
setIsExpanded(!isExpanded);
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<SearchInput
|
|
18
|
+
placeholder="Find by name"
|
|
19
|
+
value={value}
|
|
20
|
+
onChange={onChange}
|
|
21
|
+
onClear={() => onChange('')}
|
|
22
|
+
expandableInput={{ isExpanded, onToggleExpand, toggleAriaLabel: 'Expandable search input toggle' }}
|
|
23
|
+
/>
|
|
24
|
+
);
|
|
25
|
+
};
|