@patternfly/react-templates 6.0.0-alpha.9 → 6.0.0-prerelease.2
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 +204 -0
- package/README.md +4 -17
- package/components/package.json +1 -1
- package/dist/dynamic/components/Dropdown/package.json +1 -0
- package/dist/dynamic/components/Select/package.json +1 -1
- package/dist/dynamic-modules.json +19 -0
- package/dist/esm/components/Dropdown/SimpleDropdown.d.ts +48 -0
- package/dist/esm/components/Dropdown/SimpleDropdown.d.ts.map +1 -0
- package/dist/esm/components/Dropdown/SimpleDropdown.js +33 -0
- package/dist/esm/components/Dropdown/SimpleDropdown.js.map +1 -0
- package/dist/esm/components/Dropdown/index.d.ts +2 -0
- package/dist/esm/components/Dropdown/index.d.ts.map +1 -0
- package/dist/esm/components/Dropdown/index.js +2 -0
- package/dist/esm/components/Dropdown/index.js.map +1 -0
- package/dist/esm/components/Select/CheckboxSelect.d.ts +7 -3
- package/dist/esm/components/Select/CheckboxSelect.d.ts.map +1 -1
- package/dist/esm/components/Select/CheckboxSelect.js +14 -6
- package/dist/esm/components/Select/CheckboxSelect.js.map +1 -1
- package/dist/esm/components/Select/MultiTypeaheadSelect.d.ts +33 -0
- package/dist/esm/components/Select/MultiTypeaheadSelect.d.ts.map +1 -0
- package/dist/esm/components/Select/MultiTypeaheadSelect.js +186 -0
- package/dist/esm/components/Select/MultiTypeaheadSelect.js.map +1 -0
- package/dist/esm/components/Select/{SelectSimple.d.ts → SimpleSelect.d.ts} +12 -7
- package/dist/esm/components/Select/SimpleSelect.d.ts.map +1 -0
- package/dist/esm/components/Select/SimpleSelect.js +39 -0
- package/dist/esm/components/Select/SimpleSelect.js.map +1 -0
- package/dist/esm/components/Select/TypeaheadSelect.d.ts +43 -0
- package/dist/esm/components/Select/TypeaheadSelect.d.ts.map +1 -0
- package/dist/esm/components/Select/TypeaheadSelect.js +219 -0
- package/dist/esm/components/Select/TypeaheadSelect.js.map +1 -0
- package/dist/esm/components/Select/index.d.ts +3 -1
- package/dist/esm/components/Select/index.d.ts.map +1 -1
- package/dist/esm/components/Select/index.js +3 -1
- package/dist/esm/components/Select/index.js.map +1 -1
- package/dist/esm/components/index.d.ts +1 -0
- package/dist/esm/components/index.d.ts.map +1 -1
- package/dist/esm/components/index.js +1 -0
- package/dist/esm/components/index.js.map +1 -1
- package/dist/js/components/Dropdown/SimpleDropdown.d.ts +48 -0
- package/dist/js/components/Dropdown/SimpleDropdown.d.ts.map +1 -0
- package/dist/js/components/Dropdown/SimpleDropdown.js +36 -0
- package/dist/js/components/Dropdown/SimpleDropdown.js.map +1 -0
- package/dist/js/components/Dropdown/index.d.ts +2 -0
- package/dist/js/components/Dropdown/index.d.ts.map +1 -0
- package/dist/js/components/Dropdown/index.js +5 -0
- package/dist/js/components/Dropdown/index.js.map +1 -0
- package/dist/js/components/Select/CheckboxSelect.d.ts +7 -3
- package/dist/js/components/Select/CheckboxSelect.d.ts.map +1 -1
- package/dist/js/components/Select/CheckboxSelect.js +14 -6
- package/dist/js/components/Select/CheckboxSelect.js.map +1 -1
- package/dist/js/components/Select/MultiTypeaheadSelect.d.ts +33 -0
- package/dist/js/components/Select/MultiTypeaheadSelect.d.ts.map +1 -0
- package/dist/js/components/Select/MultiTypeaheadSelect.js +190 -0
- package/dist/js/components/Select/MultiTypeaheadSelect.js.map +1 -0
- package/dist/js/components/Select/{SelectSimple.d.ts → SimpleSelect.d.ts} +12 -7
- package/dist/js/components/Select/SimpleSelect.d.ts.map +1 -0
- package/dist/js/components/Select/SimpleSelect.js +42 -0
- package/dist/js/components/Select/SimpleSelect.js.map +1 -0
- package/dist/js/components/Select/TypeaheadSelect.d.ts +43 -0
- package/dist/js/components/Select/TypeaheadSelect.d.ts.map +1 -0
- package/dist/js/components/Select/TypeaheadSelect.js +223 -0
- package/dist/js/components/Select/TypeaheadSelect.js.map +1 -0
- package/dist/js/components/Select/index.d.ts +3 -1
- package/dist/js/components/Select/index.d.ts.map +1 -1
- package/dist/js/components/Select/index.js +3 -1
- package/dist/js/components/Select/index.js.map +1 -1
- package/dist/js/components/index.d.ts +1 -0
- package/dist/js/components/index.d.ts.map +1 -1
- package/dist/js/components/index.js +1 -0
- package/dist/js/components/index.js.map +1 -1
- package/dist/umd/assets/output-Drxynxu2.css +23380 -0
- package/dist/umd/react-templates.min.js +1 -1
- package/package.json +10 -14
- package/rollup.config.mjs +8 -0
- package/src/components/Dropdown/SimpleDropdown.tsx +139 -0
- package/src/components/Dropdown/__tests__/SimpleDropdown.test.tsx +266 -0
- package/src/components/Dropdown/__tests__/__snapshots__/SimpleDropdown.test.tsx.snap +81 -0
- package/src/components/Dropdown/examples/DropdownTemplates.md +24 -0
- package/src/components/Dropdown/examples/SimpleDropdownExample.tsx +53 -0
- package/src/components/Dropdown/index.ts +1 -0
- package/src/components/Select/CheckboxSelect.tsx +23 -7
- package/src/components/Select/MultiTypeaheadSelect.tsx +348 -0
- package/src/components/Select/{SelectSimple.tsx → SimpleSelect.tsx} +39 -18
- package/src/components/Select/TypeaheadSelect.tsx +388 -0
- package/src/components/Select/{CheckboxSelect.test.tsx → __tests__/CheckboxSelect.test.tsx} +29 -1
- package/src/components/Select/{CheckboxSelectSnapshots.test.tsx → __tests__/CheckboxSelectSnapshots.test.tsx} +1 -1
- package/src/components/Select/__tests__/MultiTypeaheadSelect.test.tsx +414 -0
- package/src/components/Select/__tests__/SimpleSelect.test.tsx +241 -0
- package/src/components/Select/__tests__/TypeaheadSelect.test.tsx +408 -0
- package/src/components/Select/__tests__/__snapshots__/CheckboxSelectSnapshots.test.tsx.snap +83 -0
- package/src/components/Select/__tests__/__snapshots__/MultiTypeaheadSelect.test.tsx.snap +97 -0
- package/src/components/Select/__tests__/__snapshots__/SimpleSelect.test.tsx.snap +83 -0
- package/src/components/Select/__tests__/__snapshots__/TypeaheadSelect.test.tsx.snap +97 -0
- package/src/components/Select/examples/CheckboxSelectDemo.tsx +25 -8
- package/src/components/Select/examples/MultiTypeaheadSelectDemo.tsx +35 -0
- package/src/components/Select/examples/SelectTemplates.md +17 -4
- package/src/components/Select/examples/SimpleSelectDemo.tsx +36 -0
- package/src/components/Select/examples/TypeaheadSelectDemo.tsx +65 -0
- package/src/components/Select/index.ts +3 -1
- package/src/components/index.ts +1 -0
- package/dist/esm/components/Select/SelectSimple.d.ts.map +0 -1
- package/dist/esm/components/Select/SelectSimple.js +0 -31
- package/dist/esm/components/Select/SelectSimple.js.map +0 -1
- package/dist/js/components/Select/SelectSimple.d.ts.map +0 -1
- package/dist/js/components/Select/SelectSimple.js +0 -34
- package/dist/js/components/Select/SelectSimple.js.map +0 -1
- package/rollup.config.js +0 -7
- package/src/components/Select/__snapshots__/CheckboxSelectSnapshots.test.tsx.snap +0 -212
- package/src/components/Select/examples/SelectSimpleDemo.tsx +0 -26
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Dropdown,
|
|
4
|
+
DropdownItem,
|
|
5
|
+
DropdownList,
|
|
6
|
+
DropdownItemProps,
|
|
7
|
+
DropdownProps
|
|
8
|
+
} from '@patternfly/react-core/dist/esm/components/Dropdown';
|
|
9
|
+
import { MenuToggle, MenuToggleElement, MenuToggleProps } from '@patternfly/react-core/dist/esm/components/MenuToggle';
|
|
10
|
+
import { Divider } from '@patternfly/react-core/dist/esm/components/Divider';
|
|
11
|
+
import { OUIAProps } from '@patternfly/react-core/dist/esm/helpers';
|
|
12
|
+
|
|
13
|
+
export interface SimpleDropdownItem extends Omit<DropdownItemProps, 'content'> {
|
|
14
|
+
/** Content of the dropdown item. If the isDivider prop is true, this prop will be ignored. */
|
|
15
|
+
content?: React.ReactNode;
|
|
16
|
+
/** Unique identifier for the dropdown item, which is used in the dropdown onSelect callback */
|
|
17
|
+
value: string | number;
|
|
18
|
+
/** Callback for when the dropdown item is clicked. */
|
|
19
|
+
onClick?: (event?: any) => void;
|
|
20
|
+
/** URL to redirect to when the dropdown item is clicked. */
|
|
21
|
+
to?: string;
|
|
22
|
+
/** Flag indicating whether the dropdown item should render as a divider. If true, the item will be rendered without
|
|
23
|
+
* the dropdown item wrapper.
|
|
24
|
+
*/
|
|
25
|
+
isDivider?: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface SimpleDropdownProps extends Omit<DropdownProps, 'toggle'>, OUIAProps {
|
|
29
|
+
/** Initial items of the dropdown. */
|
|
30
|
+
initialItems?: SimpleDropdownItem[];
|
|
31
|
+
/** @hide Forwarded ref */
|
|
32
|
+
innerRef?: React.Ref<any>;
|
|
33
|
+
/** Flag indicating the dropdown should be disabled. */
|
|
34
|
+
isDisabled?: boolean;
|
|
35
|
+
/** Flag indicated whether the dropdown toggle should take up the full width of its parent. */
|
|
36
|
+
isToggleFullWidth?: boolean;
|
|
37
|
+
/** Callback triggered when any dropdown item is clicked. */
|
|
38
|
+
onSelect?: (event?: React.MouseEvent<Element, MouseEvent>, value?: string | number) => void;
|
|
39
|
+
/** Callback triggered when the dropdown toggle opens or closes. */
|
|
40
|
+
onToggle?: (nextIsOpen: boolean) => void;
|
|
41
|
+
/** Flag indicating the dropdown toggle should be focused after a dropdown item is clicked. */
|
|
42
|
+
shouldFocusToggleOnSelect?: boolean;
|
|
43
|
+
/** Adds an accessible name to the dropdown toggle. Required when the dropdown toggle does not
|
|
44
|
+
* have any text content.
|
|
45
|
+
*/
|
|
46
|
+
toggleAriaLabel?: string;
|
|
47
|
+
/** Content of the toggle. */
|
|
48
|
+
toggleContent: React.ReactNode;
|
|
49
|
+
/** Variant style of the dropdown toggle. */
|
|
50
|
+
toggleVariant?: 'default' | 'plain' | 'plainText';
|
|
51
|
+
/** Width of the toggle. */
|
|
52
|
+
toggleWidth?: string;
|
|
53
|
+
/** Additional props passed to the toggle. */
|
|
54
|
+
toggleProps?: MenuToggleProps;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const SimpleDropdownBase: React.FunctionComponent<SimpleDropdownProps> = ({
|
|
58
|
+
innerRef,
|
|
59
|
+
initialItems,
|
|
60
|
+
onSelect: onSelectProp,
|
|
61
|
+
onToggle: onToggleProp,
|
|
62
|
+
isDisabled,
|
|
63
|
+
toggleAriaLabel,
|
|
64
|
+
toggleContent,
|
|
65
|
+
isToggleFullWidth,
|
|
66
|
+
toggleVariant = 'default',
|
|
67
|
+
toggleWidth,
|
|
68
|
+
toggleProps,
|
|
69
|
+
shouldFocusToggleOnSelect,
|
|
70
|
+
...props
|
|
71
|
+
}: SimpleDropdownProps) => {
|
|
72
|
+
const [isOpen, setIsOpen] = React.useState(false);
|
|
73
|
+
|
|
74
|
+
const onSelect = (event: React.MouseEvent<Element, MouseEvent>, value: string | number) => {
|
|
75
|
+
onSelectProp && onSelectProp(event, value);
|
|
76
|
+
onToggleProp && onToggleProp(false);
|
|
77
|
+
setIsOpen(false);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const onToggle = () => {
|
|
81
|
+
onToggleProp && onToggleProp(!isOpen);
|
|
82
|
+
setIsOpen(!isOpen);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const dropdownToggle = (toggleRef: React.Ref<MenuToggleElement>) => (
|
|
86
|
+
<MenuToggle
|
|
87
|
+
ref={toggleRef}
|
|
88
|
+
onClick={onToggle}
|
|
89
|
+
isExpanded={isOpen}
|
|
90
|
+
isDisabled={isDisabled}
|
|
91
|
+
variant={toggleVariant}
|
|
92
|
+
aria-label={toggleAriaLabel}
|
|
93
|
+
isFullWidth={isToggleFullWidth}
|
|
94
|
+
style={
|
|
95
|
+
{
|
|
96
|
+
width: toggleWidth
|
|
97
|
+
} as React.CSSProperties
|
|
98
|
+
}
|
|
99
|
+
{...toggleProps}
|
|
100
|
+
>
|
|
101
|
+
{toggleContent}
|
|
102
|
+
</MenuToggle>
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
const dropdownSimpleItems = initialItems?.map((item) => {
|
|
106
|
+
const { content, onClick, to, value, isDivider, ...itemProps } = item;
|
|
107
|
+
|
|
108
|
+
return isDivider ? (
|
|
109
|
+
<Divider component="li" key={value} />
|
|
110
|
+
) : (
|
|
111
|
+
<DropdownItem onClick={onClick} to={to} key={value} value={value} {...itemProps}>
|
|
112
|
+
{content}
|
|
113
|
+
</DropdownItem>
|
|
114
|
+
);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
<Dropdown
|
|
119
|
+
toggle={dropdownToggle}
|
|
120
|
+
isOpen={isOpen}
|
|
121
|
+
onSelect={onSelect}
|
|
122
|
+
shouldFocusToggleOnSelect={shouldFocusToggleOnSelect}
|
|
123
|
+
onOpenChange={(isOpen) => {
|
|
124
|
+
onToggleProp && onToggleProp(isOpen);
|
|
125
|
+
setIsOpen(isOpen);
|
|
126
|
+
}}
|
|
127
|
+
ref={innerRef}
|
|
128
|
+
{...props}
|
|
129
|
+
>
|
|
130
|
+
<DropdownList>{dropdownSimpleItems}</DropdownList>
|
|
131
|
+
</Dropdown>
|
|
132
|
+
);
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
export const SimpleDropdown = React.forwardRef((props: SimpleDropdownProps, ref: React.Ref<any>) => (
|
|
136
|
+
<SimpleDropdownBase {...props} innerRef={ref} />
|
|
137
|
+
));
|
|
138
|
+
|
|
139
|
+
SimpleDropdown.displayName = 'SimpleDropdown';
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { render, screen } from '@testing-library/react';
|
|
3
|
+
import userEvent from '@testing-library/user-event';
|
|
4
|
+
import { SimpleDropdown } from '../SimpleDropdown';
|
|
5
|
+
import styles from '@patternfly/react-styles/css/components/MenuToggle/menu-toggle';
|
|
6
|
+
|
|
7
|
+
describe('Dropdown toggle', () => {
|
|
8
|
+
test('Renders dropdown toggle as not disabled when isDisabled is not true', () => {
|
|
9
|
+
render(<SimpleDropdown toggleContent="Dropdown" />);
|
|
10
|
+
|
|
11
|
+
expect(screen.getByRole('button', { name: 'Dropdown' })).not.toBeDisabled();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test('Renders dropdown toggle as disabled when isDisabled is true', () => {
|
|
15
|
+
render(<SimpleDropdown toggleContent="Dropdown" isDisabled />);
|
|
16
|
+
|
|
17
|
+
expect(screen.getByRole('button', { name: 'Dropdown' })).toBeDisabled();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test('Passes toggleVariant', () => {
|
|
21
|
+
render(<SimpleDropdown toggleContent="Dropdown" toggleVariant="plain" />);
|
|
22
|
+
|
|
23
|
+
expect(screen.getByRole('button', { name: 'Dropdown' })).toHaveClass(styles.modifiers.plain);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test('Passes toggleWidth', () => {
|
|
27
|
+
render(<SimpleDropdown toggleContent="Dropdown" toggleWidth="500px" />);
|
|
28
|
+
|
|
29
|
+
expect(screen.getByRole('button', { name: 'Dropdown' })).toHaveAttribute('style', 'width: 500px;');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('Passes additional toggleProps', () => {
|
|
33
|
+
render(<SimpleDropdown toggleContent="Dropdown" toggleProps={{ id: 'toggle' }} />);
|
|
34
|
+
|
|
35
|
+
expect(screen.getByRole('button', { name: 'Dropdown' })).toHaveAttribute('id', 'toggle');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('Passes toggleAriaLabel', () => {
|
|
39
|
+
render(<SimpleDropdown toggleContent="Dropdown" toggleAriaLabel="Aria label content" />);
|
|
40
|
+
|
|
41
|
+
expect(screen.getByRole('button')).toHaveAccessibleName('Aria label content');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('Calls onToggle with next isOpen state when dropdown toggle is clicked', async () => {
|
|
45
|
+
const onToggle = jest.fn();
|
|
46
|
+
const user = userEvent.setup();
|
|
47
|
+
render(<SimpleDropdown onToggle={onToggle} toggleContent="Dropdown" />);
|
|
48
|
+
|
|
49
|
+
const toggle = screen.getByRole('button', { name: 'Dropdown' });
|
|
50
|
+
await user.click(toggle);
|
|
51
|
+
expect(onToggle).toHaveBeenCalledWith(true);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('Does not call onToggle when dropdown toggle is not clicked', async () => {
|
|
55
|
+
const onToggle = jest.fn();
|
|
56
|
+
const items = [{ content: 'Action', value: 1 }];
|
|
57
|
+
const user = userEvent.setup();
|
|
58
|
+
render(
|
|
59
|
+
<div>
|
|
60
|
+
<button>Actual</button>
|
|
61
|
+
<SimpleDropdown initialItems={items} onToggle={onToggle} toggleContent="Dropdown" />
|
|
62
|
+
</div>
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
const btn = screen.getByRole('button', { name: 'Actual' });
|
|
66
|
+
await user.click(btn);
|
|
67
|
+
expect(onToggle).not.toHaveBeenCalled();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('Calls toggle onSelect when item is clicked', async () => {
|
|
71
|
+
const onSelect = jest.fn();
|
|
72
|
+
const items = [{ content: 'Action', value: 1 }];
|
|
73
|
+
const user = userEvent.setup();
|
|
74
|
+
render(<SimpleDropdown initialItems={items} toggleContent="Dropdown" onSelect={onSelect} />);
|
|
75
|
+
|
|
76
|
+
const toggle = screen.getByRole('button', { name: 'Dropdown' });
|
|
77
|
+
await user.click(toggle);
|
|
78
|
+
|
|
79
|
+
const actionItem = screen.getByRole('menuitem', { name: 'Action' });
|
|
80
|
+
await user.click(actionItem);
|
|
81
|
+
expect(onSelect).toHaveBeenCalledTimes(1);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test('Does not call toggle onSelect when item is not clicked', async () => {
|
|
85
|
+
const onSelect = jest.fn();
|
|
86
|
+
const items = [{ content: 'Action', value: 1 }];
|
|
87
|
+
const user = userEvent.setup();
|
|
88
|
+
render(<SimpleDropdown initialItems={items} toggleContent="Dropdown" onSelect={onSelect} />);
|
|
89
|
+
|
|
90
|
+
const toggle = screen.getByRole('button', { name: 'Dropdown' });
|
|
91
|
+
await user.click(toggle);
|
|
92
|
+
await user.click(toggle);
|
|
93
|
+
expect(onSelect).not.toHaveBeenCalled();
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test('Does not pass isToggleFullWidth to menu toggle by default', () => {
|
|
97
|
+
render(<SimpleDropdown toggleContent="Dropdown" />);
|
|
98
|
+
|
|
99
|
+
expect(screen.getByRole('button', { name: 'Dropdown' })).not.toHaveClass(styles.modifiers.fullWidth);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test('Passes isToggleFullWidth to menu toggle when passed in', () => {
|
|
103
|
+
render(<SimpleDropdown isToggleFullWidth toggleContent="Dropdown" />);
|
|
104
|
+
|
|
105
|
+
expect(screen.getByRole('button', { name: 'Dropdown' })).toHaveClass(styles.modifiers.fullWidth);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test('Does not focus toggle on item select by default', async () => {
|
|
109
|
+
const items = [{ content: 'Action', value: 1 }];
|
|
110
|
+
const user = userEvent.setup();
|
|
111
|
+
render(<SimpleDropdown initialItems={items} toggleContent="Dropdown" />);
|
|
112
|
+
|
|
113
|
+
const toggle = screen.getByRole('button', { name: 'Dropdown' });
|
|
114
|
+
await user.click(toggle);
|
|
115
|
+
const actionItem = screen.getByRole('menuitem', { name: 'Action' });
|
|
116
|
+
await user.click(actionItem);
|
|
117
|
+
|
|
118
|
+
expect(toggle).not.toHaveFocus();
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test('Focuses toggle on item select when shouldFocusToggleOnSelect is true', async () => {
|
|
122
|
+
const items = [{ content: 'Action', value: 1 }];
|
|
123
|
+
const user = userEvent.setup();
|
|
124
|
+
render(<SimpleDropdown shouldFocusToggleOnSelect initialItems={items} toggleContent="Dropdown" />);
|
|
125
|
+
|
|
126
|
+
const toggle = screen.getByRole('button', { name: 'Dropdown' });
|
|
127
|
+
await user.click(toggle);
|
|
128
|
+
const actionItem = screen.getByRole('menuitem', { name: 'Action' });
|
|
129
|
+
await user.click(actionItem);
|
|
130
|
+
|
|
131
|
+
expect(toggle).toHaveFocus();
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test('Matches snapshot', () => {
|
|
135
|
+
const { asFragment } = render(<SimpleDropdown toggleContent="Dropdown" />);
|
|
136
|
+
|
|
137
|
+
expect(asFragment()).toMatchSnapshot();
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
describe('Dropdown items', () => {
|
|
142
|
+
test('Renders with items', async () => {
|
|
143
|
+
const items = [
|
|
144
|
+
{ content: 'Action', value: 1 },
|
|
145
|
+
{ value: 'separator', isDivider: true }
|
|
146
|
+
];
|
|
147
|
+
const user = userEvent.setup();
|
|
148
|
+
render(<SimpleDropdown initialItems={items} toggleContent="Dropdown" />);
|
|
149
|
+
|
|
150
|
+
const toggle = screen.getByRole('button', { name: 'Dropdown' });
|
|
151
|
+
await user.click(toggle);
|
|
152
|
+
|
|
153
|
+
const actionItem = screen.getByRole('menuitem', { name: 'Action' });
|
|
154
|
+
const dividerItem = screen.getByRole('separator');
|
|
155
|
+
expect(actionItem).toBeInTheDocument();
|
|
156
|
+
expect(dividerItem).toBeInTheDocument();
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test('Renders with a link item', async () => {
|
|
160
|
+
const items = [{ content: 'Link', value: 1, to: '#' }];
|
|
161
|
+
const user = userEvent.setup();
|
|
162
|
+
render(<SimpleDropdown initialItems={items} toggleContent="Dropdown" />);
|
|
163
|
+
|
|
164
|
+
const toggle = screen.getByRole('button', { name: 'Dropdown' });
|
|
165
|
+
await user.click(toggle);
|
|
166
|
+
|
|
167
|
+
const linkItem = screen.getByRole('menuitem', { name: 'Link' });
|
|
168
|
+
expect(linkItem.getAttribute('href')).toBe('#');
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test('Renders with items not disabled by default', async () => {
|
|
172
|
+
const items = [{ content: 'Action', value: 1 }];
|
|
173
|
+
const user = userEvent.setup();
|
|
174
|
+
render(<SimpleDropdown initialItems={items} toggleContent="Dropdown" />);
|
|
175
|
+
|
|
176
|
+
const toggle = screen.getByRole('button', { name: 'Dropdown' });
|
|
177
|
+
await user.click(toggle);
|
|
178
|
+
|
|
179
|
+
const actionItem = screen.getByRole('menuitem', { name: 'Action' });
|
|
180
|
+
expect(actionItem).not.toBeDisabled();
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
test('Renders with a disabled item', async () => {
|
|
184
|
+
const items = [{ content: 'Action', value: 1, isDisabled: true }];
|
|
185
|
+
const user = userEvent.setup();
|
|
186
|
+
render(<SimpleDropdown initialItems={items} toggleContent="Dropdown" />);
|
|
187
|
+
|
|
188
|
+
const toggle = screen.getByRole('button', { name: 'Dropdown' });
|
|
189
|
+
await user.click(toggle);
|
|
190
|
+
|
|
191
|
+
const actionItem = screen.getByRole('menuitem', { name: 'Action' });
|
|
192
|
+
expect(actionItem).toBeDisabled();
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
test('Spreads props on item', async () => {
|
|
196
|
+
const items = [{ content: 'Action', value: 1, id: 'Test' }];
|
|
197
|
+
const user = userEvent.setup();
|
|
198
|
+
render(<SimpleDropdown initialItems={items} toggleContent="Dropdown" />);
|
|
199
|
+
|
|
200
|
+
const toggle = screen.getByRole('button', { name: 'Dropdown' });
|
|
201
|
+
await user.click(toggle);
|
|
202
|
+
|
|
203
|
+
const actionItem = screen.getByRole('menuitem', { name: 'Action' });
|
|
204
|
+
expect(actionItem.getAttribute('id')).toBe('Test');
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
test('Calls item onClick when clicked', async () => {
|
|
208
|
+
const onClick = jest.fn();
|
|
209
|
+
const items = [{ content: 'Action', value: 1, onClick }];
|
|
210
|
+
const user = userEvent.setup();
|
|
211
|
+
render(<SimpleDropdown initialItems={items} toggleContent="Dropdown" />);
|
|
212
|
+
|
|
213
|
+
const toggle = screen.getByRole('button', { name: 'Dropdown' });
|
|
214
|
+
await user.click(toggle);
|
|
215
|
+
|
|
216
|
+
const actionItem = screen.getByRole('menuitem', { name: 'Action' });
|
|
217
|
+
await user.click(actionItem);
|
|
218
|
+
expect(onClick).toHaveBeenCalledTimes(1);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
test('Does not call item onClick when not clicked', async () => {
|
|
222
|
+
const onClick = jest.fn();
|
|
223
|
+
const items = [
|
|
224
|
+
{ content: 'Action', value: 1, onClick },
|
|
225
|
+
{ content: 'Action 2', value: 2 }
|
|
226
|
+
];
|
|
227
|
+
const user = userEvent.setup();
|
|
228
|
+
render(<SimpleDropdown initialItems={items} toggleContent="Dropdown" />);
|
|
229
|
+
|
|
230
|
+
const toggle = screen.getByRole('button', { name: 'Dropdown' });
|
|
231
|
+
await user.click(toggle);
|
|
232
|
+
|
|
233
|
+
const actionItem = screen.getByRole('menuitem', { name: 'Action 2' });
|
|
234
|
+
await user.click(actionItem);
|
|
235
|
+
expect(onClick).not.toHaveBeenCalled();
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
test('Does not call item onClick when clicked and item is disabled', async () => {
|
|
239
|
+
const onClick = jest.fn();
|
|
240
|
+
const items = [{ content: 'Action', value: 1, onClick, isDisabled: true }];
|
|
241
|
+
const user = userEvent.setup();
|
|
242
|
+
render(<SimpleDropdown initialItems={items} toggleContent="Dropdown" />);
|
|
243
|
+
|
|
244
|
+
const toggle = screen.getByRole('button', { name: 'Dropdown' });
|
|
245
|
+
await user.click(toggle);
|
|
246
|
+
|
|
247
|
+
const actionItem = screen.getByRole('menuitem', { name: 'Action' });
|
|
248
|
+
await user.click(actionItem);
|
|
249
|
+
expect(onClick).not.toHaveBeenCalled();
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
test('Matches snapshot', async () => {
|
|
253
|
+
const items = [
|
|
254
|
+
{ content: 'Action', value: 1, ouiaId: '1' },
|
|
255
|
+
{ value: 'separator', isDivider: true, ouiaId: '2' },
|
|
256
|
+
{ content: 'Link', value: 'separator', to: '#', ouiaId: '3' }
|
|
257
|
+
];
|
|
258
|
+
const user = userEvent.setup();
|
|
259
|
+
const { asFragment } = render(<SimpleDropdown ouiaId={4} initialItems={items} toggleContent="Dropdown" />);
|
|
260
|
+
|
|
261
|
+
const toggle = screen.getByRole('button', { name: 'Dropdown' });
|
|
262
|
+
await user.click(toggle);
|
|
263
|
+
|
|
264
|
+
expect(asFragment()).toMatchSnapshot();
|
|
265
|
+
});
|
|
266
|
+
});
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
|
+
|
|
3
|
+
exports[`Dropdown items Matches snapshot 1`] = `
|
|
4
|
+
<DocumentFragment>
|
|
5
|
+
<button
|
|
6
|
+
aria-expanded="true"
|
|
7
|
+
class="pf-v6-c-menu-toggle pf-m-expanded"
|
|
8
|
+
data-ouia-component-id="OUIA-Generated-MenuToggle-default-23"
|
|
9
|
+
data-ouia-component-type="PF6/MenuToggle"
|
|
10
|
+
data-ouia-safe="true"
|
|
11
|
+
type="button"
|
|
12
|
+
>
|
|
13
|
+
<span
|
|
14
|
+
class="pf-v6-c-menu-toggle__text"
|
|
15
|
+
>
|
|
16
|
+
Dropdown
|
|
17
|
+
</span>
|
|
18
|
+
<span
|
|
19
|
+
class="pf-v6-c-menu-toggle__controls"
|
|
20
|
+
>
|
|
21
|
+
<span
|
|
22
|
+
class="pf-v6-c-menu-toggle__toggle-icon"
|
|
23
|
+
>
|
|
24
|
+
<svg
|
|
25
|
+
aria-hidden="true"
|
|
26
|
+
class="pf-v6-svg"
|
|
27
|
+
fill="currentColor"
|
|
28
|
+
height="1em"
|
|
29
|
+
role="img"
|
|
30
|
+
viewBox="0 0 320 512"
|
|
31
|
+
width="1em"
|
|
32
|
+
>
|
|
33
|
+
<path
|
|
34
|
+
d="M31.3 192h257.3c17.8 0 26.7 21.5 14.1 34.1L174.1 354.8c-7.8 7.8-20.5 7.8-28.3 0L17.2 226.1C4.6 213.5 13.5 192 31.3 192z"
|
|
35
|
+
/>
|
|
36
|
+
</svg>
|
|
37
|
+
</span>
|
|
38
|
+
</span>
|
|
39
|
+
</button>
|
|
40
|
+
</DocumentFragment>
|
|
41
|
+
`;
|
|
42
|
+
|
|
43
|
+
exports[`Dropdown toggle Matches snapshot 1`] = `
|
|
44
|
+
<DocumentFragment>
|
|
45
|
+
<button
|
|
46
|
+
aria-expanded="false"
|
|
47
|
+
class="pf-v6-c-menu-toggle"
|
|
48
|
+
data-ouia-component-id="OUIA-Generated-MenuToggle-default-14"
|
|
49
|
+
data-ouia-component-type="PF6/MenuToggle"
|
|
50
|
+
data-ouia-safe="true"
|
|
51
|
+
type="button"
|
|
52
|
+
>
|
|
53
|
+
<span
|
|
54
|
+
class="pf-v6-c-menu-toggle__text"
|
|
55
|
+
>
|
|
56
|
+
Dropdown
|
|
57
|
+
</span>
|
|
58
|
+
<span
|
|
59
|
+
class="pf-v6-c-menu-toggle__controls"
|
|
60
|
+
>
|
|
61
|
+
<span
|
|
62
|
+
class="pf-v6-c-menu-toggle__toggle-icon"
|
|
63
|
+
>
|
|
64
|
+
<svg
|
|
65
|
+
aria-hidden="true"
|
|
66
|
+
class="pf-v6-svg"
|
|
67
|
+
fill="currentColor"
|
|
68
|
+
height="1em"
|
|
69
|
+
role="img"
|
|
70
|
+
viewBox="0 0 320 512"
|
|
71
|
+
width="1em"
|
|
72
|
+
>
|
|
73
|
+
<path
|
|
74
|
+
d="M31.3 192h257.3c17.8 0 26.7 21.5 14.1 34.1L174.1 354.8c-7.8 7.8-20.5 7.8-28.3 0L17.2 226.1C4.6 213.5 13.5 192 31.3 192z"
|
|
75
|
+
/>
|
|
76
|
+
</svg>
|
|
77
|
+
</span>
|
|
78
|
+
</span>
|
|
79
|
+
</button>
|
|
80
|
+
</DocumentFragment>
|
|
81
|
+
`;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: Dropdown
|
|
3
|
+
section: components
|
|
4
|
+
subsection: menus
|
|
5
|
+
template: true
|
|
6
|
+
beta: true
|
|
7
|
+
propComponents: ['SimpleDropdown', 'SimpleDropdownItem']
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
Note: Templates live in their own package at [@patternfly/react-templates](https://www.npmjs.com/package/@patternfly/react-templates)!
|
|
11
|
+
|
|
12
|
+
For custom use cases, please see the dropdown component suite from [@patternfly/react-core](https://www.npmjs.com/package/@patternfly/react-core).
|
|
13
|
+
|
|
14
|
+
import { Checkbox, Flex, FlexItem } from '@patternfly/react-core';
|
|
15
|
+
import { SimpleDropdown } from '@patternfly/react-templates';
|
|
16
|
+
import EllipsisVIcon from '@patternfly/react-icons/dist/esm/icons/ellipsis-v-icon';
|
|
17
|
+
|
|
18
|
+
## Examples
|
|
19
|
+
|
|
20
|
+
### Simple
|
|
21
|
+
|
|
22
|
+
```ts file="./SimpleDropdownExample.tsx"
|
|
23
|
+
|
|
24
|
+
```
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Checkbox, Flex, FlexItem } from '@patternfly/react-core';
|
|
3
|
+
import { SimpleDropdown, SimpleDropdownItem } from '@patternfly/react-templates';
|
|
4
|
+
import EllipsisVIcon from '@patternfly/react-icons/dist/esm/icons/ellipsis-v-icon';
|
|
5
|
+
|
|
6
|
+
export const SimpleDropdownExample: React.FunctionComponent = () => {
|
|
7
|
+
const [isDisabled, setIsDisabled] = React.useState(false);
|
|
8
|
+
|
|
9
|
+
const items: SimpleDropdownItem[] = [
|
|
10
|
+
// eslint-disable-next-line no-console
|
|
11
|
+
{ content: 'Action', value: 1, onClick: () => console.log('Action clicked') },
|
|
12
|
+
// Prevent default click behavior on link for example purposes
|
|
13
|
+
{ content: 'Link', value: 2, to: '#', onClick: (event: any) => event.preventDefault() },
|
|
14
|
+
{ content: 'Disabled Action', value: 3, isDisabled: true },
|
|
15
|
+
{ value: 'separator', isDivider: true },
|
|
16
|
+
// eslint-disable-next-line no-console
|
|
17
|
+
{ content: 'Second action', value: 4, onClick: () => console.log('Second action clicked') }
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<React.Fragment>
|
|
22
|
+
<Checkbox
|
|
23
|
+
id="simple-example-disabled-toggle"
|
|
24
|
+
label="Disable simple dropdown toggles"
|
|
25
|
+
isChecked={isDisabled}
|
|
26
|
+
onChange={(_event: React.FormEvent<HTMLInputElement>, checked: boolean) => setIsDisabled(checked)}
|
|
27
|
+
style={{ marginBottom: 20 }}
|
|
28
|
+
/>
|
|
29
|
+
<Flex gap={{ default: 'gapLg' }}>
|
|
30
|
+
<FlexItem>
|
|
31
|
+
<SimpleDropdown initialItems={items} isDisabled={isDisabled} toggleContent="Dropdown" />
|
|
32
|
+
</FlexItem>
|
|
33
|
+
<FlexItem>
|
|
34
|
+
<SimpleDropdown
|
|
35
|
+
toggleVariant="plainText"
|
|
36
|
+
initialItems={items}
|
|
37
|
+
isDisabled={isDisabled}
|
|
38
|
+
toggleContent="Dropdown with plainText styling"
|
|
39
|
+
/>
|
|
40
|
+
</FlexItem>
|
|
41
|
+
<FlexItem>
|
|
42
|
+
<SimpleDropdown
|
|
43
|
+
toggleVariant="plain"
|
|
44
|
+
initialItems={items}
|
|
45
|
+
isDisabled={isDisabled}
|
|
46
|
+
toggleContent={<EllipsisVIcon aria-hidden="true" />}
|
|
47
|
+
toggleAriaLabel="Plain dropdown toggle"
|
|
48
|
+
/>
|
|
49
|
+
</FlexItem>
|
|
50
|
+
</Flex>
|
|
51
|
+
</React.Fragment>
|
|
52
|
+
);
|
|
53
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './SimpleDropdown';
|
|
@@ -3,10 +3,12 @@ import {
|
|
|
3
3
|
Badge,
|
|
4
4
|
MenuToggle,
|
|
5
5
|
MenuToggleElement,
|
|
6
|
+
MenuToggleProps,
|
|
6
7
|
Select,
|
|
7
8
|
SelectList,
|
|
8
9
|
SelectOption,
|
|
9
|
-
SelectOptionProps
|
|
10
|
+
SelectOptionProps,
|
|
11
|
+
SelectProps
|
|
10
12
|
} from '@patternfly/react-core';
|
|
11
13
|
|
|
12
14
|
export interface CheckboxSelectOption extends Omit<SelectOptionProps, 'content'> {
|
|
@@ -16,7 +18,7 @@ export interface CheckboxSelectOption extends Omit<SelectOptionProps, 'content'>
|
|
|
16
18
|
value: string | number;
|
|
17
19
|
}
|
|
18
20
|
|
|
19
|
-
export interface CheckboxSelectProps {
|
|
21
|
+
export interface CheckboxSelectProps extends Omit<SelectProps, 'toggle'> {
|
|
20
22
|
/** @hide Forwarded ref */
|
|
21
23
|
innerRef?: React.Ref<any>;
|
|
22
24
|
/** Initial options of the select. */
|
|
@@ -27,8 +29,12 @@ export interface CheckboxSelectProps {
|
|
|
27
29
|
onToggle?: (nextIsOpen: boolean) => void;
|
|
28
30
|
/** Flag indicating the select should be disabled. */
|
|
29
31
|
isDisabled?: boolean;
|
|
30
|
-
/** Content of the toggle. Defaults to
|
|
32
|
+
/** Content of the toggle. Defaults to a string with badge count of selected options. */
|
|
31
33
|
toggleContent?: React.ReactNode;
|
|
34
|
+
/** Width of the toggle. */
|
|
35
|
+
toggleWidth?: string;
|
|
36
|
+
/** Additional props passed to the toggle. */
|
|
37
|
+
toggleProps?: MenuToggleProps;
|
|
32
38
|
}
|
|
33
39
|
|
|
34
40
|
const CheckboxSelectBase: React.FunctionComponent<CheckboxSelectProps> = ({
|
|
@@ -38,16 +44,23 @@ const CheckboxSelectBase: React.FunctionComponent<CheckboxSelectProps> = ({
|
|
|
38
44
|
onSelect: passedOnSelect,
|
|
39
45
|
onToggle,
|
|
40
46
|
toggleContent,
|
|
47
|
+
toggleWidth = '200px',
|
|
48
|
+
toggleProps,
|
|
41
49
|
...props
|
|
42
50
|
}: CheckboxSelectProps) => {
|
|
43
51
|
const [isOpen, setIsOpen] = React.useState(false);
|
|
44
52
|
const [selected, setSelected] = React.useState<string[]>([]);
|
|
45
53
|
|
|
54
|
+
React.useEffect(() => {
|
|
55
|
+
const selectedOptions = initialOptions?.filter((option) => option.selected);
|
|
56
|
+
setSelected(selectedOptions?.map((selectedOption) => String(selectedOption.value)) ?? []);
|
|
57
|
+
}, [initialOptions]);
|
|
58
|
+
|
|
46
59
|
const checkboxSelectOptions = initialOptions?.map((option) => {
|
|
47
60
|
const { content, value, ...props } = option;
|
|
48
61
|
const isSelected = selected.includes(`${value}`);
|
|
49
62
|
return (
|
|
50
|
-
<SelectOption
|
|
63
|
+
<SelectOption value={value} key={value} hasCheckbox isSelected={isSelected} {...props}>
|
|
51
64
|
{content}
|
|
52
65
|
</SelectOption>
|
|
53
66
|
);
|
|
@@ -83,9 +96,10 @@ const CheckboxSelectBase: React.FunctionComponent<CheckboxSelectProps> = ({
|
|
|
83
96
|
isDisabled={isDisabled}
|
|
84
97
|
style={
|
|
85
98
|
{
|
|
86
|
-
width:
|
|
99
|
+
width: toggleWidth
|
|
87
100
|
} as React.CSSProperties
|
|
88
101
|
}
|
|
102
|
+
{...toggleProps}
|
|
89
103
|
>
|
|
90
104
|
{toggleContent || defaultToggleContent}
|
|
91
105
|
</MenuToggle>
|
|
@@ -93,11 +107,13 @@ const CheckboxSelectBase: React.FunctionComponent<CheckboxSelectProps> = ({
|
|
|
93
107
|
|
|
94
108
|
return (
|
|
95
109
|
<Select
|
|
96
|
-
id="checkbox-select"
|
|
97
110
|
isOpen={isOpen}
|
|
98
111
|
selected={selected}
|
|
99
112
|
onSelect={onSelect}
|
|
100
|
-
onOpenChange={(isOpen) =>
|
|
113
|
+
onOpenChange={(isOpen) => {
|
|
114
|
+
onToggle && onToggle(isOpen);
|
|
115
|
+
setIsOpen(isOpen);
|
|
116
|
+
}}
|
|
101
117
|
toggle={toggle}
|
|
102
118
|
ref={innerRef}
|
|
103
119
|
role="menu"
|