@patternfly/react-core 4.227.3 → 4.227.4

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 (23) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/package.json +5 -5
  3. package/src/components/Menu/examples/Menu.md +19 -1290
  4. package/src/components/Menu/examples/MenuBasic.tsx +51 -0
  5. package/src/components/Menu/examples/MenuFilteringWithTextInput.tsx +52 -0
  6. package/src/components/Menu/examples/MenuOptionMultiSelect.tsx +30 -0
  7. package/src/components/Menu/examples/MenuOptionSingleSelect.tsx +27 -0
  8. package/src/components/Menu/examples/MenuScrollable.tsx +48 -0
  9. package/src/components/Menu/examples/MenuScrollableCustomMenuHeight.tsx +48 -0
  10. package/src/components/Menu/examples/MenuWithActions.tsx +75 -0
  11. package/src/components/Menu/examples/MenuWithCheckbox.tsx +2 -2
  12. package/src/components/Menu/examples/MenuWithDescription.tsx +35 -0
  13. package/src/components/Menu/examples/{MenuDrilldown.tsx → MenuWithDrilldown.tsx} +1 -1
  14. package/src/components/Menu/examples/MenuWithDrilldownBreadcrumbs.tsx +331 -0
  15. package/src/components/Menu/examples/{MenuDrilldownInitialState.tsx → MenuWithDrilldownInitialState.tsx} +0 -0
  16. package/src/components/Menu/examples/{MenuDrilldownSubmenuFunctions.tsx → MenuWithDrilldownSubmenuFunctions.tsx} +1 -1
  17. package/src/components/Menu/examples/MenuWithFavorites.tsx +104 -0
  18. package/src/components/Menu/examples/MenuWithFooter.tsx +40 -0
  19. package/src/components/Menu/examples/MenuWithIcons.tsx +33 -0
  20. package/src/components/Menu/examples/MenuWithLinks.tsx +30 -0
  21. package/src/components/Menu/examples/MenuWithSeparators.tsx +26 -0
  22. package/src/components/Menu/examples/MenuWithTitledGroups.tsx +46 -0
  23. package/src/components/Menu/examples/MenuWithViewMore.tsx +118 -0
@@ -20,112 +20,12 @@ import AngleLeftIcon from '@patternfly/react-icons/dist/esm/icons/angle-left-ico
20
20
 
21
21
  ### Basic
22
22
 
23
- ```js
24
- import React from 'react';
25
- import { Menu, MenuContent, MenuList, MenuItem, Checkbox } from '@patternfly/react-core';
26
-
27
- class MenuBasicList extends React.Component {
28
- constructor(props) {
29
- super(props);
30
- this.state = {
31
- activeItem: 0,
32
- isPlain: false
33
- };
34
- this.onSelect = (event, itemId) => {
35
- console.log(`clicked ${itemId}`);
36
- this.setState({
37
- activeItem: itemId
38
- });
39
- };
40
- this.togglePlain = checked => {
41
- this.setState({
42
- isPlain: checked
43
- });
44
- };
45
- }
46
-
47
- render() {
48
- const { activeItem, isPlain } = this.state;
49
- return (
50
- <React.Fragment>
51
- <Menu activeItemId={activeItem} onSelect={this.onSelect} isPlain={isPlain}>
52
- <MenuContent>
53
- <MenuList>
54
- <MenuItem itemId={0}>Action</MenuItem>
55
- <MenuItem
56
- itemId={1}
57
- to="#default-link2"
58
- // just for demo so that navigation is not triggered
59
- onClick={event => event.preventDefault()}
60
- >
61
- Link
62
- </MenuItem>
63
- <MenuItem isDisabled>Disabled action</MenuItem>
64
- <MenuItem isDisabled to="#default-link4">
65
- Disabled link
66
- </MenuItem>
67
- </MenuList>
68
- </MenuContent>
69
- </Menu>
70
- <div style={{ marginTop: 20 }}>
71
- <Checkbox
72
- label="Plain menu"
73
- isChecked={isPlain}
74
- onChange={this.togglePlain}
75
- aria-label="plain menu checkbox"
76
- id="toggle-plain"
77
- name="toggle-plain"
78
- />
79
- </div>
80
- </React.Fragment>
81
- );
82
- }
83
- }
23
+ ```ts file="MenuBasic.tsx"
84
24
  ```
85
25
 
86
26
  ### With icons
87
27
 
88
- ```js
89
- import React from 'react';
90
- import { Menu, MenuContent, MenuList, MenuItem } from '@patternfly/react-core';
91
- import CodeBranchIcon from '@patternfly/react-icons/dist/esm/icons/code-branch-icon';
92
- import LayerGroupIcon from '@patternfly/react-icons/dist/esm/icons/layer-group-icon';
93
- import CubeIcon from '@patternfly/react-icons/dist/esm/icons/cube-icon';
94
-
95
- class MenuIconsList extends React.Component {
96
- constructor(props) {
97
- super(props);
98
- this.state = {
99
- activeItem: 0
100
- };
101
- this.onSelect = (event, itemId) => {
102
- this.setState({
103
- activeItem: itemId
104
- });
105
- };
106
- }
107
-
108
- render() {
109
- const { activeItem } = this.state;
110
- return (
111
- <Menu onSelect={this.onSelect} activeItemId={activeItem}>
112
- <MenuContent>
113
- <MenuList>
114
- <MenuItem icon={<CodeBranchIcon aria-hidden />} itemId={0}>
115
- From git
116
- </MenuItem>
117
- <MenuItem icon={<LayerGroupIcon aria-hidden />} itemId={1}>
118
- Container image
119
- </MenuItem>
120
- <MenuItem icon={<CubeIcon aria-hidden />} itemId={2}>
121
- Docker file
122
- </MenuItem>
123
- </MenuList>
124
- </MenuContent>
125
- </Menu>
126
- );
127
- }
128
- }
28
+ ```ts file="MenuWithIcons.tsx"
129
29
  ```
130
30
 
131
31
  ### With checkbox
@@ -135,1258 +35,87 @@ class MenuIconsList extends React.Component {
135
35
 
136
36
  ### Filtering with text input
137
37
 
138
- ```js
139
- import React from 'react';
140
- import { Menu, MenuList, MenuItem, MenuContent, MenuInput, TextInput, Divider } from '@patternfly/react-core';
141
-
142
- class MenuWithFiltering extends React.Component {
143
- constructor(props) {
144
- super(props);
145
- this.state = {
146
- activeItem: 0,
147
- input: ''
148
- };
149
-
150
- this.onSelect = (event, itemId) => {
151
- this.setState({
152
- activeItem: itemId
153
- });
154
- };
155
-
156
- this.handleTextInputChange = (value, field) => {
157
- this.setState({ [field]: value });
158
- };
159
- }
160
-
161
- render() {
162
- const { activeItem, input } = this.state;
163
- const menuListItemsText = ['Action 1', 'Action 2', 'Action 3'];
164
-
165
- const menuListItems = menuListItemsText
166
- .filter(item => !input || item.toLowerCase().includes(input.toString().toLowerCase()))
167
- .map((currentValue, index) => (
168
- <MenuItem key={currentValue} itemId={index}>
169
- {currentValue}
170
- </MenuItem>
171
- ));
172
- if (input && menuListItems.length === 0) {
173
- menuListItems.push(
174
- <MenuItem isDisabled key="no result">
175
- No results found
176
- </MenuItem>
177
- );
178
- }
179
-
180
- return (
181
- <Menu onSelect={this.onSelect} activeItemId={activeItem}>
182
- <MenuInput>
183
- <TextInput
184
- value={input}
185
- aria-label="Filter menu items"
186
- iconVariant="search"
187
- type="search"
188
- onChange={value => this.handleTextInputChange(value, 'input')}
189
- />
190
- </MenuInput>
191
- <Divider />
192
- <MenuContent>
193
- <MenuList>{menuListItems}</MenuList>
194
- </MenuContent>
195
- </Menu>
196
- );
197
- }
198
- }
38
+ ```ts file="MenuFilteringWithTextInput.tsx"
199
39
  ```
200
40
 
201
41
  ### With links
202
42
 
203
- ```js
204
- import React from 'react';
205
- import { Menu, MenuContent, MenuList, MenuItem } from '@patternfly/react-core';
206
-
207
- class MenuWithLinks extends React.Component {
208
- constructor(props) {
209
- super(props);
210
- this.state = {
211
- activeItem: 0
212
- };
213
- this.onSelect = (event, itemId) => {
214
- this.setState({
215
- activeItem: itemId
216
- });
217
- };
218
- }
219
-
220
- render() {
221
- const { activeItem } = this.state;
222
- return (
223
- <Menu onSelect={this.onSelect} activeItemId={activeItem}>
224
- <MenuContent>
225
- <MenuList>
226
- <MenuItem isExternalLink to="#default-link1" itemId={0}>
227
- Link 1
228
- </MenuItem>
229
- <MenuItem isExternalLink to="#default-link2" itemId={1}>
230
- Link 2
231
- </MenuItem>
232
- <MenuItem to="#default-link3" itemId={2}>
233
- Link 3
234
- </MenuItem>
235
- </MenuList>
236
- </MenuContent>
237
- </Menu>
238
- );
239
- }
240
- }
43
+ ```ts file="MenuWithLinks.tsx"
241
44
  ```
242
45
 
243
46
  ### With separator(s)
244
47
 
245
- ```js
246
- import React from 'react';
247
- import { Divider, Menu, MenuContent, MenuList, MenuItem } from '@patternfly/react-core';
248
-
249
- class MenuWithSeparators extends React.Component {
250
- constructor(props) {
251
- super(props);
252
- this.state = {
253
- activeItem: 0
254
- };
255
- this.onSelect = (event, itemId) => {
256
- this.setState({
257
- activeItem: itemId
258
- });
259
- };
260
- }
261
-
262
- render() {
263
- const { activeItem } = this.state;
264
- return (
265
- <Menu onSelect={this.onSelect} activeItemId={activeItem}>
266
- <MenuContent>
267
- <MenuList>
268
- <MenuItem itemId={0}>Action 1</MenuItem>
269
- <MenuItem itemId={1}>Action 2</MenuItem>
270
- <Divider component="li" />
271
- <MenuItem itemId={2}>Action 3</MenuItem>
272
- </MenuList>
273
- </MenuContent>
274
- </Menu>
275
- );
276
- }
277
- }
48
+ ```ts file="MenuWithSeparators.tsx"
278
49
  ```
279
50
 
280
51
  ### With titled groups
281
52
 
282
- ```js
283
- import React from 'react';
284
- import { Menu, MenuContent, MenuGroup, MenuList, MenuItem, Divider } from '@patternfly/react-core';
285
-
286
- class MenuWithTitledGroups extends React.Component {
287
- constructor(props) {
288
- super(props);
289
- this.state = {
290
- activeItem: 0
291
- };
292
- this.onSelect = (event, itemId) => {
293
- this.setState({
294
- activeItem: itemId
295
- });
296
- };
297
- }
298
-
299
- render() {
300
- const { activeItem } = this.state;
301
- return (
302
- <Menu onSelect={this.onSelect} activeItemId={activeItem}>
303
- <MenuContent>
304
- <MenuGroup>
305
- <MenuList>
306
- <MenuItem to="#" itemId={0}>
307
- Link not in group
308
- </MenuItem>
309
- </MenuList>
310
- </MenuGroup>
311
- <Divider />
312
- <MenuGroup label="Group 1" labelHeadingLevel="h3">
313
- <MenuList>
314
- <MenuItem to="#" itemId={1}>
315
- Link 1
316
- </MenuItem>
317
- <MenuItem itemId={2}>Link 2</MenuItem>
318
- </MenuList>
319
- </MenuGroup>
320
- <Divider />
321
- <MenuGroup label="Group 2" labelHeadingLevel="h3">
322
- <MenuList>
323
- <MenuItem to="#" itemId={3}>
324
- Link 1
325
- </MenuItem>
326
- <MenuItem to="#" itemId={4}>
327
- Link 2
328
- </MenuItem>
329
- </MenuList>
330
- </MenuGroup>
331
- </MenuContent>
332
- </Menu>
333
- );
334
- }
335
- }
53
+ ```ts file="MenuWithTitledGroups.tsx"
336
54
  ```
337
55
 
338
56
  ### With description
339
57
 
340
- ```js
341
- import React from 'react';
342
- import { Menu, MenuContent, MenuList, MenuItem } from '@patternfly/react-core';
343
- import CodeBranchIcon from '@patternfly/react-icons/dist/esm/icons/code-branch-icon';
344
-
345
- class MenuWithDescription extends React.Component {
346
- constructor(props) {
347
- super(props);
348
- this.state = {
349
- activeItem: 0
350
- };
351
- this.onSelect = (event, itemId) => {
352
- this.setState({
353
- activeItem: itemId
354
- });
355
- };
356
- }
357
-
358
- render() {
359
- const { activeItem } = this.state;
360
- return (
361
- <Menu onSelect={this.onSelect} activeItemId={activeItem}>
362
- <MenuContent>
363
- <MenuList>
364
- <MenuItem icon={<CodeBranchIcon aria-hidden />} description="Description" itemId={0}>
365
- Action 1
366
- </MenuItem>
367
- <MenuItem isDisabled icon={<CodeBranchIcon aria-hidden />} description="Description" itemId={1}>
368
- Action 2 disabled
369
- </MenuItem>
370
- <MenuItem
371
- icon={<CodeBranchIcon aria-hidden />}
372
- description="Nunc non ornare ex, et pretium dui. Duis nec augue at urna elementum blandit tincidunt eget metus. Aenean sed metus id urna dignissim interdum. Aenean vel nisl vitae arcu vehicula pulvinar eget nec turpis. Cras sit amet est est."
373
- itemId={2}
374
- >
375
- Action 3
376
- </MenuItem>
377
- </MenuList>
378
- </MenuContent>
379
- </Menu>
380
- );
381
- }
382
- }
58
+ ```ts file="MenuWithDescription.tsx"
383
59
  ```
384
60
 
385
61
  ### With actions
386
62
 
387
- ```js
388
- import React from 'react';
389
- import { Menu, MenuContent, MenuGroup, MenuList, MenuItem, MenuItemAction } from '@patternfly/react-core';
390
- import BarsIcon from '@patternfly/react-icons/dist/esm/icons/bars-icon';
391
- import ClipboardIcon from '@patternfly/react-icons/dist/esm/icons/clipboard-icon';
392
- import CodeBranchIcon from '@patternfly/react-icons/dist/esm/icons/code-branch-icon';
393
- import BellIcon from '@patternfly/react-icons/dist/esm/icons/bell-icon';
394
-
395
- class MenuWithActions extends React.Component {
396
- constructor(props) {
397
- super(props);
398
- this.state = {
399
- activeItem: 0,
400
- isActionMenuOpen: false,
401
- selectedItems: [0, 2, 3]
402
- };
403
-
404
- this.onSelect = (event, itemId) => {
405
- if (this.state.selectedItems.indexOf(itemId) !== -1) {
406
- this.setState({
407
- selectedItems: this.state.selectedItems.filter(id => id !== itemId)
408
- });
409
- } else {
410
- this.setState({
411
- selectedItems: [...this.state.selectedItems, itemId]
412
- });
413
- }
414
- };
415
- }
416
-
417
- render() {
418
- const { activeItem, selectedItems } = this.state;
419
-
420
- return (
421
- <Menu
422
- onSelect={this.onSelect}
423
- onActionClick={(event, itemId, actionId) => console.log(`clicked on ${itemId} - ${actionId}`)}
424
- activeItemId={activeItem}
425
- >
426
- <MenuContent>
427
- <MenuGroup label="Actions" labelHeadingLevel="h3">
428
- <MenuList>
429
- <MenuItem
430
- isSelected={selectedItems.indexOf(0) !== -1}
431
- actions={
432
- <MenuItemAction
433
- icon={<CodeBranchIcon aria-hidden />}
434
- actionId="code"
435
- onClick={() => console.log('clicked on code icon')}
436
- aria-label="Code"
437
- />
438
- }
439
- description="This is a description"
440
- itemId={0}
441
- >
442
- Item 1
443
- </MenuItem>
444
- <MenuItem
445
- isDisabled
446
- isSelected={selectedItems.indexOf(1) !== -1}
447
- actions={<MenuItemAction icon={<BellIcon aria-hidden />} actionId="alert" aria-label="Alert" />}
448
- description="This is a description"
449
- itemId={1}
450
- >
451
- Item 2
452
- </MenuItem>
453
- <MenuItem
454
- isSelected={selectedItems.indexOf(2) !== -1}
455
- actions={<MenuItemAction icon={<ClipboardIcon aria-hidden />} actionId="copy" aria-label="Copy" />}
456
- itemId={2}
457
- >
458
- Item 3
459
- </MenuItem>
460
- <MenuItem
461
- isSelected={selectedItems.indexOf(3) !== -1}
462
- actions={<MenuItemAction icon={<BarsIcon aria-hidden />} actionId="expand" aria-label="Expand" />}
463
- description="This is a description"
464
- itemId={3}
465
- >
466
- Item 4
467
- </MenuItem>
468
- </MenuList>
469
- </MenuGroup>
470
- </MenuContent>
471
- </Menu>
472
- );
473
- }
474
- }
63
+ ```ts file="MenuWithActions.tsx"
475
64
  ```
476
65
 
477
66
  ### With favorites
478
67
 
479
- ```js
480
- import React from 'react';
481
- import { Menu, MenuContent, MenuItem, MenuItemAction, MenuGroup, MenuList, Divider } from '@patternfly/react-core';
482
- import BarsIcon from '@patternfly/react-icons/dist/esm/icons/bars-icon';
483
- import ClipboardIcon from '@patternfly/react-icons/dist/esm/icons/clipboard-icon';
484
- import BellIcon from '@patternfly/react-icons/dist/esm/icons/bell-icon';
485
-
486
- class MenuWithFavorites extends React.Component {
487
- constructor(props) {
488
- super(props);
489
- this.state = {
490
- activeItem: 0,
491
- favorites: []
492
- };
493
-
494
- this.onSelect = (event, itemId) => {
495
- this.setState({
496
- activeItem: itemId
497
- });
498
- };
499
- this.onFavorite = (event, itemId, actionId) => {
500
- console.log(`clicked ${itemId} - ${actionId}`);
501
- if (actionId === 'fav') {
502
- const isFavorite = this.state.favorites.includes(itemId);
503
- if (isFavorite) {
504
- this.setState({
505
- favorites: this.state.favorites.filter(fav => fav !== itemId)
506
- });
507
- } else {
508
- this.setState({
509
- favorites: [...this.state.favorites, itemId]
510
- });
511
- }
512
- }
513
- };
514
- }
515
-
516
- render() {
517
- const { activeItem, favorites } = this.state;
518
-
519
- const items = [
520
- {
521
- text: 'Item 1',
522
- description: 'Description 1',
523
- itemId: 'item-1',
524
- action: <BarsIcon aria-hidden />,
525
- actionId: 'bars'
526
- },
527
- {
528
- text: 'Item 2',
529
- description: 'Description 2',
530
- itemId: 'item-2',
531
- action: <ClipboardIcon aria-hidden />,
532
- actionId: 'clipboard'
533
- },
534
- {
535
- text: 'Item 3',
536
- description: 'Description 3',
537
- itemId: 'item-3',
538
- action: <BellIcon aria-hidden />,
539
- actionId: 'bell'
540
- }
541
- ];
542
-
543
- return (
544
- <Menu onSelect={this.onSelect} onActionClick={this.onFavorite} activeItemId={activeItem}>
545
- <MenuContent>
546
- {favorites.length > 0 && (
547
- <React.Fragment>
548
- <MenuGroup label="Favorites" labelHeadingLevel="h3">
549
- <MenuList>
550
- {items
551
- // map the items into the favorites group that have been favorited
552
- .filter(item => favorites.includes(item.itemId))
553
- .map(item => {
554
- const { text, description, itemId, action, actionId } = item;
555
- return (
556
- <MenuItem
557
- key={`fav-${itemId}`}
558
- isFavorited
559
- description={description}
560
- itemId={itemId}
561
- actions={<MenuItemAction actionId={actionId} icon={action} aria-label={actionId} />}
562
- >
563
- {text}
564
- </MenuItem>
565
- );
566
- })}
567
- </MenuList>
568
- </MenuGroup>
569
- <Divider />
570
- </React.Fragment>
571
- )}
572
- <MenuGroup label="All actions" labelHeadingLevel="h3">
573
- <MenuList>
574
- {items.map(item => {
575
- const { text, description, itemId, action, actionId } = item;
576
- const isFavorited = favorites.includes(item.itemId);
577
- return (
578
- <MenuItem
579
- key={itemId}
580
- isFavorited={isFavorited}
581
- description={description}
582
- itemId={itemId}
583
- actions={<MenuItemAction actionId={actionId} icon={action} aria-label={actionId} />}
584
- >
585
- {text}
586
- </MenuItem>
587
- );
588
- })}
589
- </MenuList>
590
- </MenuGroup>
591
- </MenuContent>
592
- </Menu>
593
- );
594
- }
595
- }
68
+ ```ts file="MenuWithFavorites.tsx"
596
69
  ```
597
70
 
598
71
  ### Option single select
599
72
 
600
- ```js
601
- import React from 'react';
602
- import { Menu, MenuContent, MenuList, MenuItem } from '@patternfly/react-core';
603
- import TableIcon from '@patternfly/react-icons/dist/esm/icons/table-icon';
604
-
605
- class MenuOptionSingleSelect extends React.Component {
606
- constructor(props) {
607
- super(props);
608
- this.state = {
609
- activeItem: 0,
610
- selectedItem: 0
611
- };
612
-
613
- this.onSelect = (event, itemId) => {
614
- this.setState({
615
- activeItem: itemId,
616
- selectedItem: itemId
617
- });
618
- };
619
- }
620
-
621
- render() {
622
- const { activeItem, selectedItem } = this.state;
623
- return (
624
- <Menu onSelect={this.onSelect} activeItemId={activeItem} selected={selectedItem}>
625
- <MenuContent>
626
- <MenuList>
627
- <MenuItem itemId={0}>Option 1</MenuItem>
628
- <MenuItem itemId={1}>Option 2</MenuItem>
629
- <MenuItem icon={<TableIcon aria-hidden />} itemId={2}>
630
- Option 3
631
- </MenuItem>
632
- </MenuList>
633
- </MenuContent>
634
- </Menu>
635
- );
636
- }
637
- }
73
+ ```ts file="MenuOptionSingleSelect.tsx"
638
74
  ```
639
75
 
640
76
  ### Option multi select
641
77
 
642
- ```js
643
- import React from 'react';
644
- import { Menu, MenuContent, MenuList, MenuItem } from '@patternfly/react-core';
645
- import TableIcon from '@patternfly/react-icons/dist/esm/icons/table-icon';
646
-
647
- class MenuOptionMultiSelect extends React.Component {
648
- constructor(props) {
649
- super(props);
650
- this.state = {
651
- activeItem: 0,
652
- selectedItems: []
653
- };
654
- this.onSelect = (event, itemId) => {
655
- if (this.state.selectedItems.indexOf(itemId) !== -1) {
656
- this.setState({
657
- selectedItems: this.state.selectedItems.filter(id => id !== itemId)
658
- });
659
- } else {
660
- this.setState({
661
- selectedItems: [...this.state.selectedItems, itemId]
662
- });
663
- }
664
- };
665
- }
666
-
667
- render() {
668
- const { activeItem, selectedItems } = this.state;
669
- return (
670
- <Menu onSelect={this.onSelect} activeItemId={activeItem} selected={selectedItems}>
671
- <MenuContent>
672
- <MenuList>
673
- <MenuItem itemId={0}>Option 1</MenuItem>
674
- <MenuItem itemId={1}>Option 2</MenuItem>
675
- <MenuItem icon={<TableIcon aria-hidden />} itemId={2}>
676
- Option 3
677
- </MenuItem>
678
- </MenuList>
679
- </MenuContent>
680
- </Menu>
681
- );
682
- }
683
- }
78
+ ```ts file="MenuOptionMultiSelect.tsx"
684
79
  ```
685
80
 
686
81
  ### With drilldown
687
82
 
688
- ```ts file="./MenuDrilldown.tsx" isBeta
83
+ ```ts file="./MenuWithDrilldown.tsx" isBeta
689
84
  ```
690
85
 
691
86
  ### With drilldown - initial drill in state
692
87
 
693
88
  To render an initially drilled in menu, the `menuDrilledIn`, `drilldownPath`, and `activeMenu` states must be set to an initial state. The `menuHeights` state must also be set, defining the height of the root menu. The `setHeight` function passed into the `onGetMenuHeight` property must also account for updating heights, other than the root menu, as menus drill in and out of view.
694
89
 
695
- ```ts file="./MenuDrilldownInitialState.tsx" isBeta
90
+ ```ts file="./MenuWithDrilldownInitialState.tsx" isBeta
696
91
  ```
697
92
 
698
93
  ### With drilldown - submenu functions
699
94
 
700
- ```ts file="./MenuDrilldownSubmenuFunctions.tsx" isBeta
95
+ ```ts file="./MenuWithDrilldownSubmenuFunctions.tsx" isBeta
701
96
  ```
702
97
 
703
98
  ### With drilldown breadcrumbs
704
99
 
705
- ```js isBeta
706
- import React from 'react';
707
- import {
708
- Menu,
709
- MenuContent,
710
- MenuList,
711
- MenuItem,
712
- Divider,
713
- DrilldownMenu,
714
- Breadcrumb,
715
- BreadcrumbItem,
716
- BreadcrumbHeading,
717
- MenuBreadcrumb,
718
- Dropdown,
719
- DropdownItem,
720
- BadgeToggle,
721
- Checkbox
722
- } from '@patternfly/react-core';
723
- import StorageDomainIcon from '@patternfly/react-icons/dist/esm/icons/storage-domain-icon';
724
- import CodeBranchIcon from '@patternfly/react-icons/dist/esm/icons/code-branch-icon';
725
- import LayerGroupIcon from '@patternfly/react-icons/dist/esm/icons/layer-group-icon';
726
- import AngleLeftIcon from '@patternfly/react-icons/dist/esm/icons/angle-left-icon';
727
- import CubeIcon from '@patternfly/react-icons/dist/esm/icons/cube-icon';
728
-
729
- class MenuWithDrilldownBreadcrumbs extends React.Component {
730
- constructor(props) {
731
- super(props);
732
- this.state = {
733
- menuDrilledIn: [],
734
- drilldownPath: [],
735
- menuHeights: {},
736
- activeMenu: 'breadcrumbs-rootMenu',
737
- breadcrumb: undefined,
738
- withMaxMenuHeight: false
739
- };
740
-
741
- this.onToggle = (isOpen, key) => {
742
- switch (key) {
743
- case 'app':
744
- this.setState({
745
- breadcrumb: this.appGroupingBreadcrumb(isOpen)
746
- });
747
- break;
748
- case 'label':
749
- this.setState({
750
- breadcrumb: this.labelsBreadcrumb(isOpen)
751
- });
752
- break;
753
- default:
754
- break;
755
- }
756
- };
757
-
758
- this.onToggleMaxMenuHeight = checked => {
759
- this.setState({
760
- withMaxMenuHeight: checked
761
- });
762
- };
763
-
764
- this.drillOut = (toMenuId, fromPathId, breadcrumb) => {
765
- const indexOfMenuId = this.state.menuDrilledIn.indexOf(toMenuId);
766
- const menuDrilledInSansLast = this.state.menuDrilledIn.slice(0, indexOfMenuId);
767
- const indexOfMenuIdPath = this.state.drilldownPath.indexOf(fromPathId);
768
- const pathSansLast = this.state.drilldownPath.slice(0, indexOfMenuIdPath);
769
- this.setState({
770
- menuDrilledIn: menuDrilledInSansLast,
771
- drilldownPath: pathSansLast,
772
- activeMenu: toMenuId,
773
- breadcrumb
774
- });
775
- };
776
- this.setHeight = (menuId, height) => {
777
- if (!this.state.menuHeights[menuId]) {
778
- this.setState({
779
- menuHeights: {
780
- ...this.state.menuHeights,
781
- [menuId]: height
782
- }
783
- });
784
- }
785
- };
786
- this.drillIn = (fromMenuId, toMenuId, pathId) => {
787
- this.setState({
788
- menuDrilledIn: [...this.state.menuDrilledIn, fromMenuId],
789
- drilldownPath: [...this.state.drilldownPath, pathId],
790
- activeMenu: toMenuId
791
- });
792
- };
793
-
794
- this.startRolloutBreadcrumb = (
795
- <Breadcrumb>
796
- <BreadcrumbItem component="button" onClick={() => this.drillOut('breadcrumbs-rootMenu', 'group:start_rollout', null)}>
797
- Root
798
- </BreadcrumbItem>
799
- <BreadcrumbHeading component="button">Start rollout</BreadcrumbHeading>
800
- </Breadcrumb>
801
- );
802
-
803
- this.appGroupingBreadcrumb = isOpen => {
804
- return (
805
- <Breadcrumb>
806
- <BreadcrumbItem component="button" onClick={() => this.drillOut('breadcrumbs-rootMenu', 'group:start_rollout', null)}>
807
- Root
808
- </BreadcrumbItem>
809
- <BreadcrumbItem isDropdown>
810
- <Dropdown
811
- toggle={
812
- <BadgeToggle id="toggle-id" onToggle={open => this.onToggle(open, 'app')}>
813
- 1
814
- </BadgeToggle>
815
- }
816
- isOpen={isOpen}
817
- dropdownItems={[
818
- <DropdownItem
819
- key="dropdown-start"
820
- component="button"
821
- icon={<AngleLeftIcon />}
822
- onClick={() =>
823
- this.drillOut('breadcrumbs-drilldownMenuStart', 'group:app_grouping_start', this.startRolloutBreadcrumb)
824
- }
825
- >
826
- Start rollout
827
- </DropdownItem>
828
- ]}
829
- />
830
- </BreadcrumbItem>
831
- <BreadcrumbHeading component="button">Application Grouping</BreadcrumbHeading>
832
- </Breadcrumb>
833
- );
834
- };
835
-
836
- this.labelsBreadcrumb = isOpen => (
837
- <Breadcrumb>
838
- <BreadcrumbItem component="button" onClick={() => this.drillOut('breadcrumbs-rootMenu', 'group:start_rollout', null)}>
839
- Root
840
- </BreadcrumbItem>
841
- <BreadcrumbItem isDropdown>
842
- <Dropdown
843
- toggle={
844
- <BadgeToggle id="toggle-id" onToggle={open => this.onToggle(open, 'label')}>
845
- 1
846
- </BadgeToggle>
847
- }
848
- isOpen={isOpen}
849
- dropdownItems={[
850
- <DropdownItem
851
- key="dropdown-start"
852
- component="button"
853
- icon={<AngleLeftIcon />}
854
- onClick={() => this.drillOut('breadcrumbs-drilldownMenuStart', 'group:labels_start', this.startRolloutBreadcrumb)}
855
- >
856
- Start rollout
857
- </DropdownItem>
858
- ]}
859
- />
860
- </BreadcrumbItem>
861
- <BreadcrumbHeading component="button">Labels</BreadcrumbHeading>
862
- </Breadcrumb>
863
- );
864
-
865
- this.pauseRolloutsBreadcrumb = (
866
- <Breadcrumb>
867
- <BreadcrumbItem component="button" onClick={() => this.drillOut('breadcrumbs-rootMenu', 'group:pause_rollout', null)}>
868
- Root
869
- </BreadcrumbItem>
870
- <BreadcrumbHeading component="button">Pause rollouts</BreadcrumbHeading>
871
- </Breadcrumb>
872
- );
873
-
874
- this.pauseRolloutsAppGrpBreadcrumb = (
875
- <Breadcrumb>
876
- <BreadcrumbItem component="button" onClick={() => this.drillOut('breadcrumbs-rootMenu', 'group:pause_rollout', null)}>
877
- Root
878
- </BreadcrumbItem>
879
- <BreadcrumbItem
880
- component="button"
881
- onClick={() => this.drillOut('breadcrumbs-drilldownMenuPause', 'group:app_grouping', this.pauseRolloutsBreadcrumb)}
882
- >
883
- Pause rollouts
884
- </BreadcrumbItem>
885
- <BreadcrumbHeading component="button">Application Grouping</BreadcrumbHeading>
886
- </Breadcrumb>
887
- );
888
-
889
- this.pauseRolloutsLabelsBreadcrumb = (
890
- <Breadcrumb>
891
- <BreadcrumbItem component="button" onClick={() => this.drillOut('breadcrumbs-rootMenu', 'group:pause_rollout', null)}>
892
- Root
893
- </BreadcrumbItem>
894
- <BreadcrumbItem
895
- component="button"
896
- onClick={() => this.drillOut('breadcrumbs-drilldownMenuPause', 'group:labels', this.pauseRolloutsBreadcrumb)}
897
- >
898
- Pause rollouts
899
- </BreadcrumbItem>
900
- <BreadcrumbHeading component="button">Labels</BreadcrumbHeading>
901
- </Breadcrumb>
902
- );
903
-
904
- this.addStorageBreadcrumb = (
905
- <Breadcrumb>
906
- <BreadcrumbItem component="button" onClick={() => this.drillOut('breadcrumbs-rootMenu', 'group:storage', null)}>
907
- Root
908
- </BreadcrumbItem>
909
- <BreadcrumbHeading component="button">Add storage</BreadcrumbHeading>
910
- </Breadcrumb>
911
- );
912
- }
913
-
914
- render() {
915
- const { menuDrilledIn, drilldownPath, activeMenu, menuHeights, breadcrumb, withMaxMenuHeight } = this.state;
916
-
917
- return (
918
- <>
919
- <Checkbox
920
- label="Set max menu height"
921
- isChecked={withMaxMenuHeight}
922
- onChange={this.onToggleMaxMenuHeight}
923
- aria-label="Set max menu height checkbox"
924
- id="toggle-max-menu-height"
925
- name="toggle-max-menu-height"
926
- />
927
- <br />
928
- <Menu
929
- id="breadcrumbs-rootMenu"
930
- containsDrilldown
931
- drilldownItemPath={drilldownPath}
932
- drilledInMenus={menuDrilledIn}
933
- activeMenu={activeMenu}
934
- onDrillIn={this.drillIn}
935
- onDrillOut={this.drillOut}
936
- onGetMenuHeight={this.setHeight}
937
- >
938
- {breadcrumb && (
939
- <>
940
- <MenuBreadcrumb>{breadcrumb}</MenuBreadcrumb>
941
- <Divider component="li" />
942
- </>
943
- )}
944
- <MenuContent menuHeight={`${menuHeights[activeMenu]}px`} maxMenuHeight={withMaxMenuHeight ? '100px' : 'auto'}>
945
- <MenuList>
946
- <MenuItem
947
- itemId="group:start_rollout"
948
- direction="down"
949
- onClick={() => this.setState({ breadcrumb: this.startRolloutBreadcrumb })}
950
- drilldownMenu={
951
- <DrilldownMenu id="breadcrumbs-drilldownMenuStart">
952
- <MenuItem
953
- itemId="group:app_grouping_start"
954
- description="Groups A-G"
955
- direction="down"
956
- onClick={() => this.setState({ breadcrumb: this.appGroupingBreadcrumb(false) })}
957
- drilldownMenu={
958
- <DrilldownMenu id="breadcrumbs-drilldownMenuStartGrouping">
959
- <MenuItem itemId="group_a">Group A</MenuItem>
960
- <MenuItem itemId="group_b">Group B</MenuItem>
961
- <MenuItem itemId="group_c">Group C</MenuItem>
962
- <MenuItem itemId="group_d">Group D</MenuItem>
963
- <MenuItem itemId="group_e">Group E</MenuItem>
964
- <MenuItem itemId="group_f">Group F</MenuItem>
965
- <MenuItem itemId="group_g">Group G</MenuItem>
966
- </DrilldownMenu>
967
- }
968
- >
969
- Application grouping
970
- </MenuItem>
971
- <MenuItem itemId="count">Count</MenuItem>
972
- <MenuItem
973
- itemId="group:labels_start"
974
- direction="down"
975
- onClick={() => this.setState({ breadcrumb: this.labelsBreadcrumb(false) })}
976
- drilldownMenu={
977
- <DrilldownMenu id="breadcrumbs-drilldownMenuStartLabels">
978
- <MenuItem itemId="label_1">Label 1</MenuItem>
979
- <MenuItem itemId="label_2">Label 2</MenuItem>
980
- <MenuItem itemId="label_3">Label 3</MenuItem>
981
- </DrilldownMenu>
982
- }
983
- >
984
- Labels
985
- </MenuItem>
986
- <MenuItem itemId="annotations">Annotations</MenuItem>
987
- </DrilldownMenu>
988
- }
989
- >
990
- Start rollout
991
- </MenuItem>
992
- <MenuItem
993
- itemId="group:pause_rollout"
994
- direction="down"
995
- onClick={() => this.setState({ breadcrumb: this.pauseRolloutsBreadcrumb })}
996
- drilldownMenu={
997
- <DrilldownMenu id="breadcrumbs-drilldownMenuPause">
998
- <MenuItem
999
- itemId="group:app_grouping"
1000
- description="Groups A-C"
1001
- direction="down"
1002
- onClick={() => this.setState({ breadcrumb: this.pauseRolloutsAppGrpBreadcrumb })}
1003
- drilldownMenu={
1004
- <DrilldownMenu id="breadcrumbs-drilldownMenuGrouping">
1005
- <MenuItem itemId="group_a">Group A</MenuItem>
1006
- <MenuItem itemId="group_b">Group B</MenuItem>
1007
- <MenuItem itemId="group_c">Group C</MenuItem>
1008
- </DrilldownMenu>
1009
- }
1010
- >
1011
- Application grouping
1012
- </MenuItem>
1013
- <MenuItem itemId="count">Count</MenuItem>
1014
- <MenuItem
1015
- itemId="group:labels"
1016
- direction="down"
1017
- onClick={() => this.setState({ breadcrumb: this.pauseRolloutsLabelsBreadcrumb })}
1018
- drilldownMenu={
1019
- <DrilldownMenu id="breadcrumbs-drilldownMenuLabels">
1020
- <MenuItem itemId="label_1">Label 1</MenuItem>
1021
- <MenuItem itemId="label_2">Label 2</MenuItem>
1022
- <MenuItem itemId="label_3">Label 3</MenuItem>
1023
- </DrilldownMenu>
1024
- }
1025
- >
1026
- Labels
1027
- </MenuItem>
1028
- <MenuItem itemId="annotations">Annotations</MenuItem>
1029
- </DrilldownMenu>
1030
- }
1031
- >
1032
- Pause rollouts
1033
- </MenuItem>
1034
- <MenuItem
1035
- itemId="group:storage"
1036
- icon={<StorageDomainIcon aria-hidden />}
1037
- direction="down"
1038
- onClick={() => this.setState({ breadcrumb: this.addStorageBreadcrumb })}
1039
- drilldownMenu={
1040
- <DrilldownMenu id="breadcrumbs-drilldownMenuStorage">
1041
- <MenuItem icon={<CodeBranchIcon aria-hidden />} itemId="git">
1042
- From git
1043
- </MenuItem>
1044
- <MenuItem icon={<LayerGroupIcon aria-hidden />} itemId="container">
1045
- Container image
1046
- </MenuItem>
1047
- <MenuItem icon={<CubeIcon aria-hidden />} itemId="docker">
1048
- Docker file
1049
- </MenuItem>
1050
- </DrilldownMenu>
1051
- }
1052
- >
1053
- Add storage
1054
- </MenuItem>
1055
- <MenuItem itemId="edit">Edit</MenuItem>
1056
- <MenuItem itemId="delete_deployment">Delete deployment config</MenuItem>
1057
- </MenuList>
1058
- </MenuContent>
1059
- </Menu>
1060
- </>
1061
- );
1062
- }
1063
- }
100
+ ```ts file="MenuWithDrilldownBreadcrumbs.tsx" isBeta
1064
101
  ```
1065
102
 
1066
103
  ### Scrollable
1067
104
 
1068
- ```js
1069
- import React from 'react';
1070
- import { Menu, MenuContent, MenuList, MenuItem } from '@patternfly/react-core';
1071
-
1072
- class MenuBasicList extends React.Component {
1073
- constructor(props) {
1074
- super(props);
1075
- this.state = {
1076
- activeItem: 0
1077
- };
1078
- this.onSelect = (event, itemId) => {
1079
- console.log(`clicked ${itemId}`);
1080
- this.setState({
1081
- activeItem: itemId
1082
- });
1083
- };
1084
- }
1085
-
1086
- render() {
1087
- const { activeItem } = this.state;
1088
- return (
1089
- <Menu activeItemId={activeItem} onSelect={this.onSelect} isScrollable>
1090
- <MenuContent>
1091
- <MenuList>
1092
- <MenuItem>Action 1</MenuItem>
1093
- <MenuItem>Action 2</MenuItem>
1094
- <MenuItem>Action 3</MenuItem>
1095
- <MenuItem>Action 4</MenuItem>
1096
- <MenuItem>Action 5</MenuItem>
1097
- <MenuItem>Action 6</MenuItem>
1098
- <MenuItem>Action 7</MenuItem>
1099
- <MenuItem>Action 8</MenuItem>
1100
- <MenuItem>Action 9</MenuItem>
1101
- <MenuItem>Action 10</MenuItem>
1102
- <MenuItem>Action 11</MenuItem>
1103
- <MenuItem>Action 12</MenuItem>
1104
- <MenuItem>Action 13</MenuItem>
1105
- <MenuItem>Action 14</MenuItem>
1106
- <MenuItem>Action 15</MenuItem>
1107
- <MenuItem
1108
- itemId={1}
1109
- to="#default-link2"
1110
- // just for demo so that navigation is not triggered
1111
- onClick={event => event.preventDefault()}
1112
- >
1113
- Link
1114
- </MenuItem>
1115
- <MenuItem isDisabled>Disabled action</MenuItem>
1116
- <MenuItem isDisabled to="#default-link4">
1117
- Disabled link
1118
- </MenuItem>
1119
- </MenuList>
1120
- </MenuContent>
1121
- </Menu>
1122
- );
1123
- }
1124
- }
105
+ ```ts file="MenuScrollable.tsx"
1125
106
  ```
1126
107
 
1127
108
  ### Scrollable with custom menu height
1128
109
 
1129
- ```js
1130
- import React from 'react';
1131
- import { Menu, MenuContent, MenuList, MenuItem } from '@patternfly/react-core';
1132
-
1133
- class MenuBasicList extends React.Component {
1134
- constructor(props) {
1135
- super(props);
1136
- this.state = {
1137
- activeItem: 0
1138
- };
1139
- this.onSelect = (event, itemId) => {
1140
- console.log(`clicked ${itemId}`);
1141
- this.setState({
1142
- activeItem: itemId
1143
- });
1144
- };
1145
- }
1146
-
1147
- render() {
1148
- const { activeItem } = this.state;
1149
- return (
1150
- <Menu activeItemId={activeItem} onSelect={this.onSelect} isScrollable>
1151
- <MenuContent menuHeight="200px">
1152
- <MenuList>
1153
- <MenuItem>Action 1</MenuItem>
1154
- <MenuItem>Action 2</MenuItem>
1155
- <MenuItem>Action 3</MenuItem>
1156
- <MenuItem>Action 4</MenuItem>
1157
- <MenuItem>Action 5</MenuItem>
1158
- <MenuItem>Action 6</MenuItem>
1159
- <MenuItem>Action 7</MenuItem>
1160
- <MenuItem>Action 8</MenuItem>
1161
- <MenuItem>Action 9</MenuItem>
1162
- <MenuItem>Action 10</MenuItem>
1163
- <MenuItem>Action 11</MenuItem>
1164
- <MenuItem>Action 12</MenuItem>
1165
- <MenuItem>Action 13</MenuItem>
1166
- <MenuItem>Action 14</MenuItem>
1167
- <MenuItem>Action 15</MenuItem>
1168
- <MenuItem
1169
- itemId={1}
1170
- to="#default-link2"
1171
- // just for demo so that navigation is not triggered
1172
- onClick={event => event.preventDefault()}
1173
- >
1174
- Link
1175
- </MenuItem>
1176
- <MenuItem isDisabled>Disabled action</MenuItem>
1177
- <MenuItem isDisabled to="#default-link4">
1178
- Disabled link
1179
- </MenuItem>
1180
- </MenuList>
1181
- </MenuContent>
1182
- </Menu>
1183
- );
1184
- }
1185
- }
110
+ ```ts file="MenuScrollableCustomMenuHeight.tsx"
1186
111
  ```
1187
112
 
1188
113
  ### With footer
1189
114
 
1190
- ```js
1191
- import React from 'react';
1192
- import { Menu, MenuList, MenuItem, MenuContent, MenuFooter, Button } from '@patternfly/react-core';
1193
-
1194
- class FooterMenu extends React.Component {
1195
- constructor(props) {
1196
- super(props);
1197
- this.state = {
1198
- activeItem: 0
1199
- };
1200
- this.onSelect = (event, itemId) => {
1201
- console.log(`clicked ${itemId}`);
1202
- this.setState({
1203
- activeItem: itemId
1204
- });
1205
- };
1206
- }
1207
-
1208
- render() {
1209
- const { activeItem } = this.state;
1210
- return (
1211
- <Menu activeItemId={activeItem} onSelect={this.onSelect}>
1212
- <MenuContent>
1213
- <MenuList>
1214
- <MenuItem itemId={0}>Action</MenuItem>
1215
- <MenuItem
1216
- itemId={1}
1217
- to="#default-link2"
1218
- // just for demo so that navigation is not triggered
1219
- onClick={event => event.preventDefault()}
1220
- >
1221
- Link
1222
- </MenuItem>
1223
- <MenuItem isDisabled>Disabled action</MenuItem>
1224
- <MenuItem isDisabled to="#default-link4">
1225
- Disabled link
1226
- </MenuItem>
1227
- </MenuList>
1228
- </MenuContent>
1229
- <MenuFooter>
1230
- <Button variant="link" isInline>
1231
- Action
1232
- </Button>
1233
- </MenuFooter>
1234
- </Menu>
1235
- );
1236
- }
1237
- }
115
+ ```ts file="MenuWithFooter.tsx"
1238
116
  ```
1239
117
 
1240
118
  ### With view more
1241
119
 
1242
- ```js
1243
- import React from 'react';
1244
- import { Menu, MenuList, MenuItem, MenuContent, MenuFooter, Spinner } from '@patternfly/react-core';
1245
-
1246
- class ViewMoreMenu extends React.Component {
1247
- constructor(props) {
1248
- super(props);
1249
- this.state = {
1250
- activeItem: 0,
1251
- isLoading: false,
1252
- numOptions: 3
1253
- };
1254
- this.activeItemRef = React.createRef();
1255
- this.viewMoreRef = React.createRef();
1256
-
1257
- this.menuOptions = [
1258
- <MenuItem key={0} itemId={'view-more-0'}>
1259
- Action
1260
- </MenuItem>,
1261
- <MenuItem
1262
- key={1}
1263
- itemId={'view-more-1'}
1264
- to="#default-link2"
1265
- // just for demo so that navigation is not triggered
1266
- onClick={event => event.preventDefault()}
1267
- >
1268
- Link
1269
- </MenuItem>,
1270
- <MenuItem key={2} itemId="view-more-2" isDisabled>
1271
- Disabled action
1272
- </MenuItem>,
1273
- <MenuItem key={3} itemId="view-more-3" isDisabled to="#default-link4">
1274
- Disabled link
1275
- </MenuItem>,
1276
- <MenuItem key={4} itemId="view-more-4" ref={this.activeItemRef}>
1277
- Action 2
1278
- </MenuItem>,
1279
- <MenuItem key={5} itemId="view-more-5">
1280
- Action 3
1281
- </MenuItem>,
1282
- <MenuItem key={6} itemId="view-more-6">
1283
- Action 4
1284
- </MenuItem>,
1285
- <MenuItem key={7} itemId="view-more-7">
1286
- Action 5
1287
- </MenuItem>,
1288
- <MenuItem key={8} itemId="view-more-8">
1289
- Final option
1290
- </MenuItem>
1291
- ];
1292
-
1293
- this.onSelect = (event, itemId) => {
1294
- console.log(`clicked ${itemId}`);
1295
- this.setState({
1296
- activeItem: itemId
1297
- });
1298
- };
1299
-
1300
- this.simulateNetworkCall = networkCallback => {
1301
- setTimeout(networkCallback, 2000);
1302
- };
1303
-
1304
- this.getNextValidItem = (startingIndex, maxLength) => {
1305
- let validItem;
1306
- for (let i = startingIndex; i < maxLength; i++) {
1307
- if (this.menuOptions[i].props.isDisabled) {
1308
- continue;
1309
- } else {
1310
- validItem = this.menuOptions[i];
1311
- break;
1312
- }
1313
- }
1314
- return validItem;
1315
- };
1316
-
1317
- this.loadMoreOptions = stateCallback => {
1318
- const newLength =
1319
- this.state.numOptions + 3 <= this.menuOptions.length ? this.state.numOptions + 3 : this.menuOptions.length;
1320
- const prevPosition = this.state.numOptions;
1321
- const nextValidItem = this.getNextValidItem(prevPosition, newLength);
1322
-
1323
- this.setState({ numOptions: newLength, isLoading: false, activeItem: nextValidItem.props.itemId }, stateCallback);
1324
- };
1325
-
1326
- this.onViewMoreClick = event => {
1327
- this.setState({ isLoading: true });
1328
- this.simulateNetworkCall(() => {
1329
- this.loadMoreOptions(() => {
1330
- this.activeItemRef.current.focus();
1331
- });
1332
- });
1333
- };
1334
-
1335
- this.onViewMoreKeyDown = event => {
1336
- if (!(event.key === ' ' || event.key === 'Enter')) {
1337
- return;
1338
- }
1339
- event.stopPropagation();
1340
- event.preventDefault();
1341
-
1342
- this.setState({ isLoading: true });
1343
- this.simulateNetworkCall(() => {
1344
- this.loadMoreOptions(() => {
1345
- if (this.viewMoreRef.current) {
1346
- this.viewMoreRef.current.tabIndex = -1;
1347
- }
1348
- const firstMenuItem = this.activeItemRef.current.closest('ul').firstChild;
1349
- firstMenuItem.querySelector('button, a').tabIndex = -1;
1350
- this.activeItemRef.current.tabIndex = 0;
1351
- this.activeItemRef.current.focus();
1352
- });
1353
- });
1354
- };
1355
- }
1356
-
1357
- render() {
1358
- const { activeItem, isLoading, numOptions } = this.state;
1359
- return (
1360
- <Menu activeItemId={activeItem} onSelect={this.onSelect}>
1361
- <MenuContent>
1362
- <MenuList>
1363
- {this.menuOptions.slice(0, numOptions).map(option => {
1364
- const props = option.props;
1365
-
1366
- return (
1367
- <MenuItem
1368
- key={option.key}
1369
- {...props}
1370
- ref={props.itemId === this.state.activeItem ? this.activeItemRef : null}
1371
- />
1372
- );
1373
- })}
1374
- {numOptions !== this.menuOptions.length && (
1375
- <MenuItem
1376
- {...(!isLoading && { isLoadButton: true })}
1377
- {...(isLoading && { isLoading: true })}
1378
- onKeyDown={this.onViewMoreKeyDown}
1379
- onClick={this.onViewMoreClick}
1380
- itemId="loader"
1381
- ref={this.viewMoreRef}
1382
- >
1383
- {isLoading ? <Spinner size="lg" /> : 'View more'}
1384
- </MenuItem>
1385
- )}
1386
- </MenuList>
1387
- </MenuContent>
1388
- </Menu>
1389
- );
1390
- }
1391
- }
120
+ ```ts file="MenuWithViewMore.tsx"
1392
121
  ```