@patternfly/react-core 4.271.0 → 4.271.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@patternfly/react-core",
3
- "version": "4.271.0",
3
+ "version": "4.271.1",
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",
@@ -71,5 +71,5 @@
71
71
  "react": "^16.8 || ^17 || ^18",
72
72
  "react-dom": "^16.8 || ^17 || ^18"
73
73
  },
74
- "gitHead": "25c2750b4d7f220fe08d5cfa5ffacd06102d63fe"
74
+ "gitHead": "541f76a7ca04fc3206d75f13648dda8cc92c9e15"
75
75
  }
@@ -13,7 +13,7 @@ import { SearchInput } from '../SearchInput';
13
13
  * such as sorting, can also be passed into this sub-component.
14
14
  */
15
15
 
16
- export interface DualListSelectorPaneProps {
16
+ export interface DualListSelectorPaneProps extends Omit<React.HTMLProps<HTMLDivElement>, 'title'> {
17
17
  /** Additional classes applied to the dual list selector pane. */
18
18
  className?: string;
19
19
  /** A dual list selector list or dual list selector tree to be rendered in the pane. */
@@ -65,6 +65,8 @@ export interface DualListSelectorPaneProps {
65
65
  searchInputAriaLabel?: string;
66
66
  /** @hide Callback for updating the filtered options in DualListSelector. To be used when isSearchable is true. */
67
67
  onFilterUpdate?: (newFilteredOptions: React.ReactNode[], paneType: string, isSearchReset: boolean) => void;
68
+ /** Minimum height of the list of options rendered in the pane. **/
69
+ listMinHeight?: string;
68
70
  }
69
71
 
70
72
  export const DualListSelectorPane: React.FunctionComponent<DualListSelectorPaneProps> = ({
@@ -87,6 +89,7 @@ export const DualListSelectorPane: React.FunctionComponent<DualListSelectorPaneP
87
89
  filterOption,
88
90
  id = getUniqueId('dual-list-selector-pane'),
89
91
  isDisabled = false,
92
+ listMinHeight,
90
93
  ...props
91
94
  }: DualListSelectorPaneProps) => {
92
95
  const [input, setInput] = React.useState('');
@@ -198,12 +201,21 @@ export const DualListSelectorPane: React.FunctionComponent<DualListSelectorPaneP
198
201
  displayOption={displayOption}
199
202
  id={`${id}-list`}
200
203
  isDisabled={isDisabled}
204
+ {...(listMinHeight && {
205
+ style: { '--pf-c-dual-list-selector__menu--MinHeight': listMinHeight } as React.CSSProperties
206
+ })}
201
207
  >
202
208
  {children}
203
209
  </DualListSelectorListWrapper>
204
210
  )}
205
211
  {isTree && (
206
- <DualListSelectorListWrapper aria-labelledby={`${id}-status`} id={`${id}-list`}>
212
+ <DualListSelectorListWrapper
213
+ aria-labelledby={`${id}-status`}
214
+ id={`${id}-list`}
215
+ {...(listMinHeight && {
216
+ style: { '--pf-c-dual-list-selector__menu--MinHeight': listMinHeight } as React.CSSProperties
217
+ })}
218
+ >
207
219
  {options.length > 0 ? (
208
220
  <DualListSelectorList>
209
221
  <DualListSelectorTree
@@ -20,6 +20,7 @@ import AngleLeftIcon from '@patternfly/react-icons/dist/esm/icons/angle-left-ico
20
20
  import AngleDoubleRightIcon from '@patternfly/react-icons/dist/esm/icons/angle-double-right-icon';
21
21
  import AngleRightIcon from '@patternfly/react-icons/dist/esm/icons/angle-right-icon';
22
22
  import PficonSortCommonAscIcon from '@patternfly/react-icons/dist/esm/icons/pficon-sort-common-asc-icon';
23
+ import SearchIcon from '@patternfly/react-icons/dist/esm/icons/search-icon';
23
24
 
24
25
  ## Examples
25
26
 
@@ -8,13 +8,20 @@ import {
8
8
  DualListSelectorListItem,
9
9
  DualListSelectorControlsWrapper,
10
10
  DualListSelectorControl,
11
- SearchInput
11
+ SearchInput,
12
+ Title,
13
+ EmptyState,
14
+ EmptyStateVariant,
15
+ EmptyStateIcon,
16
+ EmptyStateBody,
17
+ EmptyStatePrimary
12
18
  } from '@patternfly/react-core';
13
19
  import AngleDoubleLeftIcon from '@patternfly/react-icons/dist/esm/icons/angle-double-left-icon';
14
20
  import AngleLeftIcon from '@patternfly/react-icons/dist/esm/icons/angle-left-icon';
15
21
  import AngleDoubleRightIcon from '@patternfly/react-icons/dist/esm/icons/angle-double-right-icon';
16
22
  import AngleRightIcon from '@patternfly/react-icons/dist/esm/icons/angle-right-icon';
17
23
  import PficonSortCommonAscIcon from '@patternfly/react-icons/dist/esm/icons/pficon-sort-common-asc-icon';
24
+ import SearchIcon from '@patternfly/react-icons/dist/esm/icons/search-icon';
18
25
 
19
26
  interface Option {
20
27
  text: string;
@@ -86,25 +93,23 @@ export const DualListSelectorComposable: React.FunctionComponent = () => {
86
93
  }
87
94
  };
88
95
 
89
- // builds a search input - used in each dual list selector pane
90
- const buildSearchInput = (isAvailable: boolean) => {
91
- const onChange = (value: string) => {
92
- isAvailable ? setAvailableFilter(value) : setChosenFilter(value);
93
- const toFilter = isAvailable ? [...availableOptions] : [...chosenOptions];
94
- toFilter.forEach(option => {
95
- option.isVisible = value === '' || option.text.toLowerCase().includes(value.toLowerCase());
96
- });
97
- };
98
-
99
- return (
100
- <SearchInput
101
- value={isAvailable ? availableFilter : chosenFilter}
102
- onChange={onChange}
103
- onClear={() => onChange('')}
104
- />
105
- );
96
+ const onFilterChange = (value: string, isAvailable: boolean) => {
97
+ isAvailable ? setAvailableFilter(value) : setChosenFilter(value);
98
+ const toFilter = isAvailable ? [...availableOptions] : [...chosenOptions];
99
+ toFilter.forEach(option => {
100
+ option.isVisible = value === '' || option.text.toLowerCase().includes(value.toLowerCase());
101
+ });
106
102
  };
107
103
 
104
+ // builds a search input - used in each dual list selector pane
105
+ const buildSearchInput = (isAvailable: boolean) => (
106
+ <SearchInput
107
+ value={isAvailable ? availableFilter : chosenFilter}
108
+ onChange={value => onFilterChange(value, isAvailable)}
109
+ onClear={() => onFilterChange('', isAvailable)}
110
+ />
111
+ );
112
+
108
113
  // builds a sort control - passed to both dual list selector panes
109
114
  const buildSort = (isAvailable: boolean) => {
110
115
  const onSort = () => {
@@ -132,6 +137,21 @@ export const DualListSelectorComposable: React.FunctionComponent = () => {
132
137
  );
133
138
  };
134
139
 
140
+ const buildEmptyState = (isAvailable: boolean) => (
141
+ <EmptyState variant={EmptyStateVariant.small}>
142
+ <EmptyStateIcon icon={SearchIcon} />
143
+ <Title headingLevel="h4" size="md">
144
+ No results found
145
+ </Title>
146
+ <EmptyStateBody>No results match the filter criteria. Clear all filters and try again.</EmptyStateBody>
147
+ <EmptyStatePrimary>
148
+ <Button variant="link" onClick={() => onFilterChange('', isAvailable)}>
149
+ Clear all filters
150
+ </Button>
151
+ </EmptyStatePrimary>
152
+ </EmptyState>
153
+ );
154
+
135
155
  return (
136
156
  <DualListSelector>
137
157
  <DualListSelectorPane
@@ -141,21 +161,27 @@ export const DualListSelectorComposable: React.FunctionComponent = () => {
141
161
  } options selected`}
142
162
  searchInput={buildSearchInput(true)}
143
163
  actions={[buildSort(true)]}
164
+ listMinHeight="300px"
144
165
  >
145
- <DualListSelectorList>
146
- {availableOptions.map((option, index) =>
147
- option.isVisible ? (
148
- <DualListSelectorListItem
149
- key={index}
150
- isSelected={option.selected}
151
- id={`composable-available-option-${index}`}
152
- onOptionSelect={e => onOptionSelect(e, index, false)}
153
- >
154
- {option.text}
155
- </DualListSelectorListItem>
156
- ) : null
157
- )}
158
- </DualListSelectorList>
166
+ {availableFilter !== '' &&
167
+ availableOptions.filter(option => option.isVisible).length === 0 &&
168
+ buildEmptyState(true)}
169
+ {availableOptions.filter(option => option.isVisible).length > 0 && (
170
+ <DualListSelectorList>
171
+ {availableOptions.map((option, index) =>
172
+ option.isVisible ? (
173
+ <DualListSelectorListItem
174
+ key={index}
175
+ isSelected={option.selected}
176
+ id={`composable-available-option-${index}`}
177
+ onOptionSelect={e => onOptionSelect(e, index, false)}
178
+ >
179
+ {option.text}
180
+ </DualListSelectorListItem>
181
+ ) : null
182
+ )}
183
+ </DualListSelectorList>
184
+ )}
159
185
  </DualListSelectorPane>
160
186
  <DualListSelectorControlsWrapper>
161
187
  <DualListSelectorControl
@@ -199,21 +225,25 @@ export const DualListSelectorComposable: React.FunctionComponent = () => {
199
225
  searchInput={buildSearchInput(false)}
200
226
  actions={[buildSort(false)]}
201
227
  isChosen
228
+ listMinHeight="300px"
202
229
  >
203
- <DualListSelectorList>
204
- {chosenOptions.map((option, index) =>
205
- option.isVisible ? (
206
- <DualListSelectorListItem
207
- key={index}
208
- isSelected={option.selected}
209
- id={`composable-chosen-option-${index}`}
210
- onOptionSelect={e => onOptionSelect(e, index, true)}
211
- >
212
- {option.text}
213
- </DualListSelectorListItem>
214
- ) : null
215
- )}
216
- </DualListSelectorList>
230
+ {chosenFilter !== '' && chosenOptions.filter(option => option.isVisible).length === 0 && buildEmptyState(false)}
231
+ {chosenOptions.filter(option => option.isVisible).length > 0 && (
232
+ <DualListSelectorList>
233
+ {chosenOptions.map((option, index) =>
234
+ option.isVisible ? (
235
+ <DualListSelectorListItem
236
+ key={index}
237
+ isSelected={option.selected}
238
+ id={`composable-chosen-option-${index}`}
239
+ onOptionSelect={e => onOptionSelect(e, index, true)}
240
+ >
241
+ {option.text}
242
+ </DualListSelectorListItem>
243
+ ) : null
244
+ )}
245
+ </DualListSelectorList>
246
+ )}
217
247
  </DualListSelectorPane>
218
248
  </DualListSelector>
219
249
  );
@@ -7,12 +7,20 @@ import {
7
7
  DualListSelectorControl,
8
8
  DualListSelectorTree,
9
9
  DualListSelectorTreeItemData,
10
- SearchInput
10
+ SearchInput,
11
+ Title,
12
+ Button,
13
+ EmptyState,
14
+ EmptyStateVariant,
15
+ EmptyStateIcon,
16
+ EmptyStateBody,
17
+ EmptyStatePrimary
11
18
  } from '@patternfly/react-core';
12
19
  import AngleDoubleLeftIcon from '@patternfly/react-icons/dist/esm/icons/angle-double-left-icon';
13
20
  import AngleLeftIcon from '@patternfly/react-icons/dist/esm/icons/angle-left-icon';
14
21
  import AngleDoubleRightIcon from '@patternfly/react-icons/dist/esm/icons/angle-double-right-icon';
15
22
  import AngleRightIcon from '@patternfly/react-icons/dist/esm/icons/angle-right-icon';
23
+ import SearchIcon from '@patternfly/react-icons/dist/esm/icons/search-icon';
16
24
 
17
25
  interface FoodNode {
18
26
  id: string;
@@ -235,19 +243,37 @@ export const DualListSelectorComposableTree: React.FunctionComponent<ExampleProp
235
243
  isChosen ? chosenLeafIds.includes(id) : !chosenLeafIds.includes(id)
236
244
  ).length;
237
245
  const status = `${numSelected} of ${numOptions} options selected`;
246
+ const filterApplied = isChosen ? chosenFilter !== '' : availableFilter !== '';
238
247
  return (
239
248
  <DualListSelectorPane
240
249
  title={isChosen ? 'Chosen' : 'Available'}
241
250
  status={status}
242
251
  searchInput={buildSearchInput(isChosen)}
243
252
  isChosen={isChosen}
253
+ listMinHeight="300px"
244
254
  >
245
- <DualListSelectorList>
246
- <DualListSelectorTree
247
- data={options}
248
- onOptionCheck={(e, isChecked, itemData) => onOptionCheck(e, isChecked, itemData, isChosen)}
249
- />
250
- </DualListSelectorList>
255
+ {filterApplied && options.length === 0 && (
256
+ <EmptyState variant={EmptyStateVariant.small}>
257
+ <EmptyStateIcon icon={SearchIcon} />
258
+ <Title headingLevel="h4" size="md">
259
+ No results found
260
+ </Title>
261
+ <EmptyStateBody>No results match the filter criteria. Clear all filters and try again.</EmptyStateBody>
262
+ <EmptyStatePrimary>
263
+ <Button variant="link" onClick={() => (isChosen ? setChosenFilter('') : setAvailableFilter(''))}>
264
+ Clear all filters
265
+ </Button>
266
+ </EmptyStatePrimary>
267
+ </EmptyState>
268
+ )}
269
+ {options.length > 0 && (
270
+ <DualListSelectorList>
271
+ <DualListSelectorTree
272
+ data={options}
273
+ onOptionCheck={(e, isChecked, itemData) => onOptionCheck(e, isChecked, itemData, isChosen)}
274
+ />
275
+ </DualListSelectorList>
276
+ )}
251
277
  </DualListSelectorPane>
252
278
  );
253
279
  };