@sproutsocial/seeds-react-menu 1.6.0 → 1.6.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/.turbo/turbo-build.log +10 -10
- package/CHANGELOG.md +14 -0
- package/dist/esm/index.js +22 -20
- package/dist/esm/index.js.map +1 -1
- package/dist/index.d.mts +3 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +22 -20
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/ActionMenu/ActionMenu.stories.tsx +20 -1
- package/src/Autocomplete/AutocompleteTypes.ts +1 -0
- package/src/Autocomplete/MultiAutocomplete.tsx +2 -1
- package/src/Autocomplete/SingleAutocomplete.tsx +2 -1
- package/src/Autocomplete/__tests__/Autocomplete.test.tsx +18 -3
- package/src/MenuGroup/__tests__/MenuGroup.test.tsx +33 -0
- package/src/MenuGroup/__tests__/MenuGroup.typetest.tsx +5 -0
- package/src/hooks/useMenuChildren.tsx +23 -22
- package/src/types.ts +2 -4
package/package.json
CHANGED
|
@@ -65,11 +65,22 @@ const handleOnClick = (e: React.MouseEvent<HTMLElement>, itemName: string) => {
|
|
|
65
65
|
action(`${itemName}`)(e);
|
|
66
66
|
};
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
type ActionMenuSimpleStoryArgs = ActionMenuStoryArgs & {
|
|
69
|
+
"Show Conditional Item"?: boolean;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export const ActionMenuSimple: StoryObj<ActionMenuSimpleStoryArgs> = {
|
|
69
73
|
name: "Simple Action Menu",
|
|
70
74
|
args: {
|
|
71
75
|
"Trigger Text": "ActionMenu",
|
|
72
76
|
"Trigger Appearance": "secondary",
|
|
77
|
+
"Show Conditional Item": false,
|
|
78
|
+
},
|
|
79
|
+
argTypes: {
|
|
80
|
+
"Show Conditional Item": {
|
|
81
|
+
control: { type: "boolean" },
|
|
82
|
+
description: "Toggle to show/hide the conditional menu item",
|
|
83
|
+
},
|
|
73
84
|
},
|
|
74
85
|
render: (args) => {
|
|
75
86
|
return (
|
|
@@ -106,6 +117,14 @@ export const ActionMenuSimple: Story = {
|
|
|
106
117
|
>
|
|
107
118
|
This Opens a Link
|
|
108
119
|
</MenuItem>
|
|
120
|
+
{args["Show Conditional Item"] && (
|
|
121
|
+
<MenuItem
|
|
122
|
+
onClick={(e) => handleOnClick(e, "Conditional Menu Item")}
|
|
123
|
+
id="conditional"
|
|
124
|
+
>
|
|
125
|
+
Conditional Menu Item
|
|
126
|
+
</MenuItem>
|
|
127
|
+
)}
|
|
109
128
|
</MenuGroup>
|
|
110
129
|
</MenuContent>
|
|
111
130
|
</ActionMenu>
|
|
@@ -16,6 +16,7 @@ export type TypeBaseAutocompleteProps = TypeHigherOrderComponentProps &
|
|
|
16
16
|
/** Set multiSelect to true to allow multiple selections.
|
|
17
17
|
* Default: false*/
|
|
18
18
|
multiSelect?: boolean;
|
|
19
|
+
autoCloseOnEmpty?: boolean;
|
|
19
20
|
};
|
|
20
21
|
|
|
21
22
|
export type TypeSingleAutocompleteProps<
|
|
@@ -37,6 +37,7 @@ export const MultiAutocomplete = <
|
|
|
37
37
|
getA11yMultiStatusMessage,
|
|
38
38
|
onMultiSelectStateChange,
|
|
39
39
|
multiSelectStateReducer,
|
|
40
|
+
autoCloseOnEmpty = true,
|
|
40
41
|
...useComboboxProps
|
|
41
42
|
}: TypeMultiAutocompleteProps<I>) => {
|
|
42
43
|
/** Get a list of menuItems and children */
|
|
@@ -175,6 +176,7 @@ export const MultiAutocomplete = <
|
|
|
175
176
|
<MenuRootComponent
|
|
176
177
|
{...{
|
|
177
178
|
isListbox: true,
|
|
179
|
+
autoCloseOnEmpty,
|
|
178
180
|
items: allMenuItems,
|
|
179
181
|
itemToString,
|
|
180
182
|
initialSelectedItems,
|
|
@@ -192,7 +194,6 @@ export const MultiAutocomplete = <
|
|
|
192
194
|
focusOnContent: false,
|
|
193
195
|
...userPopoutProps,
|
|
194
196
|
}}
|
|
195
|
-
autoCloseOnEmpty
|
|
196
197
|
>
|
|
197
198
|
{children}
|
|
198
199
|
</MenuRootComponent>
|
|
@@ -20,6 +20,7 @@ export const SingleAutocomplete = <
|
|
|
20
20
|
MenuItemComponent,
|
|
21
21
|
itemToString = defaultItemToString,
|
|
22
22
|
getIsItemVisible,
|
|
23
|
+
autoCloseOnEmpty = true,
|
|
23
24
|
...useComboboxProps
|
|
24
25
|
}: TypeSingleAutocompleteProps<I>) => {
|
|
25
26
|
/** Get a list of menuItems and children */
|
|
@@ -63,6 +64,7 @@ export const SingleAutocomplete = <
|
|
|
63
64
|
<MenuRootComponent
|
|
64
65
|
{...{
|
|
65
66
|
isListbox: true,
|
|
67
|
+
autoCloseOnEmpty,
|
|
66
68
|
items: allMenuItems,
|
|
67
69
|
itemToString,
|
|
68
70
|
...useComboboxProps,
|
|
@@ -73,7 +75,6 @@ export const SingleAutocomplete = <
|
|
|
73
75
|
focusOnContent: false,
|
|
74
76
|
...userPopoutProps,
|
|
75
77
|
}}
|
|
76
|
-
autoCloseOnEmpty
|
|
77
78
|
>
|
|
78
79
|
{children}
|
|
79
80
|
</MenuRootComponent>
|
|
@@ -174,7 +174,7 @@ describe("Autocomplete Component", () => {
|
|
|
174
174
|
expect(customFilter).toHaveBeenCalled();
|
|
175
175
|
});
|
|
176
176
|
|
|
177
|
-
it("should close the dropdown when no items match the input", async () => {
|
|
177
|
+
it("should close the dropdown when no items match the input by default", async () => {
|
|
178
178
|
const { user } = render(<Autocomplete {...defaultAutocompleteProps} />);
|
|
179
179
|
const inputElement = screen.getByRole("combobox");
|
|
180
180
|
|
|
@@ -184,11 +184,26 @@ describe("Autocomplete Component", () => {
|
|
|
184
184
|
});
|
|
185
185
|
|
|
186
186
|
await waitFor(() => {
|
|
187
|
-
const
|
|
188
|
-
expect(
|
|
187
|
+
const emptyState = screen.queryByText("No items found");
|
|
188
|
+
expect(emptyState).not.toBeInTheDocument();
|
|
189
189
|
});
|
|
190
190
|
});
|
|
191
191
|
|
|
192
|
+
it("should not close the dropdown when no items match the input if supplied autoCloseOnEmpty={false}", async () => {
|
|
193
|
+
const { user } = render(
|
|
194
|
+
<Autocomplete {...defaultAutocompleteProps} autoCloseOnEmpty={false} />
|
|
195
|
+
);
|
|
196
|
+
const inputElement = screen.getByRole("combobox");
|
|
197
|
+
|
|
198
|
+
await act(async () => {
|
|
199
|
+
await user.click(inputElement);
|
|
200
|
+
await user.type(inputElement, "something not in the list");
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
const emptyState = screen.getByText("No items found");
|
|
204
|
+
expect(emptyState).toBeInTheDocument();
|
|
205
|
+
});
|
|
206
|
+
|
|
192
207
|
it("should apply custom popoutProps.width to the popout container", async () => {
|
|
193
208
|
const { user } = render(
|
|
194
209
|
<Autocomplete
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
import {
|
|
9
9
|
SingleSelectMenu,
|
|
10
10
|
MultiSelectMenu,
|
|
11
|
+
ActionMenu,
|
|
11
12
|
MenuContent,
|
|
12
13
|
MenuGroup,
|
|
13
14
|
MenuToggleButton,
|
|
@@ -284,4 +285,36 @@ describe("MenuGroup", () => {
|
|
|
284
285
|
xit("ignores the customMenuItem prop", () => {});
|
|
285
286
|
});
|
|
286
287
|
});
|
|
288
|
+
|
|
289
|
+
it("supports conditionally rendered items when false", () => {
|
|
290
|
+
// Spy on console.error to catch and verify console errors
|
|
291
|
+
const consoleErrorSpy = jest
|
|
292
|
+
.spyOn(console, "error")
|
|
293
|
+
.mockImplementation(() => {});
|
|
294
|
+
|
|
295
|
+
render(
|
|
296
|
+
<ActionMenu
|
|
297
|
+
defaultIsOpen
|
|
298
|
+
menuToggleElement={<MenuToggleButton>Test Menu</MenuToggleButton>}
|
|
299
|
+
>
|
|
300
|
+
<MenuContent>
|
|
301
|
+
<MenuGroup id="group">
|
|
302
|
+
<MenuItem id="item-1">Item 1</MenuItem>
|
|
303
|
+
{false && (
|
|
304
|
+
<MenuItem id="conditional" onClick={() => {}}>
|
|
305
|
+
Conditional Menu Item
|
|
306
|
+
</MenuItem>
|
|
307
|
+
)}
|
|
308
|
+
<MenuItem id="item-2">Item 2</MenuItem>
|
|
309
|
+
</MenuGroup>
|
|
310
|
+
</MenuContent>
|
|
311
|
+
</ActionMenu>
|
|
312
|
+
);
|
|
313
|
+
|
|
314
|
+
// Verify no console errors occurred during render
|
|
315
|
+
expect(consoleErrorSpy).not.toHaveBeenCalled();
|
|
316
|
+
|
|
317
|
+
// Restore console.error
|
|
318
|
+
consoleErrorSpy.mockRestore();
|
|
319
|
+
});
|
|
287
320
|
});
|
|
@@ -10,6 +10,11 @@ export const MenuGroupTypeTest = () => (
|
|
|
10
10
|
<MenuItem id="1">Test 1</MenuItem>
|
|
11
11
|
<MenuItem id="2">Test 2</MenuItem>
|
|
12
12
|
</MenuGroup>
|
|
13
|
+
{/* MenuGroup allows children with conditional MenuItems */}
|
|
14
|
+
<MenuGroup id="group">
|
|
15
|
+
<MenuItem id="1">Test 1</MenuItem>
|
|
16
|
+
{false && <MenuItem id="2">Test 2</MenuItem>}
|
|
17
|
+
</MenuGroup>
|
|
13
18
|
{/* MenuGroup allows menuItems prop */}
|
|
14
19
|
<MenuGroup id="group" menuItems={[{ id: "1", children: "1" }]} />
|
|
15
20
|
{/* MenuGroup allows menuItems prop with MenuItemComponent */}
|
|
@@ -5,7 +5,7 @@ import type {
|
|
|
5
5
|
TypeSubmenuValidMenuComponentProps,
|
|
6
6
|
} from "../SubmenuItem";
|
|
7
7
|
import type { TypeMenuContext } from "../MenuContext";
|
|
8
|
-
import type { TypeMenuGroupProps
|
|
8
|
+
import type { TypeMenuGroupProps } from "../MenuGroup";
|
|
9
9
|
import type { TypeMenuRootProps } from "../MenuRoot";
|
|
10
10
|
import type {
|
|
11
11
|
TypeNotEmptyChildren,
|
|
@@ -46,12 +46,13 @@ const walkAllChildren = (
|
|
|
46
46
|
if (element === null || element === undefined) return;
|
|
47
47
|
callback(element, parents);
|
|
48
48
|
const newParents = [...parents, element];
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
49
|
+
if (React.isValidElement(element) && element.props?.children) {
|
|
50
|
+
React.Children.toArray(element.props.children).forEach((child) => {
|
|
51
|
+
walk(child, newParents);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
53
54
|
};
|
|
54
|
-
const parents = [];
|
|
55
|
+
const parents: ReactNode[] = [];
|
|
55
56
|
React.Children.toArray(children).forEach((child) => {
|
|
56
57
|
walk(child, parents);
|
|
57
58
|
});
|
|
@@ -114,22 +115,22 @@ const addItemsFromGroup = <I extends TypeMenuItemProps = TypeMenuItemProps>({
|
|
|
114
115
|
});
|
|
115
116
|
});
|
|
116
117
|
} else if (menuGroup.props.children) {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
if (!Array.isArray(children)) {
|
|
120
|
-
children = [children];
|
|
121
|
-
}
|
|
118
|
+
// Convert children to array to handle both arrays and iterables
|
|
119
|
+
const children = React.Children.toArray(menuGroup.props.children);
|
|
122
120
|
children.forEach((menuItem) => {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
121
|
+
// Only process React elements that have props
|
|
122
|
+
if (React.isValidElement(menuItem)) {
|
|
123
|
+
addMenuItem({
|
|
124
|
+
itemProps: {
|
|
125
|
+
id: menuItem.props.id,
|
|
126
|
+
disabled: menuItem.props.disabled,
|
|
127
|
+
menuItemProps: menuItem.props,
|
|
128
|
+
menuGroupProps: menuGroup.props,
|
|
129
|
+
},
|
|
130
|
+
allMenuItems,
|
|
131
|
+
menuItemsMap,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
133
134
|
});
|
|
134
135
|
}
|
|
135
136
|
};
|
|
@@ -269,7 +270,7 @@ export const useMenuChildren = <
|
|
|
269
270
|
});
|
|
270
271
|
children = getChildrenFromMenuItems({ menuItems, MenuItemComponent });
|
|
271
272
|
} else if (childrenProp) {
|
|
272
|
-
children = childrenProp
|
|
273
|
+
children = childrenProp;
|
|
273
274
|
setMenuItemsFromChildren({
|
|
274
275
|
children: childrenProp,
|
|
275
276
|
allMenuItems,
|
package/src/types.ts
CHANGED
|
@@ -46,10 +46,8 @@ export type TypeChildrenOrItems<
|
|
|
46
46
|
}
|
|
47
47
|
);
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
| React.ReactFragment
|
|
52
|
-
| React.ReactPortal;
|
|
49
|
+
// Type that excludes null and undefined from React.ReactNode
|
|
50
|
+
export type TypeNotEmptyChildren = Exclude<React.ReactNode, null | undefined>;
|
|
53
51
|
|
|
54
52
|
type TypeInputEventListeners =
|
|
55
53
|
| "onKeyDown"
|