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):
//
//
// Required structure for Mantine:
//
//
//
//
//
//
//
//
//
//
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}
);
});