Skip to content

Commit 52e0fd9

Browse files
committed
Updated submenu code
1 parent 589c6f2 commit 52e0fd9

File tree

2 files changed

+46
-143
lines changed

2 files changed

+46
-143
lines changed

packages/mantine/src/menu/Menu.tsx

Lines changed: 44 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,10 @@ import {
22
CheckIcon as MantineCheckIcon,
33
Menu as MantineMenu,
44
} from "@mantine/core";
5-
import { mergeRefs } from "@mantine/hooks";
65

76
import { assertEmpty } from "@blocknote/core";
87
import { ComponentProps } from "@blocknote/react";
9-
import {
10-
createContext,
11-
forwardRef,
12-
useCallback,
13-
useContext,
14-
useRef,
15-
useState,
16-
} from "react";
17-
import { HiChevronRight } from "react-icons/hi";
8+
import { createContext, forwardRef, useContext } from "react";
189

1910
const SubMenuContext = createContext<
2011
| {
@@ -24,128 +15,26 @@ const SubMenuContext = createContext<
2415
| undefined
2516
>(undefined);
2617

27-
// https://github.com/orgs/mantinedev/discussions/2307
28-
// Mantine does not officially support sub menus, so we have to use a workaround
29-
// which uses an unconventional nesting structure:
30-
//
31-
// Conventional nesting structure (used by Ariakit/ShadCN):
32-
// <Menu>
33-
// <MenuTrigger>
34-
// <MenuItem>Find</MenuItem>
35-
// </MenuTrigger>
36-
// <MenuDropdown>
37-
// <MenuItem>Undo</MenuItem>
38-
// <MenuItem>Redo</MenuItem>
39-
// <Menu>
40-
// <MenuTrigger>
41-
// <MenuItem>Find</MenuItem>
42-
// </MenuTrigger>
43-
// <MenuDropdown>
44-
// <MenuItem>Find Next</MenuItem>
45-
// <MenuItem>Find Previous</MenuItem>
46-
// </MenuDropdown>
47-
// </Menu>
48-
// </MenuDropdown>
49-
// </Menu>
50-
//
51-
// Required structure for Mantine:
52-
// <Menu>
53-
// <MenuTrigger>
54-
// <MenuItem>Find</MenuItem>
55-
// </MenuTrigger>
56-
// <MenuDropdown>
57-
// <MenuItem>Undo</MenuItem>
58-
// <MenuItem>Redo</MenuItem>
59-
// <MenuItem>
60-
// <Menu>
61-
// <MenuTrigger>
62-
// <div>Find</div>
63-
// </MenuTrigger>
64-
// <MenuDropdown>
65-
// <MenuItem>Find Next</MenuItem>
66-
// <MenuItem>Find Previous</MenuItem>
67-
// </MenuDropdown>
68-
// </Menu>
69-
// </MenuItem>
70-
// </MenuDropdown>
71-
// </Menu>
72-
const SubMenu = forwardRef<
73-
HTMLButtonElement,
74-
ComponentProps["Generic"]["Menu"]["Root"]
75-
>((props, ref) => {
76-
const {
77-
children,
78-
onOpenChange,
79-
position,
80-
sub, // not used
81-
...rest
82-
} = props;
83-
84-
assertEmpty(rest);
85-
86-
const [opened, setOpened] = useState(false);
87-
88-
const itemRef = useRef<HTMLButtonElement | null>(null);
89-
90-
const menuCloseTimer = useRef<ReturnType<typeof setTimeout> | undefined>(
91-
undefined,
92-
);
93-
94-
const mouseLeave = useCallback(() => {
95-
if (menuCloseTimer.current) {
96-
clearTimeout(menuCloseTimer.current);
97-
}
98-
menuCloseTimer.current = setTimeout(() => {
99-
setOpened(false);
100-
}, 250);
101-
}, []);
102-
103-
const mouseOver = useCallback(() => {
104-
if (menuCloseTimer.current) {
105-
clearTimeout(menuCloseTimer.current);
106-
}
107-
setOpened(true);
108-
}, []);
109-
110-
return (
111-
<SubMenuContext.Provider
112-
value={{
113-
onMenuMouseOver: mouseOver,
114-
onMenuMouseLeave: mouseLeave,
115-
}}
116-
>
117-
<MantineMenu.Item
118-
className="bn-menu-item bn-mt-sub-menu-item"
119-
ref={mergeRefs(ref, itemRef)}
120-
onMouseOver={mouseOver}
121-
onMouseLeave={mouseLeave}
122-
>
123-
<MantineMenu
124-
portalProps={{
125-
target: itemRef.current
126-
? itemRef.current.parentElement!
127-
: undefined,
128-
}}
129-
middlewares={{ flip: true, shift: true, inline: false, size: true }}
130-
trigger={"hover"}
131-
opened={opened}
132-
onChange={onOpenChange}
133-
position={position}
134-
>
135-
{children}
136-
</MantineMenu>
137-
</MantineMenu.Item>
138-
</SubMenuContext.Provider>
139-
);
140-
});
141-
14218
export const Menu = (props: ComponentProps["Generic"]["Menu"]["Root"]) => {
14319
const { children, onOpenChange, position, sub, ...rest } = props;
14420

14521
assertEmpty(rest);
14622

14723
if (sub) {
148-
return <SubMenu {...props} />;
24+
return (
25+
<MantineMenu.Sub
26+
transitionProps={{ duration: 250, exitDelay: 250 }}
27+
withinPortal={false}
28+
middlewares={{ flip: true, shift: true, inline: false, size: true }}
29+
// For some reason, `onChange` is not supported in submenus yet
30+
// `onOpen` and `onClose` are.
31+
onOpen={() => onOpenChange?.(true)}
32+
onClose={() => onOpenChange?.(false)}
33+
position={position}
34+
>
35+
{children}
36+
</MantineMenu.Sub>
37+
);
14938
}
15039

15140
return (
@@ -173,17 +62,22 @@ export const MenuItem = forwardRef<
17362

17463
if (subTrigger) {
17564
return (
176-
<div
177-
onClick={(e) => {
178-
e.preventDefault();
179-
e.stopPropagation();
180-
}}
65+
<MantineMenu.Sub.Item
66+
className={className}
18167
ref={ref}
68+
leftSection={icon}
69+
rightSection={
70+
checked ? (
71+
<MantineCheckIcon size={10} />
72+
) : checked === false ? (
73+
<div className={"bn-tick-space"} />
74+
) : null
75+
}
76+
onClick={onClick}
18277
{...rest}
18378
>
18479
{children}
185-
<HiChevronRight size={15} />
186-
</div>
80+
</MantineMenu.Sub.Item>
18781
);
18882
}
18983

@@ -218,6 +112,10 @@ export const MenuTrigger = (
218112

219113
assertEmpty(rest);
220114

115+
if (sub) {
116+
return <MantineMenu.Sub.Target>{children}</MantineMenu.Sub.Target>;
117+
}
118+
221119
return <MantineMenu.Target>{children}</MantineMenu.Target>;
222120
};
223121

@@ -236,6 +134,19 @@ export const MenuDropdown = forwardRef<
236134

237135
const ctx = useContext(SubMenuContext);
238136

137+
if (sub) {
138+
return (
139+
<MantineMenu.Sub.Dropdown
140+
className={className}
141+
ref={ref}
142+
onMouseOver={ctx?.onMenuMouseOver}
143+
onMouseLeave={ctx?.onMenuMouseLeave}
144+
>
145+
{children}
146+
</MantineMenu.Sub.Dropdown>
147+
);
148+
}
149+
239150
return (
240151
<MantineMenu.Dropdown
241152
className={className}

packages/mantine/src/style.css

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
}
3939

4040
/* Mantine Menu component base styles */
41-
.bn-mantine .mantine-Menu-dropdown {
41+
.bn-mantine .bn-menu-dropdown {
4242
background-color: var(--bn-colors-menu-background);
4343
border: var(--bn-border);
4444
border-radius: var(--bn-border-radius-medium);
@@ -69,7 +69,7 @@
6969
}
7070

7171
/* Mantine Popover component base styles */
72-
.bn-mantine .mantine-Popover-dropdown {
72+
.bn-mantine .mantine-Popover-dropdown:not(.bn-menu-dropdown) {
7373
background-color: transparent;
7474
border: none;
7575
border-radius: 0;
@@ -609,14 +609,6 @@
609609
width: 20px;
610610
}
611611

612-
.bn-mt-sub-menu-item
613-
> .mantine-Menu-itemLabel
614-
> div:not(.mantine-Menu-dropdown) {
615-
align-items: center;
616-
display: flex;
617-
justify-content: space-between;
618-
}
619-
620612
/* Comment styling */
621613
.bn-mantine .bn-thread {
622614
background-color: var(--bn-colors-menu-background);

0 commit comments

Comments
 (0)