/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import * as React from "react";
import { Menu as BaseMenu } from "@base-ui-components/react/menu";
import { MoreHorizontal } from "lucide-react";
import { ChevronDownIcon, ChevronRightIcon } from "../icons";
import { cn } from "../utils/classname";
import type { TMenuProps, TSubMenuProps, TMenuItemProps } from "./types";
// Context for main menu to communicate with submenus
const MenuContext = React.createContext<{
closeAllSubmenus: () => void;
registerSubmenu: (closeSubmenu: () => void) => () => void;
} | null>(null);
// SubMenu context for closing submenu from nested items
const SubMenuContext = React.createContext<{ closeSubmenu: () => void } | null>(null);
// Hook to use submenu context
const useSubMenu = () => React.useContext(SubMenuContext);
// SubMenu implementation
function SubMenu(props: TSubMenuProps) {
const { children, trigger, disabled = false, className = "" } = props;
return (
{trigger}
{children}
);
}
function MenuItem(props: TMenuItemProps) {
const { children, disabled = false, onClick, className } = props;
const submenuContext = useSubMenu();
return (
{
close();
onClick?.(e);
submenuContext?.closeSubmenu();
}}
>
{children}
);
}
function Menu(props: TMenuProps) {
const {
ariaLabel,
buttonClassName = "",
customButtonClassName = "",
customButtonTabIndex = 0,
children,
customButton,
disabled = false,
ellipsis = false,
label,
maxHeight = "md",
noBorder = false,
noChevron = false,
optionsClassName = "",
menuItemsClassName = "",
verticalEllipsis = false,
menuButtonOnClick,
onMenuClose,
tabIndex,
openOnHover = false,
handleOpenChange = () => {},
} = props;
const [isOpen, setIsOpen] = React.useState(false);
// refs
const submenuClosersRef = React.useRef void>>(new Set());
const closeAllSubmenus = React.useCallback(() => {
submenuClosersRef.current.forEach((closeSubmenu) => closeSubmenu());
}, []);
const registerSubmenu = React.useCallback((closeSubmenu: () => void) => {
submenuClosersRef.current.add(closeSubmenu);
return () => {
submenuClosersRef.current.delete(closeSubmenu);
};
}, []);
const openDropdown = () => {
setIsOpen(true);
};
const closeDropdown = React.useCallback(() => {
if (isOpen) {
closeAllSubmenus();
onMenuClose?.();
}
setIsOpen(false);
}, [isOpen, closeAllSubmenus, onMenuClose]);
const handleMenuButtonClick = (e: React.MouseEvent) => {
e.stopPropagation();
e.preventDefault();
if (isOpen) {
closeDropdown();
} else {
openDropdown();
}
if (menuButtonOnClick) menuButtonOnClick();
};
return (
{customButton ? (
{customButton}
) : (
<>
{ellipsis || verticalEllipsis ? (
) : (
{label}
{!noChevron && }
)}
>
)}
{children}
);
}
Menu.MenuItem = MenuItem;
Menu.SubMenu = SubMenu;
export { Menu };