import { CheckIcon as MantineCheckIcon, Menu as MantineMenu, } from "@mantine/core"; import { mergeRefs } from "@mantine/hooks"; import { assertEmpty } from "@blocknote/core"; import { ComponentProps } from "@blocknote/react"; import { createContext, forwardRef, useCallback, useContext, useRef, useState, } from "react"; import { HiChevronRight } from "react-icons/hi"; const SubMenuContext = createContext< | { onMenuMouseOver: () => void; onMenuMouseLeave: () => void; } | undefined >(undefined); // https://github.com/orgs/mantinedev/discussions/2307 // Mantine does not officially support sub menus, so we have to use a workaround // which uses an unconventional nesting structure: // // Conventional nesting structure (used by Ariakit/ShadCN): // // // Find // // // Undo // Redo // // // Find // // // Find Next // Find Previous // // // // // // Required structure for Mantine: // // // Find // // // Undo // Redo // // // //
Find
//
// // Find Next // Find Previous // //
//
//
//
const SubMenu = forwardRef< HTMLButtonElement, ComponentProps["Generic"]["Menu"]["Root"] >((props, ref) => { const { children, onOpenChange, position, sub, // not used ...rest } = props; assertEmpty(rest); const [opened, setOpened] = useState(false); const itemRef = useRef(null); const menuCloseTimer = useRef | undefined>(); const mouseLeave = useCallback(() => { if (menuCloseTimer.current) { clearTimeout(menuCloseTimer.current); } menuCloseTimer.current = setTimeout(() => { setOpened(false); }, 250); }, []); const mouseOver = useCallback(() => { if (menuCloseTimer.current) { clearTimeout(menuCloseTimer.current); } setOpened(true); }, []); return ( onOpenChange?.(false)} onOpen={() => onOpenChange?.(true)} position={position}> {children} ); }); export const Menu = (props: ComponentProps["Generic"]["Menu"]["Root"]) => { const { children, onOpenChange, position, sub, ...rest } = props; assertEmpty(rest); if (sub) { return ; } return ( onOpenChange?.(false)} onOpen={() => onOpenChange?.(true)} position={position}> {children} ); }; export const MenuItem = forwardRef< HTMLButtonElement & HTMLDivElement, ComponentProps["Generic"]["Menu"]["Item"] >((props, ref) => { const { className, children, icon, checked, subTrigger, onClick, ...rest } = props; // false, because rest props can be added by mantine when button is used as a trigger // assertEmpty in this case is only used at typescript level, not runtime level assertEmpty(rest, false); if (subTrigger) { return (
{children}
); } return ( ) : checked === false ? (
) : null } onClick={onClick} {...rest}> {children} ); }); export const MenuTrigger = ( props: ComponentProps["Generic"]["Menu"]["Trigger"] ) => { const { children, sub, // unused ...rest } = props; assertEmpty(rest); return {children}; }; export const MenuDropdown = forwardRef< HTMLDivElement, ComponentProps["Generic"]["Menu"]["Dropdown"] >((props, ref) => { const { className, children, sub, //unused ...rest } = props; assertEmpty(rest); const ctx = useContext(SubMenuContext); return ( {children} ); }); export const MenuDivider = forwardRef< HTMLDivElement, ComponentProps["Generic"]["Menu"]["Divider"] >((props, ref) => { const { className, ...rest } = props; assertEmpty(rest); return ; }); export const MenuLabel = forwardRef< HTMLDivElement, ComponentProps["Generic"]["Menu"]["Label"] >((props, ref) => { const { className, children, ...rest } = props; assertEmpty(rest); return ( {children} ); });