@octopusdeploy/design-system-components
Version:
The design systems component library.
59 lines (58 loc) • 3.55 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.NestedMenu = NestedMenu;
const jsx_runtime_1 = require("react/jsx-runtime");
const css_1 = require("@emotion/css");
const design_system_icons_1 = require("@octopusdeploy/design-system-icons");
const react_1 = require("react");
const CustomMenu_1 = require("../..//Menu/CustomMenu");
const OctopusRoutingContext_1 = require("../../../routing/OctopusRoutingContext");
const useMenuState_1 = require("../../Menu/useMenuState");
const MenuItemButton_1 = require("../MenuItemButton/MenuItemButton");
const SimpleMenuItems_1 = require("../SimpleMenuItems");
function NestedMenu({ childMenuItems, label, onClose, autoFocus, compact }) {
const isUrlActive = (0, OctopusRoutingContext_1.useIsUrlActive)();
const isActive = hasActiveDescendants(childMenuItems, isUrlActive);
const [openMenu, menuState, buttonAriaAttributes] = (0, useMenuState_1.useMenuState)();
const closeMenu = menuState.onClose;
const close = (0, react_1.useCallback)(() => {
onClose?.();
closeMenu();
}, [closeMenu, onClose]);
const onKeyDown = (0, react_1.useCallback)((ev) => {
// We want to stop propagation so that navigation keydown events (like arrow up/down or home/end)
// don't bubble up to the parent menu, causing focus to change for that menu too.
// Normally, the modal also handles escape events, but we also don't want these to bubble up,
// and instead want to let our onClose call chain handle closing of all the nested menus,
// so that we have a single way of closing menus
ev.stopPropagation();
if (ev.key === "Escape") {
close();
}
}, [close]);
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(MenuItemButton_1.MenuItemButton, { isSelected: isActive, onClick: openMenu, autoFocus: autoFocus, compact: compact, menuButtonAttributes: buttonAriaAttributes, children: (0, jsx_runtime_1.jsxs)("div", { className: buttonContainerStyles, children: [(0, jsx_runtime_1.jsx)("div", { className: buttonLabelStyles, children: label }), (0, jsx_runtime_1.jsx)(design_system_icons_1.CaretRightIcon, { size: 20 })] }) }), (0, jsx_runtime_1.jsx)(CustomMenu_1.CustomMenu, { menuId: menuState.menuId, onClose: close, isOpen: menuState.isOpen, anchorElement: menuState.anchorElement, anchorOrigin: anchorOrigin, onKeyDown: onKeyDown, accessibleName: label, children: (0, jsx_runtime_1.jsx)(SimpleMenuItems_1.SimpleMenuItems, { items: childMenuItems, onClose: close, autoFocus: true, compact: compact }) })] }));
}
NestedMenu.displayName = "NestedMenu";
const anchorOrigin = { horizontal: "right", vertical: "top" };
function hasActiveDescendants(children, isUrlActive) {
const allLinks = children
.map((childMenuItem) => {
if (childMenuItem.type === "internal-link") {
return [childMenuItem];
}
return [];
//Use Infinity here when typescript properly supports it, until then we are assuming 20 levels of nested items
// is deep enough. Please see https://stackoverflow.com/questions/61420027/proper-typing-for-array-flatinfinity
})
.flat(20);
return allLinks.some((l) => isUrlActive(l.path, l.showAsActive ?? "if path partially matches"));
}
const buttonContainerStyles = (0, css_1.css)({
display: "flex",
alignItems: "center",
width: "100%",
textAlign: "left",
});
const buttonLabelStyles = (0, css_1.css)({
flexGrow: 1,
});