@patternfly/react-core 4.214.3 → 4.215.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +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/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/Slider/Slider.d.ts +4 -0
- package/dist/esm/components/Slider/Slider.d.ts.map +1 -1
- package/dist/esm/components/Slider/Slider.js +2 -2
- package/dist/esm/components/Slider/Slider.js.map +1 -1
- package/dist/esm/components/TextArea/TextArea.d.ts +1 -1
- package/dist/esm/components/TextArea/TextArea.d.ts.map +1 -1
- package/dist/esm/components/TextArea/TextArea.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/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/Slider/Slider.d.ts +4 -0
- package/dist/js/components/Slider/Slider.d.ts.map +1 -1
- package/dist/js/components/Slider/Slider.js +2 -2
- package/dist/js/components/Slider/Slider.js.map +1 -1
- package/dist/js/components/TextArea/TextArea.d.ts +1 -1
- package/dist/js/components/TextArea/TextArea.d.ts.map +1 -1
- package/dist/js/components/TextArea/TextArea.js.map +1 -1
- package/dist/umd/react-core.js +19 -11
- 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/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/Slider/Slider.tsx +8 -0
- package/src/components/Slider/_tests_/Slider.test.tsx +27 -1
- package/src/components/TextArea/TextArea.tsx +1 -1
- package/src/demos/ComposableMenu/ComposableMenu.md +5 -0
- package/src/demos/ComposableMenu/examples/ComposableSimpleCheckboxSelect.tsx +98 -0
|
@@ -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
|
+
};
|
|
@@ -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
|
+
};
|
|
@@ -61,6 +61,10 @@ export interface SliderProps extends Omit<React.HTMLProps<HTMLDivElement>, 'onCh
|
|
|
61
61
|
leftActions?: React.ReactNode;
|
|
62
62
|
/** Actions placed to the right of the slider */
|
|
63
63
|
rightActions?: React.ReactNode;
|
|
64
|
+
/** One or more id's to use for the slider thumb description */
|
|
65
|
+
'aria-describedby'?: string;
|
|
66
|
+
/** One or more id's to use for the slider thumb label */
|
|
67
|
+
'aria-labelledby'?: string;
|
|
64
68
|
}
|
|
65
69
|
|
|
66
70
|
const getPercentage = (current: number, max: number) => (100 * current) / max;
|
|
@@ -86,6 +90,8 @@ export const Slider: React.FunctionComponent<SliderProps> = ({
|
|
|
86
90
|
max = 100,
|
|
87
91
|
showTicks = false,
|
|
88
92
|
showBoundaries = true,
|
|
93
|
+
'aria-describedby': ariaDescribedby,
|
|
94
|
+
'aria-labelledby': ariaLabelledby,
|
|
89
95
|
...props
|
|
90
96
|
}: SliderProps) => {
|
|
91
97
|
const sliderRailRef = React.useRef<HTMLDivElement>();
|
|
@@ -362,6 +368,8 @@ export const Slider: React.FunctionComponent<SliderProps> = ({
|
|
|
362
368
|
aria-valuetext={findAriaTextValue()}
|
|
363
369
|
aria-label={thumbAriaLabel}
|
|
364
370
|
aria-disabled={isDisabled}
|
|
371
|
+
aria-describedby={ariaDescribedby}
|
|
372
|
+
aria-labelledby={ariaLabelledby}
|
|
365
373
|
onMouseDown={!isDisabled ? handleMouseDown : null}
|
|
366
374
|
onTouchStart={!isDisabled ? handleTouchStart : null}
|
|
367
375
|
onKeyDown={!isDisabled ? handleThumbKeys : null}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { render } from '@testing-library/react';
|
|
2
|
+
import { render, screen } from '@testing-library/react';
|
|
3
3
|
import { Slider } from '../Slider';
|
|
4
4
|
import { Button } from '../../Button';
|
|
5
5
|
|
|
@@ -79,3 +79,29 @@ describe('slider', () => {
|
|
|
79
79
|
expect(asFragment()).toMatchSnapshot();
|
|
80
80
|
});
|
|
81
81
|
});
|
|
82
|
+
|
|
83
|
+
test('renders slider with aria-labelledby', () => {
|
|
84
|
+
render(
|
|
85
|
+
<>
|
|
86
|
+
<p id="test">label text</p>
|
|
87
|
+
<Slider value={50} aria-labelledby="test" />
|
|
88
|
+
</>
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
const slider = screen.getByRole('slider', { name: 'label text' });
|
|
92
|
+
|
|
93
|
+
expect(slider).toBeVisible();
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test('renders slider with aria-describedby', () => {
|
|
97
|
+
render(
|
|
98
|
+
<>
|
|
99
|
+
<p id="test">descriptive text about the slider</p>
|
|
100
|
+
<Slider value={50} aria-describedby="test" />
|
|
101
|
+
</>
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
const slider = screen.getByRole('slider', { description: 'descriptive text about the slider' });
|
|
105
|
+
|
|
106
|
+
expect(slider).toBeVisible();
|
|
107
|
+
});
|
|
@@ -125,7 +125,7 @@ export class TextAreaBase extends React.Component<TextAreaProps> {
|
|
|
125
125
|
}
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
-
export const TextArea = React.forwardRef((props
|
|
128
|
+
export const TextArea = React.forwardRef<HTMLTextAreaElement, TextAreaProps>((props, ref) => (
|
|
129
129
|
<TextAreaBase {...props} innerRef={ref as React.MutableRefObject<any>} />
|
|
130
130
|
));
|
|
131
131
|
TextArea.displayName = 'TextArea';
|
|
@@ -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
|
+
};
|