@@ -2,19 +2,10 @@ import {
22 CheckIcon as MantineCheckIcon ,
33 Menu as MantineMenu ,
44} from "@mantine/core" ;
5- import { mergeRefs } from "@mantine/hooks" ;
65
76import { assertEmpty } from "@blocknote/core" ;
87import { 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
1910const 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-
14218export 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 }
0 commit comments