@patternfly/react-templates 6.0.0-alpha.5 → 6.0.0-alpha.51

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 (108) hide show
  1. package/CHANGELOG.md +190 -0
  2. package/README.md +4 -17
  3. package/components/package.json +1 -1
  4. package/dist/dynamic/components/Dropdown/package.json +1 -0
  5. package/dist/dynamic/components/Select/package.json +1 -1
  6. package/dist/dynamic-modules.json +19 -0
  7. package/dist/esm/components/Dropdown/SimpleDropdown.d.ts +48 -0
  8. package/dist/esm/components/Dropdown/SimpleDropdown.d.ts.map +1 -0
  9. package/dist/esm/components/Dropdown/SimpleDropdown.js +33 -0
  10. package/dist/esm/components/Dropdown/SimpleDropdown.js.map +1 -0
  11. package/dist/esm/components/Dropdown/index.d.ts +2 -0
  12. package/dist/esm/components/Dropdown/index.d.ts.map +1 -0
  13. package/dist/esm/components/Dropdown/index.js +2 -0
  14. package/dist/esm/components/Dropdown/index.js.map +1 -0
  15. package/dist/esm/components/Select/CheckboxSelect.d.ts +7 -3
  16. package/dist/esm/components/Select/CheckboxSelect.d.ts.map +1 -1
  17. package/dist/esm/components/Select/CheckboxSelect.js +14 -6
  18. package/dist/esm/components/Select/CheckboxSelect.js.map +1 -1
  19. package/dist/esm/components/Select/MultiTypeaheadSelect.d.ts +33 -0
  20. package/dist/esm/components/Select/MultiTypeaheadSelect.d.ts.map +1 -0
  21. package/dist/esm/components/Select/MultiTypeaheadSelect.js +186 -0
  22. package/dist/esm/components/Select/MultiTypeaheadSelect.js.map +1 -0
  23. package/dist/esm/components/Select/{SelectSimple.d.ts → SimpleSelect.d.ts} +12 -7
  24. package/dist/esm/components/Select/SimpleSelect.d.ts.map +1 -0
  25. package/dist/esm/components/Select/SimpleSelect.js +39 -0
  26. package/dist/esm/components/Select/SimpleSelect.js.map +1 -0
  27. package/dist/esm/components/Select/TypeaheadSelect.d.ts +43 -0
  28. package/dist/esm/components/Select/TypeaheadSelect.d.ts.map +1 -0
  29. package/dist/esm/components/Select/TypeaheadSelect.js +219 -0
  30. package/dist/esm/components/Select/TypeaheadSelect.js.map +1 -0
  31. package/dist/esm/components/Select/index.d.ts +3 -1
  32. package/dist/esm/components/Select/index.d.ts.map +1 -1
  33. package/dist/esm/components/Select/index.js +3 -1
  34. package/dist/esm/components/Select/index.js.map +1 -1
  35. package/dist/esm/components/index.d.ts +1 -0
  36. package/dist/esm/components/index.d.ts.map +1 -1
  37. package/dist/esm/components/index.js +1 -0
  38. package/dist/esm/components/index.js.map +1 -1
  39. package/dist/js/components/Dropdown/SimpleDropdown.d.ts +48 -0
  40. package/dist/js/components/Dropdown/SimpleDropdown.d.ts.map +1 -0
  41. package/dist/js/components/Dropdown/SimpleDropdown.js +36 -0
  42. package/dist/js/components/Dropdown/SimpleDropdown.js.map +1 -0
  43. package/dist/js/components/Dropdown/index.d.ts +2 -0
  44. package/dist/js/components/Dropdown/index.d.ts.map +1 -0
  45. package/dist/js/components/Dropdown/index.js +5 -0
  46. package/dist/js/components/Dropdown/index.js.map +1 -0
  47. package/dist/js/components/Select/CheckboxSelect.d.ts +7 -3
  48. package/dist/js/components/Select/CheckboxSelect.d.ts.map +1 -1
  49. package/dist/js/components/Select/CheckboxSelect.js +14 -6
  50. package/dist/js/components/Select/CheckboxSelect.js.map +1 -1
  51. package/dist/js/components/Select/MultiTypeaheadSelect.d.ts +33 -0
  52. package/dist/js/components/Select/MultiTypeaheadSelect.d.ts.map +1 -0
  53. package/dist/js/components/Select/MultiTypeaheadSelect.js +190 -0
  54. package/dist/js/components/Select/MultiTypeaheadSelect.js.map +1 -0
  55. package/dist/js/components/Select/{SelectSimple.d.ts → SimpleSelect.d.ts} +12 -7
  56. package/dist/js/components/Select/SimpleSelect.d.ts.map +1 -0
  57. package/dist/js/components/Select/SimpleSelect.js +42 -0
  58. package/dist/js/components/Select/SimpleSelect.js.map +1 -0
  59. package/dist/js/components/Select/TypeaheadSelect.d.ts +43 -0
  60. package/dist/js/components/Select/TypeaheadSelect.d.ts.map +1 -0
  61. package/dist/js/components/Select/TypeaheadSelect.js +223 -0
  62. package/dist/js/components/Select/TypeaheadSelect.js.map +1 -0
  63. package/dist/js/components/Select/index.d.ts +3 -1
  64. package/dist/js/components/Select/index.d.ts.map +1 -1
  65. package/dist/js/components/Select/index.js +3 -1
  66. package/dist/js/components/Select/index.js.map +1 -1
  67. package/dist/js/components/index.d.ts +1 -0
  68. package/dist/js/components/index.d.ts.map +1 -1
  69. package/dist/js/components/index.js +1 -0
  70. package/dist/js/components/index.js.map +1 -1
  71. package/dist/umd/assets/output-cDtFGi9V.css +23397 -0
  72. package/dist/umd/react-templates.min.js +1 -1
  73. package/package.json +7 -11
  74. package/rollup.config.mjs +8 -0
  75. package/src/components/Dropdown/SimpleDropdown.tsx +139 -0
  76. package/src/components/Dropdown/__tests__/SimpleDropdown.test.tsx +266 -0
  77. package/src/components/Dropdown/__tests__/__snapshots__/SimpleDropdown.test.tsx.snap +81 -0
  78. package/src/components/Dropdown/examples/DropdownTemplates.md +24 -0
  79. package/src/components/Dropdown/examples/SimpleDropdownExample.tsx +53 -0
  80. package/src/components/Dropdown/index.ts +1 -0
  81. package/src/components/Select/CheckboxSelect.tsx +23 -7
  82. package/src/components/Select/MultiTypeaheadSelect.tsx +348 -0
  83. package/src/components/Select/{SelectSimple.tsx → SimpleSelect.tsx} +39 -18
  84. package/src/components/Select/TypeaheadSelect.tsx +388 -0
  85. package/src/components/Select/{CheckboxSelect.test.tsx → __tests__/CheckboxSelect.test.tsx} +29 -1
  86. package/src/components/Select/{CheckboxSelectSnapshots.test.tsx → __tests__/CheckboxSelectSnapshots.test.tsx} +1 -1
  87. package/src/components/Select/__tests__/MultiTypeaheadSelect.test.tsx +414 -0
  88. package/src/components/Select/__tests__/SimpleSelect.test.tsx +241 -0
  89. package/src/components/Select/__tests__/TypeaheadSelect.test.tsx +408 -0
  90. package/src/components/Select/__tests__/__snapshots__/CheckboxSelectSnapshots.test.tsx.snap +83 -0
  91. package/src/components/Select/__tests__/__snapshots__/MultiTypeaheadSelect.test.tsx.snap +97 -0
  92. package/src/components/Select/__tests__/__snapshots__/SimpleSelect.test.tsx.snap +83 -0
  93. package/src/components/Select/__tests__/__snapshots__/TypeaheadSelect.test.tsx.snap +97 -0
  94. package/src/components/Select/examples/CheckboxSelectDemo.tsx +25 -8
  95. package/src/components/Select/examples/MultiTypeaheadSelectDemo.tsx +35 -0
  96. package/src/components/Select/examples/SelectTemplates.md +17 -4
  97. package/src/components/Select/examples/SimpleSelectDemo.tsx +36 -0
  98. package/src/components/Select/examples/TypeaheadSelectDemo.tsx +65 -0
  99. package/src/components/Select/index.ts +3 -1
  100. package/src/components/index.ts +1 -0
  101. package/dist/esm/components/Select/SelectSimple.d.ts.map +0 -1
  102. package/dist/esm/components/Select/SelectSimple.js +0 -31
  103. package/dist/esm/components/Select/SelectSimple.js.map +0 -1
  104. package/dist/js/components/Select/SelectSimple.d.ts.map +0 -1
  105. package/dist/js/components/Select/SelectSimple.js +0 -34
  106. package/dist/js/components/Select/SelectSimple.js.map +0 -1
  107. package/src/components/Select/__snapshots__/CheckboxSelectSnapshots.test.tsx.snap +0 -212
  108. 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 the selected option. */
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 {...props} value={value} key={value} hasCheckbox isSelected={isSelected}>
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: '200px'
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) => setIsOpen(isOpen)}
113
+ onOpenChange={(isOpen) => {
114
+ onToggle && onToggle(isOpen);
115
+ setIsOpen(isOpen);
116
+ }}
101
117
  toggle={toggle}
102
118
  ref={innerRef}
103
119
  role="menu"