@patternfly/react-core 4.221.13 → 4.221.16

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.
Files changed (36) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/esm/components/DualListSelector/DualListSelector.d.ts +4 -0
  3. package/dist/esm/components/DualListSelector/DualListSelector.d.ts.map +1 -1
  4. package/dist/esm/components/DualListSelector/DualListSelector.js +13 -2
  5. package/dist/esm/components/DualListSelector/DualListSelector.js.map +1 -1
  6. package/dist/js/components/DualListSelector/DualListSelector.d.ts +4 -0
  7. package/dist/js/components/DualListSelector/DualListSelector.d.ts.map +1 -1
  8. package/dist/js/components/DualListSelector/DualListSelector.js +13 -2
  9. package/dist/js/components/DualListSelector/DualListSelector.js.map +1 -1
  10. package/dist/umd/react-core.js +13 -2
  11. package/dist/umd/react-core.min.css +69 -69
  12. package/dist/umd/react-core.min.js +1 -1
  13. package/package.json +5 -5
  14. package/src/components/DualListSelector/DualListSelector.tsx +14 -2
  15. package/src/components/DualListSelector/examples/DualListSelector.md +38 -767
  16. package/src/components/DualListSelector/examples/DualListSelectorBasic.tsx +26 -0
  17. package/src/components/DualListSelector/examples/DualListSelectorBasicSearch.tsx +27 -0
  18. package/src/components/DualListSelector/examples/DualListSelectorBasicTooltips.tsx +34 -0
  19. package/src/components/DualListSelector/examples/DualListSelectorComplexOptionsActions.tsx +146 -0
  20. package/src/components/DualListSelector/examples/DualListSelectorComposable.tsx +214 -0
  21. package/src/components/DualListSelector/examples/DualListSelectorComposableDragDrop.tsx +190 -0
  22. package/src/components/DualListSelector/examples/{ComposableTree.tsx → DualListSelectorComposableTree.tsx} +3 -3
  23. package/src/components/DualListSelector/examples/DualListSelectorTree.tsx +91 -0
  24. package/src/components/FileUpload/examples/FileUpload.md +6 -270
  25. package/src/components/FileUpload/examples/FileUploadCustomPreview.tsx +40 -0
  26. package/src/components/FileUpload/examples/FileUploadCustomUpload.tsx +116 -0
  27. package/src/components/FileUpload/examples/FileUploadSimpleFile.tsx +31 -0
  28. package/src/components/FileUpload/examples/FileUploadSimpleText.tsx +51 -0
  29. package/src/components/FileUpload/examples/FileUploadTextWithEdits.tsx +51 -0
  30. package/src/components/FileUpload/examples/FileUploadTextWithRestrictions.tsx +71 -0
  31. package/src/components/FormSelect/examples/FormSelect.md +5 -291
  32. package/src/components/FormSelect/examples/FormSelectBasic.tsx +28 -0
  33. package/src/components/FormSelect/examples/FormSelectDisabled.tsx +28 -0
  34. package/src/components/FormSelect/examples/FormSelectGrouped.tsx +49 -0
  35. package/src/components/FormSelect/examples/FormSelectIconSpriteVariant.tsx +57 -0
  36. package/src/components/FormSelect/examples/FormSelectValidated.tsx +69 -0
@@ -2,14 +2,15 @@
2
2
  id: Dual list selector
3
3
  section: components
4
4
  cssPrefix: 'pf-c-dual-list-selector'
5
- propComponents: [
6
- 'DualListSelector',
7
- 'DualListSelectorPane',
8
- 'DualListSelectorControl',
9
- 'DualListSelectorControlsWrapper',
10
- 'DualListSelectorTree',
11
- 'DualListSelectorTreeItemData'
12
- ]
5
+ propComponents:
6
+ [
7
+ 'DualListSelector',
8
+ 'DualListSelectorPane',
9
+ 'DualListSelectorControl',
10
+ 'DualListSelectorControlsWrapper',
11
+ 'DualListSelectorTree',
12
+ 'DualListSelectorTreeItemData',
13
+ ]
13
14
  beta: true
14
15
  ---
15
16
 
@@ -23,806 +24,76 @@ import PficonSortCommonAscIcon from '@patternfly/react-icons/dist/esm/icons/pfic
23
24
 
24
25
  ### Basic
25
26
 
26
- ```js
27
- import React from 'react';
28
- import { DualListSelector } from '@patternfly/react-core';
29
-
30
- class BasicDualListSelector extends React.Component {
31
- constructor(props) {
32
- super(props);
33
- this.state = {
34
- availableOptions: ['Option 1', 'Option 2', 'Option 3', 'Option 4'],
35
- chosenOptions: []
36
- };
37
-
38
- this.onListChange = (newAvailableOptions, newChosenOptions) => {
39
- this.setState({
40
- availableOptions: newAvailableOptions.sort(),
41
- chosenOptions: newChosenOptions.sort()
42
- });
43
- };
44
- }
45
-
46
- render() {
47
- return (
48
- <DualListSelector
49
- availableOptions={this.state.availableOptions}
50
- chosenOptions={this.state.chosenOptions}
51
- onListChange={this.onListChange}
52
- id="basicSelector"
53
- />
54
- );
55
- }
56
- }
27
+ ```ts file="./DualListSelectorBasic.tsx"
57
28
  ```
58
29
 
59
30
  ### Basic with tooltips
60
31
 
61
- ```js
62
- import React from 'react';
63
- import { DualListSelector } from '@patternfly/react-core';
64
-
65
- class BasicDualListSelector extends React.Component {
66
- constructor(props) {
67
- super(props);
68
- this.state = {
69
- availableOptions: ['Option 1', 'Option 2', 'Option 3', 'Option 4'],
70
- chosenOptions: []
71
- };
72
-
73
- this.onListChange = (newAvailableOptions, newChosenOptions) => {
74
- this.setState({
75
- availableOptions: newAvailableOptions.sort(),
76
- chosenOptions: newChosenOptions.sort()
77
- });
78
- };
79
- }
80
-
81
- render() {
82
- return (
83
- <DualListSelector
84
- availableOptions={this.state.availableOptions}
85
- chosenOptions={this.state.chosenOptions}
86
- onListChange={this.onListChange}
87
- addAllTooltip="Add all options"
88
- addAllTooltipProps={{ position: 'top' }}
89
- addSelectedTooltip="Add selected options"
90
- addSelectedTooltipProps={{ position: 'right' }}
91
- removeSelectedTooltip="Remove selected options"
92
- removeSelectedTooltipProps={{ position: 'left' }}
93
- removeAllTooltip="Remove all options"
94
- removeAllTooltipProps={{ position: 'bottom' }}
95
- id="basicSelector"
96
- />
97
- );
98
- }
99
- }
32
+ ```ts file="./DualListSelectorBasicTooltips.tsx"
100
33
  ```
101
34
 
102
35
  ### Basic with search
103
36
 
104
- ```js
105
- import React from 'react';
106
- import { DualListSelector } from '@patternfly/react-core';
107
-
108
- class BasicDualListSelectorWithSearch extends React.Component {
109
- constructor(props) {
110
- super(props);
111
- this.state = {
112
- availableOptions: ['Option 1', 'Option 2', 'Option 3', 'Option 4'],
113
- chosenOptions: []
114
- };
115
-
116
- this.onListChange = (newAvailableOptions, newChosenOptions) => {
117
- this.setState({
118
- availableOptions: newAvailableOptions.sort(),
119
- chosenOptions: newChosenOptions.sort()
120
- });
121
- };
122
- }
123
-
124
- render() {
125
- return (
126
- <DualListSelector
127
- isSearchable
128
- availableOptions={this.state.availableOptions}
129
- chosenOptions={this.state.chosenOptions}
130
- onListChange={this.onListChange}
131
- id="basicSelectorWithSearch"
132
- />
133
- );
134
- }
135
- }
37
+ ```ts file="./DualListSelectorBasicSearch.tsx"
136
38
  ```
137
39
 
138
40
  ### Using more complex options with actions
139
41
 
140
- ```js
141
- import React from 'react';
142
- import { Button, ButtonVariant, Checkbox, Dropdown, DropdownItem, DualListSelector, KebabToggle } from '@patternfly/react-core';
143
- import PficonSortCommonAscIcon from '@patternfly/react-icons/dist/esm/icons/pficon-sort-common-asc-icon';
144
-
145
- class ComplexDualListSelector extends React.Component {
146
- constructor(props) {
147
- super(props);
148
- this.state = {
149
- availableOptions: [<span>Option 1</span>, <span>Option 3</span>, <span>Option 4</span>, <span>Option 2</span>],
150
- chosenOptions: [],
151
- isAvailableKebabOpen: false,
152
- isChosenKebabOpen: false,
153
- isDisabled: false
154
- };
155
-
156
- this.onSort = panel => {
157
- if (panel === 'available') {
158
- this.setState(prevState => {
159
- const available = prevState.availableOptions.sort((a, b) => {
160
- let returnValue = 0;
161
- if (a.props.children > b.props.children) returnValue = 1;
162
- if (a.props.children < b.props.children) returnValue = -1;
163
- return returnValue;
164
- });
165
- return {
166
- availableOptions: available
167
- };
168
- });
169
- }
170
-
171
- if (panel === 'chosen') {
172
- this.setState(prevState => {
173
- const chosen = prevState.chosenOptions.sort((a, b) => {
174
- let returnValue = 0;
175
- if (a.props.children > b.props.children) returnValue = 1;
176
- if (a.props.children < b.props.children) returnValue = -1;
177
- return returnValue;
178
- });
179
- return {
180
- chosenOptions: chosen
181
- };
182
- });
183
- }
184
- };
185
-
186
- this.onListChange = (newAvailableOptions, newChosenOptions) => {
187
- this.setState({
188
- availableOptions: newAvailableOptions,
189
- chosenOptions: newChosenOptions
190
- });
191
- };
192
-
193
- this.onToggle = (isOpen, pane) => {
194
- this.setState(prevState => {
195
- return {
196
- isAvailableKebabOpen: pane === 'available' ? isOpen : prevState.isAvailableKebabOpen,
197
- isChosenKebabOpen: pane === 'chosen' ? isOpen : prevState.isChosenKebabOpen
198
- };
199
- });
200
- };
201
-
202
- this.filterOption = (option, input) => {
203
- return option.props.children.includes(input);
204
- };
205
- }
206
-
207
- render() {
208
- const dropdownItems = [
209
- <DropdownItem key="link">Link</DropdownItem>,
210
- <DropdownItem key="action" component="button">
211
- Action
212
- </DropdownItem>,
213
- <DropdownItem key="second action" component="button">
214
- Second Action
215
- </DropdownItem>
216
- ];
217
-
218
- const availableOptionsActions = [
219
- <Button
220
- variant={ButtonVariant.plain}
221
- onClick={() => this.onSort('available')}
222
- aria-label="Sort"
223
- key="availableSortButton"
224
- isDisabled={this.state.isDisabled}
225
- >
226
- <PficonSortCommonAscIcon />
227
- </Button>,
228
- <Dropdown
229
- toggle={<KebabToggle isDisabled={this.state.isDisabled} onToggle={isOpen => this.onToggle(isOpen, 'available')} id="toggle-id-1" />}
230
- isOpen={this.state.isAvailableKebabOpen}
231
- isPlain
232
- dropdownItems={dropdownItems}
233
- key="availableDropdown"
234
- />
235
- ];
236
-
237
- const chosenOptionsActions = [
238
- <Button
239
- variant={ButtonVariant.plain}
240
- onClick={() => this.onSort('chosen')}
241
- aria-label="Sort"
242
- key="chosenSortButton"
243
- isDisabled={this.state.isDisabled}
244
- >
245
- <PficonSortCommonAscIcon />
246
- </Button>,
247
- <Dropdown
248
- toggle={<KebabToggle isDisabled={this.state.isDisabled} onToggle={isOpen => this.onToggle(isOpen, 'chosen')} id="toggle-id-2" />}
249
- isOpen={this.state.isChosenKebabOpen}
250
- isPlain
251
- dropdownItems={dropdownItems}
252
- key="chosenDropdown"
253
- />
254
- ];
255
-
256
- return (
257
- <React.Fragment>
258
- <DualListSelector
259
- isSearchable
260
- availableOptions={this.state.availableOptions}
261
- availableOptionsActions={availableOptionsActions}
262
- chosenOptions={this.state.chosenOptions}
263
- chosenOptionsActions={chosenOptionsActions}
264
- addAll={this.onListChange}
265
- removeAll={this.onListChange}
266
- addSelected={this.onListChange}
267
- removeSelected={this.onListChange}
268
- filterOption={this.filterOption}
269
- isDisabled={this.state.isDisabled}
270
- id="withActions"
271
- />
272
- <Checkbox
273
- key="isDisabled"
274
- id="isDisabled"
275
- label="isDisabled"
276
- aria-label="isDisabled"
277
- isChecked={this.state.isDisabled}
278
- onChange={() =>
279
- this.setState({
280
- isDisabled: !this.state.isDisabled
281
- })
282
- }
283
- />
284
- </React.Fragment>
285
- );
286
- }
287
- }
42
+ ```ts file="./DualListSelectorComplexOptionsActions.tsx"
288
43
  ```
289
44
 
290
- ### Expandable options
291
-
292
- ```js
293
- import React from 'react';
294
- import { DualListSelector } from '@patternfly/react-core';
45
+ ### With tree
295
46
 
296
- class TreeDualListSelector extends React.Component {
297
- constructor(props) {
298
- super(props);
299
- this.state = {
300
- chosenOptions: [
301
- {
302
- id: 'CF1',
303
- text: 'Chosen Folder 1',
304
- isChecked: false,
305
- checkProps: { 'aria-label': 'Chosen Folder 1' },
306
- hasBadge: true,
307
- badgeProps: { isRead: true },
308
- children: [
309
- { id: 'CO1', text: 'Chosen Option 1', isChecked: false, checkProps: { 'aria-label': 'Chosen Option 1' } },
310
- {
311
- id: 'CF1A',
312
- text: 'Chosen Folder 1A',
313
- isChecked: false,
314
- checkProps: { 'aria-label': 'Chosen Folder 1A' },
315
- children: [
316
- {
317
- id: 'CO2',
318
- text: 'Chosen Option 2',
319
- isChecked: false,
320
- checkProps: { 'aria-label': 'Chosen Option 2' }
321
- },
322
- {
323
- id: 'CO3',
324
- text: 'Chosen Option 3',
325
- isChecked: false,
326
- checkProps: { 'aria-label': 'Chosen Option 3' }
327
- }
328
- ]
329
- },
330
- { id: 'CO4', text: 'Chosen Option 4', isChecked: false, checkProps: { 'aria-label': 'Chosen Option 4' } }
331
- ]
332
- }
333
- ],
334
- availableOptions: [
335
- {
336
- id: 'F1',
337
- text: 'Folder 1',
338
- isChecked: false,
339
- checkProps: { 'aria-label': 'Folder 1' },
340
- hasBadge: true,
341
- badgeProps: { isRead: true },
342
- children: [
343
- { id: 'O1', text: 'Option 1', isChecked: false, checkProps: { 'aria-label': 'Option 1' } },
344
- {
345
- id: 'F1A',
346
- text: 'Folder 1A',
347
- isChecked: false,
348
- checkProps: { 'aria-label': 'Folder 1A' },
349
- children: [
350
- { id: 'O2', text: 'Option 2', isChecked: false, checkProps: { 'aria-label': 'Option 2' } },
351
- { id: 'O3', text: 'Option 3', isChecked: false, checkProps: { 'aria-label': 'Option 3' } }
352
- ]
353
- },
354
- { id: 'O4', text: 'Option 4', isChecked: false, checkProps: { 'aria-label': 'Option 4' } }
355
- ]
356
- },
357
- { id: 'O5', text: 'Option 5', isChecked: false, checkProps: { 'aria-label': 'Option 5' } },
358
- {
359
- id: 'F2',
360
- text: 'Folder 2',
361
- isChecked: false,
362
- checkProps: { 'aria-label': 'Folder 2' },
363
- children: [
364
- { id: 'O6', text: 'Option 6', isChecked: false, checkProps: { 'aria-label': 'Option 6' } },
365
- { id: 'O7', text: 'Option 5', isChecked: false, checkProps: { 'aria-label': 'Option 5 duplicate' } }
366
- ]
367
- }
368
- ]
369
- };
370
-
371
- this.onListChange = (newAvailableOptions, newChosenOptions) => {
372
- this.setState({
373
- availableOptions: newAvailableOptions,
374
- chosenOptions: newChosenOptions
375
- });
376
- };
377
- }
378
-
379
- render() {
380
- return (
381
- <DualListSelector
382
- availableOptions={this.state.availableOptions}
383
- chosenOptions={this.state.chosenOptions}
384
- onListChange={this.onListChange}
385
- id="basicSelector"
386
- isSearchable
387
- isTree
388
- />
389
- );
390
- }
391
- }
47
+ ```ts file="./DualListSelectorTree.tsx"
392
48
  ```
393
49
 
394
50
  ### Composable dual list selector
395
51
 
396
- For more flexibility, a Dual list selector can be built using sub components. When doing so, the intended component
397
- relationships are arranged as follows:
398
-
399
- ```js noLive
400
- import React from 'react';
401
- import { DualListSelector, DualListSelectorPane, DualListSelectorList, DualListSelectorListItem, DualListSelectorControlsWrapper, DualListSelectorControl } from '@patternfly/react-core';
52
+ For more flexibility, a dual list selector can be built using sub components. When doing so, the intended component relationships are arranged as follows:
402
53
 
54
+ ```noLive
403
55
  <DualListSelector>
404
-
405
56
  <DualListSelectorPane>
406
57
  <DualListSelectorList>
407
- <DualListSelectorListItem/>
58
+ <DualListSelectorListItem />
408
59
  </DualListSelectorList>
409
60
  </DualListSelectorPane>
410
-
61
+
411
62
  <DualListSelectorControlsWrapper>
412
- <DualListSelectorControl/> {/* The standard Dual list selector has 4 controls */}
63
+ <DualListSelectorControl /> /* The standard Dual list selector has 4 controls */
413
64
  </DualListSelectorControlsWrapper>
414
-
65
+
415
66
  <DualListSelectorPane isChosen>
416
67
  <DualListSelectorList>
417
- <DualListSelectorListItem/>
68
+ <DualListSelectorListItem />
418
69
  </DualListSelectorList>
419
70
  </DualListSelectorPane>
420
-
421
71
  </DualListSelector>
422
72
  ```
423
73
 
424
- Note: Keyboard accessibility and screen reader accessibility for the `DragDrop` component are still in development.
425
-
426
- ```js
427
- import React from 'react';
428
- import {
429
- Button,
430
- ButtonVariant,
431
- DualListSelector,
432
- DualListSelectorPane,
433
- DualListSelectorList,
434
- DualListSelectorListItem,
435
- DualListSelectorControlsWrapper,
436
- DualListSelectorControl,
437
- SearchInput
438
- } from '@patternfly/react-core';
439
- import AngleDoubleLeftIcon from '@patternfly/react-icons/dist/esm/icons/angle-double-left-icon';
440
- import AngleLeftIcon from '@patternfly/react-icons/dist/esm/icons/angle-left-icon';
441
- import AngleDoubleRightIcon from '@patternfly/react-icons/dist/esm/icons/angle-double-right-icon';
442
- import AngleRightIcon from '@patternfly/react-icons/dist/esm/icons/angle-right-icon';
443
- import PficonSortCommonAscIcon from '@patternfly/react-icons/dist/esm/icons/pficon-sort-common-asc-icon';
444
-
445
- const ComposableDualListSelector = () => {
446
- const [availableOptions, setAvailableOptions] = React.useState([
447
- { text: 'Apple', selected: false, isVisible: true },
448
- { text: 'Banana', selected: false, isVisible: true },
449
- { text: 'Pineapple', selected: false, isVisible: true },
450
- { text: 'Orange', selected: false, isVisible: true },
451
- { text: 'Grape', selected: false, isVisible: true },
452
- { text: 'Peach', selected: false, isVisible: true },
453
- { text: 'Strawberry', selected: false, isVisible: true }
454
- ]);
455
- const [chosenOptions, setChosenOptions] = React.useState([]);
456
- const [availableFilter, setAvailableFilter] = React.useState('');
457
- const [chosenFilter, setChosenFilter] = React.useState('');
458
-
459
- // callback for moving selected options between lists
460
- const moveSelected = (fromAvailable) => {
461
- const sourceOptions = fromAvailable ? availableOptions : chosenOptions;
462
- const destinationOptions = fromAvailable ? chosenOptions : availableOptions;
463
- for (let i = 0; i < sourceOptions.length; i++) {
464
- const option = sourceOptions[i];
465
- if (option.selected && option.isVisible) {
466
- sourceOptions.splice(i, 1);
467
- destinationOptions.push(option);
468
- option.selected = false;
469
- i--;
470
- }
471
- }
472
- if (fromAvailable) {
473
- setAvailableOptions([...sourceOptions]);
474
- setChosenOptions([...destinationOptions]);
475
- } else {
476
- setChosenOptions([...sourceOptions]);
477
- setAvailableOptions([...destinationOptions]);
478
- }
479
- };
480
-
481
- // callback for moving all options between lists
482
- const moveAll = (fromAvailable) => {
483
- if (fromAvailable) {
484
- setChosenOptions([...availableOptions.filter(x => x.isVisible), ...chosenOptions]);
485
- setAvailableOptions([...availableOptions.filter(x => !x.isVisible)]);
486
- } else {
487
- setAvailableOptions([...chosenOptions.filter(x => x.isVisible), ...availableOptions]);
488
- setChosenOptions([...chosenOptions.filter(x => !x.isVisible)]);
489
- }
490
- };
491
-
492
- // callback when option is selected
493
- const onOptionSelect = (event, index, isChosen) => {
494
- if (isChosen) {
495
- const newChosen = [...chosenOptions];
496
- newChosen[index].selected = !chosenOptions[index].selected;
497
- setChosenOptions(newChosen);
498
- } else {
499
- const newAvailable = [...availableOptions];
500
- newAvailable[index].selected = !availableOptions[index].selected;
501
- setAvailableOptions(newAvailable);
502
- }
503
- };
504
-
505
- // builds a search input - used in each dual list selector pane
506
- const buildSearchInput = (isAvailable) => {
507
- const onChange = (value) => {
508
- isAvailable ? setAvailableFilter(value) : setChosenFilter(value);
509
- const toFilter = isAvailable ? [...availableOptions] : [...chosenOptions];
510
- toFilter.forEach((option) => {
511
- option.isVisible = value === '' || option.text.toLowerCase().includes(value.toLowerCase());
512
- })
513
- };
514
-
515
- return (
516
- <SearchInput
517
- value={isAvailable ? availableFilter : chosenFilter}
518
- onChange={onChange}
519
- onClear={() => onChange('')}
520
- />
521
- );
522
- };
523
-
524
- // builds a sort control - passed to both dual list selector panes
525
- const buildSort = (isAvailable) => {
526
- const onSort = () => {
527
- const toSort = isAvailable ? [...availableOptions] : [...chosenOptions];
528
- toSort.sort((a,b) => {
529
- if (a.text > b.text) return 1;
530
- if (a.text < b.text) return -1;
531
- return 0;
532
- });
533
- if (isAvailable) {
534
- setAvailableOptions(toSort);
535
- } else {
536
- setChosenOptions(toSort);
537
- }
538
- };
539
-
540
- return (
541
- <Button
542
- variant={ButtonVariant.plain}
543
- onClick={onSort}
544
- aria-label="Sort"
545
- key="sortButton"
546
- >
547
- <PficonSortCommonAscIcon />
548
- </Button>
549
- );
550
- };
551
-
552
- return (
553
- <DualListSelector>
554
- <DualListSelectorPane
555
- title="Available"
556
- status={`${availableOptions.filter(x => x.selected && x.isVisible).length} of ${availableOptions.filter(x => x.isVisible).length} options selected`}
557
- searchInput={buildSearchInput(true)}
558
- actions={[buildSort(true)]}
559
- >
560
- <DualListSelectorList>
561
- {availableOptions.map((option, index) => {
562
- return option.isVisible ? (
563
- <DualListSelectorListItem
564
- key={index}
565
- isSelected={option.selected}
566
- id={`composable-option-${index}`}
567
- onOptionSelect={(e) => onOptionSelect(e, index, false)}
568
- >
569
- {option.text}
570
- </DualListSelectorListItem>
571
- ) : null;
572
- })}
573
- </DualListSelectorList>
574
- </DualListSelectorPane>
575
- <DualListSelectorControlsWrapper
576
- aria-label="Selector controls"
577
- >
578
- <DualListSelectorControl
579
- isDisabled={!availableOptions.some(option => option.selected)}
580
- onClick={() => moveSelected(true)}
581
- aria-label="Add selected"
582
- tooltipContent="Add selected"
583
- >
584
- <AngleRightIcon />
585
- </DualListSelectorControl>
586
- <DualListSelectorControl
587
- isDisabled={availableOptions.length === 0}
588
- onClick={() => moveAll(true)}
589
- aria-label="Add all"
590
- tooltipContent="Add all"
591
- >
592
- <AngleDoubleRightIcon />
593
- </DualListSelectorControl>
594
- <DualListSelectorControl
595
- isDisabled={chosenOptions.length === 0}
596
- onClick={() => moveAll(false)}
597
- aria-label="Remove all"
598
- tooltipContent="Remove all"
599
- >
600
- <AngleDoubleLeftIcon />
601
- </DualListSelectorControl>
602
- <DualListSelectorControl
603
- onClick={() => moveSelected(false)}
604
- isDisabled={!chosenOptions.some(option => option.selected)}
605
- aria-label="Remove selected"
606
- tooltipContent="Remove selected"
607
- >
608
- <AngleLeftIcon />
609
- </DualListSelectorControl>
610
- </DualListSelectorControlsWrapper>
611
- <DualListSelectorPane
612
- title="Chosen"
613
- status={`${chosenOptions.filter(x => x.selected && x.isVisible).length} of ${chosenOptions.filter(x => x.isVisible).length} options selected`}
614
- searchInput={buildSearchInput(false)}
615
- actions={[buildSort(false)]}
616
- isChosen
617
- >
618
- <DualListSelectorList>
619
- {chosenOptions.map((option, index) => {
620
- return option.isVisible ? (
621
- <DualListSelectorListItem
622
- key={index}
623
- isSelected={option.selected}
624
- id={`composable-option-${index}`}
625
- onOptionSelect={(e) => onOptionSelect(e, index, true)}
626
- >
627
- {option.text}
628
- </DualListSelectorListItem>
629
- ) : null;
630
- })}
631
- </DualListSelectorList>
632
- </DualListSelectorPane>
633
- </DualListSelector>
634
- );
635
- }
74
+ ```ts file="./DualListSelectorComposable.tsx"
636
75
  ```
637
76
 
638
- ### Reordering lists using drag and drop
77
+ ### Composable dual list selector with drag and drop
78
+
79
+ This example only allows reordering the contents of the "chosen" pane with drag and drop. To make a pane able to be reordered:
639
80
 
640
- To make a pane able to be reordered:
641
- - wrap the `DualListSelectorPane` in a `DragDrop` component
642
- - wrap the `DualListSelectorList` in a `Droppable` component
643
- - wrap the `DualListSelectorListItem` components in a `Draggable` component
644
- - define an `onDrop` callback which reorders the sortable options.
645
- - The `onDrop` function provides the starting location and destination location for a dragged item. It should return
646
- true to enable the 'drop' animation in the new location and false to enable the 'drop' animation back to the item's
81
+ - wrap the `DualListSelectorPane` in a `DragDrop` component
82
+ - wrap the `DualListSelectorList` in a `Droppable` component
83
+ - wrap the `DualListSelectorListItem` components in a `Draggable` component
84
+ - define an `onDrop` callback which reorders the sortable options.
85
+ - The `onDrop` function provides the starting location and destination location for a dragged item. It should return
86
+ true to enable the 'drop' animation in the new location and false to enable the 'drop' animation back to the item's
647
87
  old position.
648
- - define an `onDrag` callback which ensures that the drag event will not cross hairs with the `onOptionSelect` click
88
+ - define an `onDrag` callback which ensures that the drag event will not cross hairs with the `onOptionSelect` click
649
89
  event set on the option. Note: the `ignoreNextOptionSelect` state value is used to prevent selection while dragging.
650
90
 
651
- ```js
652
- import React from 'react';
653
- import {
654
- DragDrop,
655
- Droppable,
656
- Draggable,
657
- DualListSelector,
658
- DualListSelectorPane,
659
- DualListSelectorList,
660
- DualListSelectorListItem,
661
- DualListSelectorControlsWrapper,
662
- DualListSelectorControl,
663
- } from '@patternfly/react-core';
664
- import AngleDoubleLeftIcon from '@patternfly/react-icons/dist/esm/icons/angle-double-left-icon';
665
- import AngleLeftIcon from '@patternfly/react-icons/dist/esm/icons/angle-left-icon';
666
- import AngleDoubleRightIcon from '@patternfly/react-icons/dist/esm/icons/angle-double-right-icon';
667
- import AngleRightIcon from '@patternfly/react-icons/dist/esm/icons/angle-right-icon';
668
-
669
- const ComposableDualListSelector = () => {
670
- const [ignoreNextOptionSelect, setIgnoreNextOptionSelect] = React.useState(false);
671
- const [availableOptions, setAvailableOptions] = React.useState([
672
- { text: 'Apple', selected: false, isVisible: true },
673
- { text: 'Banana', selected: false, isVisible: true },
674
- { text: 'Pineapple', selected: false, isVisible: true }
675
- ]);
676
- const [chosenOptions, setChosenOptions] = React.useState([
677
- { text: 'Orange', selected: false, isVisible: true },
678
- { text: 'Grape', selected: false, isVisible: true },
679
- { text: 'Peach', selected: false, isVisible: true },
680
- { text: 'Strawberry', selected: false, isVisible: true }
681
- ]);
682
-
683
- const moveSelected = (fromAvailable) => {
684
- const sourceOptions = fromAvailable ? availableOptions : chosenOptions;
685
- const destinationOptions = fromAvailable ? chosenOptions : availableOptions;
686
- for (let i = 0; i < sourceOptions.length; i++) {
687
- const option = sourceOptions[i];
688
- if (option.selected && option.isVisible) {
689
- sourceOptions.splice(i, 1);
690
- destinationOptions.push(option);
691
- option.selected = false;
692
- i--;
693
- }
694
- }
695
- if (fromAvailable) {
696
- setAvailableOptions([...sourceOptions]);
697
- setChosenOptions([...destinationOptions]);
698
- } else {
699
- setChosenOptions([...sourceOptions]);
700
- setAvailableOptions([...destinationOptions]);
701
- }
702
- };
703
-
704
- const moveAll = (fromAvailable) => {
705
- if (fromAvailable) {
706
- setChosenOptions([...availableOptions.filter(x => x.isVisible), ...chosenOptions]);
707
- setAvailableOptions([...availableOptions.filter(x => !x.isVisible)]);
708
- } else {
709
- setAvailableOptions([...chosenOptions.filter(x => x.isVisible), ...availableOptions]);
710
- setChosenOptions([...chosenOptions.filter(x => !x.isVisible)]);
711
- }
712
- };
713
-
714
- const onOptionSelect = (event, index, isChosen) => {
715
- if (ignoreNextOptionSelect) {
716
- setIgnoreNextOptionSelect(false);
717
- return;
718
- }
719
- if (isChosen) {
720
- const newChosen = [...chosenOptions];
721
- newChosen[index].selected = !chosenOptions[index].selected;
722
- setChosenOptions(newChosen);
723
- } else {
724
- const newAvailable = [...availableOptions];
725
- newAvailable[index].selected = !availableOptions[index].selected;
726
- setAvailableOptions(newAvailable);
727
- }
728
- };
729
-
730
- const onDrop = (source, dest) => {
731
- if (dest){
732
- const newList = [...chosenOptions];
733
- const [removed] = newList.splice(source.index, 1);
734
- newList.splice(dest.index, 0, removed);
735
- setChosenOptions(newList);
736
- return true;
737
- }
738
- return false;
739
- };
91
+ Note: Keyboard accessibility and screen reader accessibility for the `DragDrop` component are still in development.
740
92
 
741
- return (
742
- <DualListSelector>
743
- <DualListSelectorPane
744
- title="Available"
745
- status={`${availableOptions.filter(x => x.selected && x.isVisible).length} of ${availableOptions.filter(x => x.isVisible).length} options selected`}
746
- >
747
- <DualListSelectorList>
748
- {availableOptions.map((option, index) => {
749
- return option.isVisible ? (
750
- <DualListSelectorListItem
751
- key={index}
752
- isSelected={option.selected}
753
- id={`composable-available-option-${index}`}
754
- onOptionSelect={(e) => onOptionSelect(e, index, false)}
755
- >
756
- {option.text}
757
- </DualListSelectorListItem>
758
- ) : null;
759
- })}
760
- </DualListSelectorList>
761
- </DualListSelectorPane>
762
- <DualListSelectorControlsWrapper
763
- aria-label="Selector controls"
764
- >
765
- <DualListSelectorControl
766
- isDisabled={!availableOptions.some(option => option.selected)}
767
- onClick={() => moveSelected(true)}
768
- aria-label="Add selected"
769
- >
770
- <AngleRightIcon />
771
- </DualListSelectorControl>
772
- <DualListSelectorControl
773
- isDisabled={availableOptions.length === 0}
774
- onClick={() => moveAll(true)}
775
- aria-label="Add all"
776
- >
777
- <AngleDoubleRightIcon />
778
- </DualListSelectorControl>
779
- <DualListSelectorControl
780
- isDisabled={chosenOptions.length === 0}
781
- onClick={() => moveAll(false)}
782
- aria-label="Remove all"
783
- >
784
- <AngleDoubleLeftIcon />
785
- </DualListSelectorControl>
786
- <DualListSelectorControl
787
- onClick={() => moveSelected(false)}
788
- isDisabled={!chosenOptions.some(option => option.selected)}
789
- aria-label="Remove selected"
790
- >
791
- <AngleLeftIcon />
792
- </DualListSelectorControl>
793
- </DualListSelectorControlsWrapper>
794
- <DragDrop onDrag={() => { setIgnoreNextOptionSelect(true); return true; }} onDrop={onDrop}>
795
- <DualListSelectorPane
796
- title="Chosen"
797
- status={`${chosenOptions.filter(x => x.selected && x.isVisible).length} of ${chosenOptions.filter(x => x.isVisible).length} options selected`}
798
- isChosen
799
- >
800
- <Droppable hasNoWrapper>
801
- <DualListSelectorList>
802
- {chosenOptions.map((option, index) => {
803
- return option.isVisible ? (
804
- <Draggable key={index} hasNoWrapper>
805
- <DualListSelectorListItem
806
- isSelected={option.selected}
807
- id={`composable-chosen-option-${index}`}
808
- onOptionSelect={(e) => onOptionSelect(e, index, true)}
809
- isDraggable
810
- >
811
- {option.text}
812
- </DualListSelectorListItem>
813
- </Draggable>
814
- ) : null;
815
- })}
816
- </DualListSelectorList>
817
- </Droppable>
818
- </DualListSelectorPane>
819
- </DragDrop>
820
- </DualListSelector>
821
- );
822
- }
93
+ ```ts file="DualListSelectorComposableDragDrop.tsx"
823
94
  ```
824
95
 
825
- ### Composable dual list selector tree
826
- ```ts file="ComposableTree.tsx"
827
- ```
96
+ ### Composable dual list selector with tree
828
97
 
98
+ ```ts file="DualListSelectorComposableTree.tsx"
99
+ ```