@patternfly/react-core 4.214.5 → 4.216.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/CHANGELOG.md +33 -0
- package/dist/esm/components/Checkbox/Checkbox.d.ts +2 -0
- package/dist/esm/components/Checkbox/Checkbox.d.ts.map +1 -1
- package/dist/esm/components/Checkbox/Checkbox.js +4 -3
- package/dist/esm/components/Checkbox/Checkbox.js.map +1 -1
- package/dist/esm/components/HelperText/HelperTextItem.d.ts +4 -0
- package/dist/esm/components/HelperText/HelperTextItem.d.ts.map +1 -1
- package/dist/esm/components/HelperText/HelperTextItem.js +7 -2
- package/dist/esm/components/HelperText/HelperTextItem.js.map +1 -1
- package/dist/esm/components/Menu/MenuItem.d.ts +3 -1
- package/dist/esm/components/Menu/MenuItem.d.ts.map +1 -1
- package/dist/esm/components/Menu/MenuItem.js +15 -6
- package/dist/esm/components/Menu/MenuItem.js.map +1 -1
- package/dist/esm/components/Select/Select.js +2 -2
- package/dist/esm/components/Select/Select.js.map +1 -1
- package/dist/js/components/Checkbox/Checkbox.d.ts +2 -0
- package/dist/js/components/Checkbox/Checkbox.d.ts.map +1 -1
- package/dist/js/components/Checkbox/Checkbox.js +4 -3
- package/dist/js/components/Checkbox/Checkbox.js.map +1 -1
- package/dist/js/components/HelperText/HelperTextItem.d.ts +4 -0
- package/dist/js/components/HelperText/HelperTextItem.d.ts.map +1 -1
- package/dist/js/components/HelperText/HelperTextItem.js +7 -2
- package/dist/js/components/HelperText/HelperTextItem.js.map +1 -1
- package/dist/js/components/Menu/MenuItem.d.ts +3 -1
- package/dist/js/components/Menu/MenuItem.d.ts.map +1 -1
- package/dist/js/components/Menu/MenuItem.js +15 -6
- package/dist/js/components/Menu/MenuItem.js.map +1 -1
- package/dist/js/components/Select/Select.js +2 -2
- package/dist/js/components/Select/Select.js.map +1 -1
- package/dist/umd/react-core.js +26 -13
- package/dist/umd/react-core.min.js +2 -2
- package/package.json +5 -5
- package/src/components/Checkbox/Checkbox.tsx +7 -3
- package/src/components/Checkbox/__tests__/Checkbox.test.tsx +9 -0
- package/src/components/Dropdown/examples/Dropdown.md +25 -1530
- package/src/components/Dropdown/examples/DropdownAlignmentOnBreakpoints.tsx +64 -0
- package/src/components/Dropdown/examples/DropdownAppendBodyVsParent.tsx +83 -0
- package/src/components/Dropdown/examples/DropdownBadge.tsx +46 -0
- package/src/components/Dropdown/examples/DropdownBasic.tsx +57 -0
- package/src/components/Dropdown/examples/DropdownBasicPanel.tsx +34 -0
- package/src/components/Dropdown/examples/DropdownDescriptions.tsx +55 -0
- package/src/components/Dropdown/examples/DropdownDirectionUp.tsx +58 -0
- package/src/components/Dropdown/examples/DropdownDisabled.tsx +57 -0
- package/src/components/Dropdown/examples/DropdownGroups.tsx +55 -0
- package/src/components/Dropdown/examples/DropdownIconOnly.tsx +59 -0
- package/src/components/Dropdown/examples/DropdownImageAndText.tsx +64 -0
- package/src/components/Dropdown/examples/DropdownInitialSelection.tsx +57 -0
- package/src/components/Dropdown/examples/DropdownKebab.tsx +54 -0
- package/src/components/Dropdown/examples/DropdownPlainTextToggle.tsx +59 -0
- package/src/components/Dropdown/examples/DropdownPositionRight.tsx +58 -0
- package/src/components/Dropdown/examples/DropdownPrimaryToggle.tsx +57 -0
- package/src/components/Dropdown/examples/DropdownRouterLink.tsx +36 -0
- package/src/components/Dropdown/examples/DropdownSecondaryToggle.tsx +57 -0
- package/src/components/Dropdown/examples/DropdownSplitButton.tsx +67 -0
- package/src/components/Dropdown/examples/DropdownSplitButtonAction.tsx +105 -0
- package/src/components/Dropdown/examples/DropdownSplitButtonActionPrimary.tsx +107 -0
- package/src/components/Dropdown/examples/DropdownSplitButtonDisabled.tsx +73 -0
- package/src/components/Dropdown/examples/DropdownSplitButtonIndeterminate.tsx +78 -0
- package/src/components/Dropdown/examples/DropdownSplitButtonText.tsx +69 -0
- package/src/components/HelperText/HelperTextItem.tsx +10 -1
- package/src/components/HelperText/__tests__/__snapshots__/HelperText.test.tsx.snap +5 -0
- package/src/components/Menu/MenuItem.tsx +69 -43
- package/src/components/Menu/__tests__/Menu.test.tsx +23 -0
- package/src/components/Menu/examples/Menu.md +5 -0
- package/src/components/Menu/examples/MenuWithCheckbox.tsx +34 -0
- package/src/components/Select/Select.tsx +2 -2
- package/src/demos/ComposableMenu/ComposableMenu.md +5 -0
- package/src/demos/ComposableMenu/examples/ComposableSimpleCheckboxSelect.tsx +98 -0
- package/src/demos/examples/DashboardHeader.js +50 -50
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Dropdown,
|
|
4
|
+
DropdownToggle,
|
|
5
|
+
DropdownToggleCheckbox,
|
|
6
|
+
DropdownItem,
|
|
7
|
+
DropdownSeparator
|
|
8
|
+
} from '@patternfly/react-core';
|
|
9
|
+
|
|
10
|
+
export const DropdownSplitButtonIndeterminate: React.FunctionComponent = () => {
|
|
11
|
+
const [isOpen, setIsOpen] = React.useState(false);
|
|
12
|
+
const [isChecked, setIsChecked] = React.useState(null);
|
|
13
|
+
|
|
14
|
+
const onToggle = (isOpen: boolean) => {
|
|
15
|
+
setIsOpen(isOpen);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const onFocus = () => {
|
|
19
|
+
const element = document.getElementById('toggle-split-button-indeterminate');
|
|
20
|
+
element.focus();
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const onSelect = () => {
|
|
24
|
+
setIsOpen(false);
|
|
25
|
+
onFocus();
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const onChange = (isChecked: boolean) => {
|
|
29
|
+
setIsChecked(isChecked);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const dropdownItems = [
|
|
33
|
+
<DropdownItem key="link">Link</DropdownItem>,
|
|
34
|
+
<DropdownItem key="action" component="button">
|
|
35
|
+
Action
|
|
36
|
+
</DropdownItem>,
|
|
37
|
+
<DropdownItem key="disabled link" isDisabled href="www.google.com">
|
|
38
|
+
Disabled link
|
|
39
|
+
</DropdownItem>,
|
|
40
|
+
<DropdownItem
|
|
41
|
+
key="disabled action"
|
|
42
|
+
isAriaDisabled
|
|
43
|
+
component="button"
|
|
44
|
+
tooltip="Tooltip for disabled item"
|
|
45
|
+
tooltipProps={{ position: 'top' }}
|
|
46
|
+
>
|
|
47
|
+
Disabled action
|
|
48
|
+
</DropdownItem>,
|
|
49
|
+
<DropdownSeparator key="separator" />,
|
|
50
|
+
<DropdownItem key="separated link">Separated link</DropdownItem>,
|
|
51
|
+
<DropdownItem key="separated action" component="button">
|
|
52
|
+
Separated action
|
|
53
|
+
</DropdownItem>
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<Dropdown
|
|
58
|
+
onSelect={onSelect}
|
|
59
|
+
toggle={
|
|
60
|
+
<DropdownToggle
|
|
61
|
+
splitButtonItems={[
|
|
62
|
+
<DropdownToggleCheckbox
|
|
63
|
+
id="split-button-indeterminate-checkbox"
|
|
64
|
+
key="split-checkbox"
|
|
65
|
+
aria-label="Select all"
|
|
66
|
+
onChange={checked => onChange(checked)}
|
|
67
|
+
isChecked={isChecked}
|
|
68
|
+
/>
|
|
69
|
+
]}
|
|
70
|
+
onToggle={onToggle}
|
|
71
|
+
id="toggle-split-button-indeterminate"
|
|
72
|
+
/>
|
|
73
|
+
}
|
|
74
|
+
isOpen={isOpen}
|
|
75
|
+
dropdownItems={dropdownItems}
|
|
76
|
+
/>
|
|
77
|
+
);
|
|
78
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Dropdown,
|
|
4
|
+
DropdownToggle,
|
|
5
|
+
DropdownToggleCheckbox,
|
|
6
|
+
DropdownItem,
|
|
7
|
+
DropdownSeparator
|
|
8
|
+
} from '@patternfly/react-core';
|
|
9
|
+
|
|
10
|
+
export const DropdownSplitButtonText: React.FunctionComponent = () => {
|
|
11
|
+
const [isOpen, setIsOpen] = React.useState(false);
|
|
12
|
+
|
|
13
|
+
const onToggle = (isOpen: boolean) => {
|
|
14
|
+
setIsOpen(isOpen);
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const onFocus = () => {
|
|
18
|
+
const element = document.getElementById('toggle-split-button-text');
|
|
19
|
+
element.focus();
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const onSelect = () => {
|
|
23
|
+
setIsOpen(false);
|
|
24
|
+
onFocus();
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const dropdownItems = [
|
|
28
|
+
<DropdownItem key="link">Link</DropdownItem>,
|
|
29
|
+
<DropdownItem key="action" component="button">
|
|
30
|
+
Action
|
|
31
|
+
</DropdownItem>,
|
|
32
|
+
<DropdownItem key="disabled link" isDisabled href="www.google.com">
|
|
33
|
+
Disabled link
|
|
34
|
+
</DropdownItem>,
|
|
35
|
+
<DropdownItem
|
|
36
|
+
key="disabled action"
|
|
37
|
+
isAriaDisabled
|
|
38
|
+
component="button"
|
|
39
|
+
tooltip="Tooltip for disabled item"
|
|
40
|
+
tooltipProps={{ position: 'top' }}
|
|
41
|
+
>
|
|
42
|
+
Disabled action
|
|
43
|
+
</DropdownItem>,
|
|
44
|
+
<DropdownSeparator key="separator" />,
|
|
45
|
+
<DropdownItem key="separated link">Separated link</DropdownItem>,
|
|
46
|
+
<DropdownItem key="separated action" component="button">
|
|
47
|
+
Separated action
|
|
48
|
+
</DropdownItem>
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<Dropdown
|
|
53
|
+
onSelect={onSelect}
|
|
54
|
+
toggle={
|
|
55
|
+
<DropdownToggle
|
|
56
|
+
splitButtonItems={[
|
|
57
|
+
<DropdownToggleCheckbox id="split-button-text-checkbox" key="split-checkbox" aria-label="Select all">
|
|
58
|
+
10 selected
|
|
59
|
+
</DropdownToggleCheckbox>
|
|
60
|
+
]}
|
|
61
|
+
onToggle={onToggle}
|
|
62
|
+
id="toggle-split-button-text"
|
|
63
|
+
/>
|
|
64
|
+
}
|
|
65
|
+
isOpen={isOpen}
|
|
66
|
+
dropdownItems={dropdownItems}
|
|
67
|
+
/>
|
|
68
|
+
);
|
|
69
|
+
};
|
|
@@ -29,6 +29,10 @@ export interface HelperTextItemProps extends React.HTMLProps<HTMLDivElement | HT
|
|
|
29
29
|
* assistive technologies.
|
|
30
30
|
*/
|
|
31
31
|
id?: string;
|
|
32
|
+
/** Text that is only accessible to screen readers in order to announce the status of a helper text item.
|
|
33
|
+
* This prop can only be used when the isDynamic prop is also passed in.
|
|
34
|
+
*/
|
|
35
|
+
screenReaderText?: string;
|
|
32
36
|
}
|
|
33
37
|
|
|
34
38
|
const variantStyle = {
|
|
@@ -48,6 +52,7 @@ export const HelperTextItem: React.FunctionComponent<HelperTextItemProps> = ({
|
|
|
48
52
|
isDynamic = false,
|
|
49
53
|
hasIcon = isDynamic,
|
|
50
54
|
id,
|
|
55
|
+
screenReaderText = `${variant} status`,
|
|
51
56
|
...props
|
|
52
57
|
}: HelperTextItemProps) => {
|
|
53
58
|
const Component = component as any;
|
|
@@ -70,7 +75,11 @@ export const HelperTextItem: React.FunctionComponent<HelperTextItemProps> = ({
|
|
|
70
75
|
{variant === 'error' && <ExclamationCircleIcon />}
|
|
71
76
|
</span>
|
|
72
77
|
)}
|
|
73
|
-
|
|
78
|
+
|
|
79
|
+
<span className={css(styles.helperTextItemText)}>
|
|
80
|
+
{children}
|
|
81
|
+
{isDynamic && <span className="pf-u-screen-reader">: {screenReaderText};</span>}
|
|
82
|
+
</span>
|
|
74
83
|
</Component>
|
|
75
84
|
);
|
|
76
85
|
};
|
|
@@ -8,10 +8,12 @@ import ExternalLinkAltIcon from '@patternfly/react-icons/dist/esm/icons/external
|
|
|
8
8
|
import AngleRightIcon from '@patternfly/react-icons/dist/esm/icons/angle-right-icon';
|
|
9
9
|
import AngleLeftIcon from '@patternfly/react-icons/dist/esm/icons/angle-left-icon';
|
|
10
10
|
import CheckIcon from '@patternfly/react-icons/dist/esm/icons/check-icon';
|
|
11
|
+
import { Checkbox } from '../Checkbox';
|
|
11
12
|
import { MenuContext, MenuItemContext } from './MenuContext';
|
|
12
13
|
import { MenuItemAction } from './MenuItemAction';
|
|
13
14
|
import { canUseDOM } from '../../helpers/util';
|
|
14
15
|
import { useIsomorphicLayoutEffect } from '../../helpers/useIsomorphicLayout';
|
|
16
|
+
import { GenerateId } from '../../helpers/GenerateId/GenerateId';
|
|
15
17
|
|
|
16
18
|
export interface MenuItemProps extends Omit<React.HTMLProps<HTMLLIElement>, 'onClick'> {
|
|
17
19
|
/** Content rendered inside the menu list item. */
|
|
@@ -22,6 +24,8 @@ export interface MenuItemProps extends Omit<React.HTMLProps<HTMLLIElement>, 'onC
|
|
|
22
24
|
itemId?: any;
|
|
23
25
|
/** Target navigation link */
|
|
24
26
|
to?: string;
|
|
27
|
+
/** @beta Flag indicating the item has a checkbox */
|
|
28
|
+
hasCheck?: boolean;
|
|
25
29
|
/** Flag indicating whether the item is active */
|
|
26
30
|
isActive?: boolean;
|
|
27
31
|
/** Flag indicating if the item is favorited */
|
|
@@ -71,6 +75,7 @@ const MenuItemBase: React.FunctionComponent<MenuItemProps> = ({
|
|
|
71
75
|
className,
|
|
72
76
|
itemId = null,
|
|
73
77
|
to,
|
|
78
|
+
hasCheck = false,
|
|
74
79
|
isActive = null,
|
|
75
80
|
isFavorited = null,
|
|
76
81
|
isLoadButton = false,
|
|
@@ -105,7 +110,10 @@ const MenuItemBase: React.FunctionComponent<MenuItemProps> = ({
|
|
|
105
110
|
setFlyoutRef,
|
|
106
111
|
disableHover
|
|
107
112
|
} = React.useContext(MenuContext);
|
|
108
|
-
|
|
113
|
+
let Component = (to ? 'a' : component) as any;
|
|
114
|
+
if (hasCheck && !to) {
|
|
115
|
+
Component = 'label' as any;
|
|
116
|
+
}
|
|
109
117
|
const [flyoutTarget, setFlyoutTarget] = React.useState(null);
|
|
110
118
|
const flyoutContext = React.useContext(FlyoutContext);
|
|
111
119
|
const [flyoutXDirection, setFlyoutXDirection] = React.useState(flyoutContext.direction);
|
|
@@ -287,52 +295,70 @@ const MenuItemBase: React.FunctionComponent<MenuItemProps> = ({
|
|
|
287
295
|
onMouseOver={onMouseOver}
|
|
288
296
|
{...(flyoutMenu && { onKeyDown: handleFlyout })}
|
|
289
297
|
ref={ref}
|
|
290
|
-
role=
|
|
298
|
+
role={!hasCheck ? 'none' : 'menuitem'}
|
|
291
299
|
{...props}
|
|
292
300
|
>
|
|
293
|
-
<
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
301
|
+
<GenerateId>
|
|
302
|
+
{randomId => (
|
|
303
|
+
<Component
|
|
304
|
+
tabIndex={-1}
|
|
305
|
+
className={css(styles.menuItem, getIsSelected() && !hasCheck && styles.modifiers.selected, className)}
|
|
306
|
+
aria-current={getAriaCurrent()}
|
|
307
|
+
{...(!hasCheck && { disabled: isDisabled })}
|
|
308
|
+
{...(!hasCheck && { role: 'menuitem' })}
|
|
309
|
+
ref={innerRef}
|
|
310
|
+
{...(!hasCheck && {
|
|
311
|
+
onClick: (event: any) => {
|
|
312
|
+
onItemSelect(event, onSelect);
|
|
313
|
+
_drill && _drill();
|
|
314
|
+
}
|
|
315
|
+
})}
|
|
316
|
+
{...(hasCheck && { htmlFor: randomId })}
|
|
317
|
+
{...additionalProps}
|
|
318
|
+
>
|
|
319
|
+
<span className={css(styles.menuItemMain)}>
|
|
320
|
+
{direction === 'up' && (
|
|
321
|
+
<span className={css(styles.menuItemToggleIcon)}>
|
|
322
|
+
<AngleLeftIcon aria-hidden />
|
|
323
|
+
</span>
|
|
324
|
+
)}
|
|
325
|
+
{icon && <span className={css(styles.menuItemIcon)}>{icon}</span>}
|
|
326
|
+
{hasCheck && (
|
|
327
|
+
<span className={css('pf-c-menu__item-check')}>
|
|
328
|
+
<Checkbox
|
|
329
|
+
id={randomId}
|
|
330
|
+
component="span"
|
|
331
|
+
isChecked={isSelected || false}
|
|
332
|
+
onChange={event => onItemSelect(event, onSelect)}
|
|
333
|
+
isDisabled={isDisabled}
|
|
334
|
+
/>
|
|
335
|
+
</span>
|
|
336
|
+
)}
|
|
337
|
+
<span className={css(styles.menuItemText)}>{children}</span>
|
|
338
|
+
{isExternalLink && (
|
|
339
|
+
<span className={css(styles.menuItemExternalIcon)}>
|
|
340
|
+
<ExternalLinkAltIcon aria-hidden />
|
|
341
|
+
</span>
|
|
342
|
+
)}
|
|
343
|
+
{(flyoutMenu || direction === 'down') && (
|
|
344
|
+
<span className={css(styles.menuItemToggleIcon)}>
|
|
345
|
+
<AngleRightIcon aria-hidden />
|
|
346
|
+
</span>
|
|
347
|
+
)}
|
|
348
|
+
{getIsSelected() && (
|
|
349
|
+
<span className={css(styles.menuItemSelectIcon)}>
|
|
350
|
+
<CheckIcon aria-hidden />
|
|
351
|
+
</span>
|
|
352
|
+
)}
|
|
327
353
|
</span>
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
</
|
|
354
|
+
{description && direction !== 'up' && (
|
|
355
|
+
<span className={css(styles.menuItemDescription)}>
|
|
356
|
+
<span>{description}</span>
|
|
357
|
+
</span>
|
|
358
|
+
)}
|
|
359
|
+
</Component>
|
|
334
360
|
)}
|
|
335
|
-
</
|
|
361
|
+
</GenerateId>
|
|
336
362
|
{flyoutVisible && (
|
|
337
363
|
<MenuContext.Provider value={{ disableHover }}>
|
|
338
364
|
<FlyoutContext.Provider value={{ direction: flyoutXDirection }}>{flyoutMenu}</FlyoutContext.Provider>
|
|
@@ -2,8 +2,12 @@ import React from 'react';
|
|
|
2
2
|
|
|
3
3
|
import { render, screen } from '@testing-library/react';
|
|
4
4
|
import '@testing-library/jest-dom';
|
|
5
|
+
import userEvent from '@testing-library/user-event';
|
|
5
6
|
|
|
6
7
|
import { Menu } from '../Menu';
|
|
8
|
+
import { MenuItem } from '../MenuItem';
|
|
9
|
+
import { MenuList } from '../MenuList';
|
|
10
|
+
import { MenuContent } from '../MenuContent';
|
|
7
11
|
|
|
8
12
|
describe('Menu', () => {
|
|
9
13
|
test('should render Menu successfully', () => {
|
|
@@ -47,4 +51,23 @@ describe('Menu', () => {
|
|
|
47
51
|
expect(screen.getByText('content')).toHaveClass('pf-m-nav');
|
|
48
52
|
});
|
|
49
53
|
});
|
|
54
|
+
|
|
55
|
+
describe('with hasCheck', () => {
|
|
56
|
+
test('should render Menu with checkbox items', () => {
|
|
57
|
+
const { asFragment } = render(
|
|
58
|
+
<Menu>
|
|
59
|
+
<MenuContent>
|
|
60
|
+
<MenuList>
|
|
61
|
+
<MenuItem hasCheck itemId={0}>
|
|
62
|
+
Checkbox 1
|
|
63
|
+
</MenuItem>
|
|
64
|
+
</MenuList>
|
|
65
|
+
</MenuContent>
|
|
66
|
+
</Menu>
|
|
67
|
+
);
|
|
68
|
+
const checkbox1 = screen.getAllByRole('checkbox')[0];
|
|
69
|
+
expect(checkbox1).not.toBeChecked();
|
|
70
|
+
expect(screen.getByText("Checkbox 1")).toBeInTheDocument();
|
|
71
|
+
});
|
|
72
|
+
});
|
|
50
73
|
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Menu, MenuContent, MenuList, MenuItem } from '@patternfly/react-core';
|
|
3
|
+
|
|
4
|
+
export const MenuCheckboxList: React.FunctionComponent = () => {
|
|
5
|
+
const [selectedItems, setSelectedItems] = React.useState<number[]>([]);
|
|
6
|
+
|
|
7
|
+
/* eslint no-unused-vars: ["error", {"args": "after-used"}] */
|
|
8
|
+
const onSelect = (event: React.MouseEvent<Element, MouseEvent>, itemId: number | string) => {
|
|
9
|
+
const item = itemId as number;
|
|
10
|
+
if (selectedItems.includes(item)) {
|
|
11
|
+
setSelectedItems(selectedItems.filter(id => id !== item));
|
|
12
|
+
} else {
|
|
13
|
+
setSelectedItems([...selectedItems, item]);
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<Menu onSelect={onSelect} selected={selectedItems}>
|
|
19
|
+
<MenuContent>
|
|
20
|
+
<MenuList>
|
|
21
|
+
<MenuItem hasCheck itemId={0} isSelected={selectedItems.includes(0)}>
|
|
22
|
+
Checkbox 1
|
|
23
|
+
</MenuItem>
|
|
24
|
+
<MenuItem hasCheck itemId={1} isSelected={selectedItems.includes(1)}>
|
|
25
|
+
Checkbox 2
|
|
26
|
+
</MenuItem>
|
|
27
|
+
<MenuItem hasCheck itemId={2} isDisabled>
|
|
28
|
+
Checkbox 3
|
|
29
|
+
</MenuItem>
|
|
30
|
+
</MenuList>
|
|
31
|
+
</MenuContent>
|
|
32
|
+
</Menu>
|
|
33
|
+
);
|
|
34
|
+
};
|
|
@@ -679,13 +679,13 @@ export class Select extends React.Component<SelectProps & OUIAProps, SelectState
|
|
|
679
679
|
};
|
|
680
680
|
|
|
681
681
|
handleTypeaheadKeys = (position: string, shiftKey: boolean = false) => {
|
|
682
|
-
const { isOpen, onFavorite } = this.props;
|
|
682
|
+
const { isOpen, onFavorite, isCreatable } = this.props;
|
|
683
683
|
const { typeaheadCurrIndex, tabbedIntoFavoritesMenu } = this.state;
|
|
684
684
|
const typeaheadActiveChild = this.getTypeaheadActiveChild(typeaheadCurrIndex);
|
|
685
685
|
if (isOpen) {
|
|
686
686
|
if (position === 'enter') {
|
|
687
687
|
if (
|
|
688
|
-
typeaheadCurrIndex !== -1 && // do not allow selection without moving to an initial option
|
|
688
|
+
(typeaheadCurrIndex !== -1 || (isCreatable && this.refCollection.length === 1)) && // do not allow selection without moving to an initial option unless it is a single create option
|
|
689
689
|
(typeaheadActiveChild || (this.refCollection[0] && this.refCollection[0][0]))
|
|
690
690
|
) {
|
|
691
691
|
if (typeaheadActiveChild) {
|
|
@@ -40,6 +40,11 @@ Composable menus currently require consumer keyboard handling and use of our und
|
|
|
40
40
|
```ts file="./examples/ComposableSimpleSelect.tsx"
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
+
### Composable simple checkbox select
|
|
44
|
+
|
|
45
|
+
```ts isBeta file="./examples/ComposableSimpleCheckboxSelect.tsx"
|
|
46
|
+
```
|
|
47
|
+
|
|
43
48
|
### Composable drilldown menu
|
|
44
49
|
|
|
45
50
|
```ts isBeta file="./examples/ComposableDrilldownMenu.tsx"
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { MenuToggle, Menu, MenuContent, MenuList, MenuItem, Popper, Badge } from '@patternfly/react-core';
|
|
3
|
+
|
|
4
|
+
export const ComposableSimpleCheckboxSelect: React.FunctionComponent = () => {
|
|
5
|
+
const [isOpen, setIsOpen] = React.useState<boolean>(false);
|
|
6
|
+
const [selectedItems, setSelectedItems] = React.useState<number[]>([]);
|
|
7
|
+
const toggleRef = React.useRef<HTMLButtonElement>();
|
|
8
|
+
const menuRef = React.useRef<HTMLDivElement>();
|
|
9
|
+
|
|
10
|
+
const handleMenuKeys = React.useCallback(
|
|
11
|
+
event => {
|
|
12
|
+
if (menuRef.current) {
|
|
13
|
+
if (isOpen && menuRef.current.contains(event.target as Node)) {
|
|
14
|
+
if (event.key === 'Escape' || event.key === 'Tab') {
|
|
15
|
+
setIsOpen(!isOpen);
|
|
16
|
+
toggleRef.current.focus();
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
[isOpen]
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
const handleClickOutside = React.useCallback(
|
|
25
|
+
event => {
|
|
26
|
+
if (isOpen && !menuRef.current.contains(event.target as Node)) {
|
|
27
|
+
setIsOpen(false);
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
[isOpen]
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
React.useEffect(() => {
|
|
34
|
+
window.addEventListener('keydown', handleMenuKeys);
|
|
35
|
+
window.addEventListener('click', handleClickOutside);
|
|
36
|
+
return () => {
|
|
37
|
+
window.removeEventListener('keydown', handleMenuKeys);
|
|
38
|
+
window.removeEventListener('click', handleClickOutside);
|
|
39
|
+
};
|
|
40
|
+
}, [handleClickOutside, handleMenuKeys]);
|
|
41
|
+
|
|
42
|
+
const onToggleClick = (ev: React.MouseEvent) => {
|
|
43
|
+
ev.stopPropagation(); // Stop handleClickOutside from handling
|
|
44
|
+
setTimeout(() => {
|
|
45
|
+
if (menuRef.current) {
|
|
46
|
+
const firstElement = menuRef.current.querySelector('li > button:not(:disabled)');
|
|
47
|
+
firstElement && (firstElement as HTMLElement).focus();
|
|
48
|
+
}
|
|
49
|
+
}, 0);
|
|
50
|
+
setIsOpen(!isOpen);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const onSelect = (ev: React.MouseEvent<Element, MouseEvent>, itemId: number | string) => {
|
|
54
|
+
const item = itemId as number;
|
|
55
|
+
if (selectedItems.includes(item)) {
|
|
56
|
+
setSelectedItems(selectedItems.filter(id => id !== item));
|
|
57
|
+
} else {
|
|
58
|
+
setSelectedItems([...selectedItems, item]);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const toggle = (
|
|
63
|
+
<MenuToggle
|
|
64
|
+
ref={toggleRef}
|
|
65
|
+
{...(selectedItems.length > 0 && { badge: <Badge isRead>{selectedItems.length}</Badge> })}
|
|
66
|
+
onClick={onToggleClick}
|
|
67
|
+
isExpanded={isOpen}
|
|
68
|
+
style={
|
|
69
|
+
{
|
|
70
|
+
width: '220px'
|
|
71
|
+
} as React.CSSProperties
|
|
72
|
+
}
|
|
73
|
+
>
|
|
74
|
+
Filter by status
|
|
75
|
+
</MenuToggle>
|
|
76
|
+
);
|
|
77
|
+
const menu = (
|
|
78
|
+
<Menu ref={menuRef} id="select-menu" onSelect={onSelect} selected={selectedItems}>
|
|
79
|
+
<MenuContent>
|
|
80
|
+
<MenuList>
|
|
81
|
+
<MenuItem hasCheck itemId={0} isSelected={selectedItems.includes(0)}>
|
|
82
|
+
Debug
|
|
83
|
+
</MenuItem>
|
|
84
|
+
<MenuItem hasCheck itemId={1} isSelected={selectedItems.includes(1)}>
|
|
85
|
+
Info
|
|
86
|
+
</MenuItem>
|
|
87
|
+
<MenuItem hasCheck itemId={2} isSelected={selectedItems.includes(2)}>
|
|
88
|
+
Warn
|
|
89
|
+
</MenuItem>
|
|
90
|
+
<MenuItem hasCheck isDisabled itemId={4} isSelected={selectedItems.includes(4)}>
|
|
91
|
+
Error
|
|
92
|
+
</MenuItem>
|
|
93
|
+
</MenuList>
|
|
94
|
+
</MenuContent>
|
|
95
|
+
</Menu>
|
|
96
|
+
);
|
|
97
|
+
return <Popper trigger={toggle} popper={menu} isVisible={isOpen} />;
|
|
98
|
+
};
|