@sproutsocial/seeds-react-menu 1.3.0 → 1.3.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 -2
- package/dist/esm/index.js +29 -17
- package/dist/esm/index.js.map +1 -1
- package/dist/index.d.mts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +29 -17
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/MenuContent/MenuContent.tsx +2 -1
- package/src/MenuContext.tsx +3 -2
- package/src/MenuRoot/__tests__/MenuRoot.test.tsx +48 -1
- package/src/MenuToggleButton/MenuToggleButton.tsx +30 -11
- package/src/menuTestingUtils.tsx +1 -1
package/package.json
CHANGED
|
@@ -45,6 +45,7 @@ const MenuContent = <I extends TypeMenuItemProps = TypeMenuItemProps>({
|
|
|
45
45
|
isListbox,
|
|
46
46
|
hasVisibleItems,
|
|
47
47
|
autoCloseOnEmpty,
|
|
48
|
+
isInitialized,
|
|
48
49
|
} = useMenuContentContext();
|
|
49
50
|
const toggleButtonProps = getToggleButtonProps?.();
|
|
50
51
|
|
|
@@ -53,7 +54,7 @@ const MenuContent = <I extends TypeMenuItemProps = TypeMenuItemProps>({
|
|
|
53
54
|
? childrenProp
|
|
54
55
|
: mapMenuItems({ menuItems, MenuItemComponent });
|
|
55
56
|
|
|
56
|
-
if (!hasVisibleItems && !autoCloseOnEmpty) {
|
|
57
|
+
if (isInitialized && !hasVisibleItems && !autoCloseOnEmpty) {
|
|
57
58
|
return emptyStateElement ?? <DefaultMenuEmptyState />;
|
|
58
59
|
}
|
|
59
60
|
|
package/src/MenuContext.tsx
CHANGED
|
@@ -122,6 +122,7 @@ export interface TypeMenuContext extends TypeMenuContextProps {
|
|
|
122
122
|
props: Pick<TypeTokenInputProps, "inputProps"> &
|
|
123
123
|
Parameters<Required<TypeMenuContextProps>["getInputProps"]>[0]
|
|
124
124
|
) => Omit<TypeDownshiftMergedInputProps<TypeTokenInputProps>, "onClick">;
|
|
125
|
+
isInitialized?: boolean;
|
|
125
126
|
}
|
|
126
127
|
|
|
127
128
|
export interface TypeMenuProviderProps {
|
|
@@ -152,6 +153,7 @@ export const defaultMenuContext: TypeMenuContext = {
|
|
|
152
153
|
isItemSelected: () => false,
|
|
153
154
|
getInputComponentProps: () => ({}),
|
|
154
155
|
hasVisibleItems: false,
|
|
156
|
+
isInitialized: false,
|
|
155
157
|
};
|
|
156
158
|
|
|
157
159
|
export const getSelectedItemIds = <
|
|
@@ -253,8 +255,6 @@ export const useMenu = ({
|
|
|
253
255
|
);
|
|
254
256
|
|
|
255
257
|
const hasVisibleItems = useMemo(() => {
|
|
256
|
-
// if (!items || items.length === 0) return false;
|
|
257
|
-
// if (!rest.hiddenItemsMap) return true;
|
|
258
258
|
return (hiddenItemsCount ?? 0) < items.length;
|
|
259
259
|
}, [items, hiddenItemsCount]);
|
|
260
260
|
|
|
@@ -272,6 +272,7 @@ export const useMenu = ({
|
|
|
272
272
|
contentRef,
|
|
273
273
|
hasVisibleItems,
|
|
274
274
|
autoCloseOnEmpty,
|
|
275
|
+
isInitialized: true,
|
|
275
276
|
...menuProps,
|
|
276
277
|
};
|
|
277
278
|
};
|
|
@@ -267,8 +267,11 @@ describe("MenuRoot", () => {
|
|
|
267
267
|
const contextProps: TypeMenuContextProps = {
|
|
268
268
|
toggleMenu: () => setIsOpen(!isOpen),
|
|
269
269
|
getToggleButtonProps: (props) => ({
|
|
270
|
-
onClick: () => setIsOpen(!isOpen),
|
|
271
270
|
...props,
|
|
271
|
+
onClick: () => {
|
|
272
|
+
props.onClick?.();
|
|
273
|
+
setIsOpen(!isOpen);
|
|
274
|
+
},
|
|
272
275
|
}),
|
|
273
276
|
isOpen,
|
|
274
277
|
};
|
|
@@ -300,6 +303,50 @@ describe("MenuRoot", () => {
|
|
|
300
303
|
expect(screen.getByText(randomText)).toBeInTheDocument();
|
|
301
304
|
});
|
|
302
305
|
|
|
306
|
+
it("calls user-supplied onClick when MenuToggleButton is clicked", async () => {
|
|
307
|
+
const userOnClick = jest.fn();
|
|
308
|
+
|
|
309
|
+
const TestCustomMenu = () => {
|
|
310
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
311
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
312
|
+
// @ts-ignore - mock contextProps may be imperfect
|
|
313
|
+
const contextProps: TypeMenuContextProps = {
|
|
314
|
+
toggleMenu: () => setIsOpen(!isOpen),
|
|
315
|
+
getToggleButtonProps: (props) => ({
|
|
316
|
+
...props,
|
|
317
|
+
onClick: (e) => {
|
|
318
|
+
props.onClick?.(e);
|
|
319
|
+
setIsOpen(!isOpen);
|
|
320
|
+
},
|
|
321
|
+
}),
|
|
322
|
+
isOpen,
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
return (
|
|
326
|
+
<MenuRoot
|
|
327
|
+
menuToggleElement={
|
|
328
|
+
<MenuToggleButton onClick={userOnClick} appearance="secondary">
|
|
329
|
+
Open Menu
|
|
330
|
+
</MenuToggleButton>
|
|
331
|
+
}
|
|
332
|
+
{...testDefaultContext(contextProps)}
|
|
333
|
+
>
|
|
334
|
+
Menu Content
|
|
335
|
+
</MenuRoot>
|
|
336
|
+
);
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
await actRender(<TestCustomMenu />);
|
|
340
|
+
|
|
341
|
+
const button = screen.getByRole("button", { name: /open menu/i });
|
|
342
|
+
|
|
343
|
+
await act(async () => {
|
|
344
|
+
fireEvent.click(button);
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
expect(userOnClick).toHaveBeenCalledTimes(1);
|
|
348
|
+
});
|
|
349
|
+
|
|
303
350
|
const contextTests: {
|
|
304
351
|
child: string;
|
|
305
352
|
TestComponent: React.FC;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Button, type TypeButtonProps } from "@sproutsocial/seeds-react-button";
|
|
2
2
|
import { useMenuToggleContext } from "../MenuContext";
|
|
3
|
+
import type React from "react";
|
|
3
4
|
|
|
4
5
|
export interface TypeMenuToggleButtonProps extends TypeButtonProps {}
|
|
5
6
|
|
|
@@ -13,17 +14,35 @@ export interface TypeMenuToggleButtonProps extends TypeButtonProps {}
|
|
|
13
14
|
export const MenuToggleButton = (props: TypeMenuToggleButtonProps) => {
|
|
14
15
|
const { getToggleButtonProps, isListbox, getDropdownProps, ref, ariaProps } =
|
|
15
16
|
useMenuToggleContext();
|
|
17
|
+
|
|
18
|
+
const {
|
|
19
|
+
onClick: userOnClick,
|
|
20
|
+
onKeyDown: userOnKeyDown,
|
|
21
|
+
onBlur: userOnBlur,
|
|
22
|
+
...restProps
|
|
23
|
+
} = props;
|
|
24
|
+
|
|
25
|
+
const defaultDropdownProps = {
|
|
26
|
+
ref,
|
|
27
|
+
onClick: userOnClick,
|
|
28
|
+
onKeyDown: userOnKeyDown,
|
|
29
|
+
onBlur: userOnBlur,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const dropdownProps =
|
|
33
|
+
getDropdownProps?.({
|
|
34
|
+
...defaultDropdownProps,
|
|
35
|
+
preventKeyAction: true,
|
|
36
|
+
}) ?? defaultDropdownProps;
|
|
37
|
+
|
|
38
|
+
const toggleButtonProps = getToggleButtonProps?.({
|
|
39
|
+
...dropdownProps,
|
|
40
|
+
refKey: "innerRef",
|
|
41
|
+
...ariaProps,
|
|
42
|
+
...(isListbox ? {} : { role: "button", "aria-haspopup": "menu" }),
|
|
43
|
+
});
|
|
44
|
+
|
|
16
45
|
return (
|
|
17
|
-
<Button
|
|
18
|
-
appearance="secondary"
|
|
19
|
-
{...getToggleButtonProps?.({
|
|
20
|
-
// Decide if we want the preventKeyAction stuff. Maybe create a prop to allow for this to be overriden.
|
|
21
|
-
...(getDropdownProps?.({ ref, preventKeyAction: true }) || { ref }),
|
|
22
|
-
refKey: "innerRef",
|
|
23
|
-
...ariaProps,
|
|
24
|
-
...(isListbox ? {} : { role: "button", "aria-haspopup": "menu" }),
|
|
25
|
-
})}
|
|
26
|
-
{...props}
|
|
27
|
-
/>
|
|
46
|
+
<Button appearance="secondary" {...toggleButtonProps} {...restProps} />
|
|
28
47
|
);
|
|
29
48
|
};
|
package/src/menuTestingUtils.tsx
CHANGED
|
@@ -29,9 +29,9 @@ export const testDefaultContext = ({
|
|
|
29
29
|
// @ts-expect-error - mocked function does not match real type
|
|
30
30
|
getToggleButtonProps: jest.fn(({ ref, refKey = "ref", ...props } = {}) => ({
|
|
31
31
|
onKeyDown: jest.fn(),
|
|
32
|
-
...getToggleButtonProps?.({ ref, refKey, ...props }),
|
|
33
32
|
...(ref ? { [refKey]: ref } : {}),
|
|
34
33
|
...props,
|
|
34
|
+
...getToggleButtonProps?.({ ref, refKey, ...props }),
|
|
35
35
|
})),
|
|
36
36
|
// @ts-expect-error - mocked function does not match real type
|
|
37
37
|
getMenuProps: jest.fn((props) => props),
|