@geotab/zenith 3.10.0 → 3.11.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/dist/index.css +8 -1
- package/dist/menu/components/controlledMenuList/controlledMenuList.d.ts +27 -0
- package/dist/menu/components/controlledMenuList/controlledMenuList.js +123 -0
- package/dist/menu/components/createControlledMenuList.d.ts +37 -0
- package/dist/menu/components/createControlledMenuList.js +55 -0
- package/dist/menu/components/createMenuItem.d.ts +67 -0
- package/dist/menu/components/createMenuItem.js +97 -0
- package/dist/menu/components/menuButton.js +8 -2
- package/dist/menu/components/menuItem.d.ts +1 -2
- package/dist/menu/components/menuItem.js +20 -74
- package/dist/menu/contexts/usePathContext.d.ts +2 -0
- package/dist/menu/contexts/usePathContext.js +9 -0
- package/dist/menu/controlledMenu.js +8 -175
- package/dist/menu/utils/buildMenuContent.d.ts +2 -0
- package/dist/menu/utils/buildMenuContent.js +38 -0
- package/dist/menu/utils/findContent.d.ts +2 -2
- package/dist/menu/utils/findContent.js +4 -3
- package/dist/menu/utils/getItemLabel.d.ts +2 -0
- package/dist/menu/utils/getItemLabel.js +8 -0
- package/dist/menu/utils/getSafeRel.d.ts +1 -0
- package/dist/menu/utils/getSafeRel.js +14 -0
- package/dist/menu/utils/isMenuItem.d.ts +2 -0
- package/dist/menu/utils/isMenuItem.js +13 -0
- package/dist/menu/utils/isSafeHref.d.ts +1 -0
- package/dist/menu/utils/isSafeHref.js +10 -0
- package/dist/menu/utils/normalizeSeparators.d.ts +2 -0
- package/dist/menu/utils/normalizeSeparators.js +23 -0
- package/dist/menu/utils/resolveKeys.d.ts +12 -0
- package/dist/menu/utils/resolveKeys.js +22 -0
- package/dist/menu/utils/useLastValidSheet.d.ts +7 -0
- package/dist/menu/utils/useLastValidSheet.js +30 -0
- package/dist/menu/utils/useMenuItemCore.d.ts +31 -0
- package/dist/menu/utils/useMenuItemCore.js +51 -0
- package/dist/menu/utils/useMenuItemKeyboardNav.d.ts +2 -0
- package/dist/menu/utils/useMenuItemKeyboardNav.js +15 -0
- package/dist/menu/utils/useMenuListKeyboardNav.d.ts +12 -0
- package/dist/menu/utils/useMenuListKeyboardNav.js +77 -0
- package/dist/menu/utils/useMenuPath.d.ts +6 -0
- package/dist/menu/utils/useMenuPath.js +35 -0
- package/dist/nav/navItem/navItem.js +6 -4
- package/dist/nav/navSection/navSection.js +7 -5
- package/esm/menu/components/controlledMenuList/controlledMenuList.d.ts +27 -0
- package/esm/menu/components/controlledMenuList/controlledMenuList.js +120 -0
- package/esm/menu/components/createControlledMenuList.d.ts +37 -0
- package/esm/menu/components/createControlledMenuList.js +51 -0
- package/esm/menu/components/createMenuItem.d.ts +67 -0
- package/esm/menu/components/createMenuItem.js +93 -0
- package/esm/menu/components/menuButton.js +8 -2
- package/esm/menu/components/menuItem.d.ts +1 -2
- package/esm/menu/components/menuItem.js +20 -74
- package/esm/menu/contexts/usePathContext.d.ts +2 -0
- package/esm/menu/contexts/usePathContext.js +5 -0
- package/esm/menu/controlledMenu.js +10 -177
- package/esm/menu/utils/buildMenuContent.d.ts +2 -0
- package/esm/menu/utils/buildMenuContent.js +34 -0
- package/esm/menu/utils/findContent.d.ts +2 -2
- package/esm/menu/utils/findContent.js +4 -3
- package/esm/menu/utils/getItemLabel.d.ts +2 -0
- package/esm/menu/utils/getItemLabel.js +4 -0
- package/esm/menu/utils/getSafeRel.d.ts +1 -0
- package/esm/menu/utils/getSafeRel.js +10 -0
- package/esm/menu/utils/isMenuItem.d.ts +2 -0
- package/esm/menu/utils/isMenuItem.js +9 -0
- package/esm/menu/utils/isSafeHref.d.ts +1 -0
- package/esm/menu/utils/isSafeHref.js +6 -0
- package/esm/menu/utils/normalizeSeparators.d.ts +2 -0
- package/esm/menu/utils/normalizeSeparators.js +19 -0
- package/esm/menu/utils/resolveKeys.d.ts +12 -0
- package/esm/menu/utils/resolveKeys.js +18 -0
- package/esm/menu/utils/useLastValidSheet.d.ts +7 -0
- package/esm/menu/utils/useLastValidSheet.js +26 -0
- package/esm/menu/utils/useMenuItemCore.d.ts +31 -0
- package/esm/menu/utils/useMenuItemCore.js +47 -0
- package/esm/menu/utils/useMenuItemKeyboardNav.d.ts +2 -0
- package/esm/menu/utils/useMenuItemKeyboardNav.js +11 -0
- package/esm/menu/utils/useMenuListKeyboardNav.d.ts +12 -0
- package/esm/menu/utils/useMenuListKeyboardNav.js +73 -0
- package/esm/menu/utils/useMenuPath.d.ts +6 -0
- package/esm/menu/utils/useMenuPath.js +31 -0
- package/esm/nav/navItem/navItem.js +6 -4
- package/esm/nav/navSection/navSection.js +7 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -47,6 +47,12 @@ Zenith library provides components defined in Zenith Design System. It includes
|
|
|
47
47
|
|
|
48
48
|
## Change log
|
|
49
49
|
|
|
50
|
+
### 3.11.0
|
|
51
|
+
|
|
52
|
+
- Add `ControlledMenuList` component and `createMenuItem`/`createControlledMenuList` factories for typed, path-based menu navigation with keyboard support
|
|
53
|
+
- Improve `NavItem` keyboard navigation
|
|
54
|
+
- Fix gap for `Nav` component
|
|
55
|
+
|
|
50
56
|
### 3.10.0
|
|
51
57
|
|
|
52
58
|
- Add `wrap` and `vertical` props to `ListItem`
|
package/dist/index.css
CHANGED
|
@@ -3433,6 +3433,11 @@ html:lang(ar) .zen-menu-button__action--drive-tablet {
|
|
|
3433
3433
|
.zen-menu-item__content {
|
|
3434
3434
|
list-style-type: none;
|
|
3435
3435
|
}
|
|
3436
|
+
.zen-menu-item--horizontal {
|
|
3437
|
+
display: flex;
|
|
3438
|
+
flex-direction: row;
|
|
3439
|
+
gap: 8px;
|
|
3440
|
+
}
|
|
3436
3441
|
.zen-popup {
|
|
3437
3442
|
font-family: var(--main-font);
|
|
3438
3443
|
font-size: 14px;
|
|
@@ -3480,6 +3485,8 @@ html:lang(ar) .zen-popup {
|
|
|
3480
3485
|
}
|
|
3481
3486
|
}
|
|
3482
3487
|
.zen-menu-separator {
|
|
3488
|
+
border-block-start: 1px solid var(--borders-general);
|
|
3489
|
+
list-style-type: none;
|
|
3483
3490
|
border-top: 1px solid var(--borders-general);
|
|
3484
3491
|
height: 0;
|
|
3485
3492
|
}
|
|
@@ -17799,7 +17806,7 @@ html:lang(ar) .zen-nav-item__title-text {
|
|
|
17799
17806
|
.zen-nav-item__content-left {
|
|
17800
17807
|
display: flex;
|
|
17801
17808
|
align-items: center;
|
|
17802
|
-
gap:
|
|
17809
|
+
gap: 8px;
|
|
17803
17810
|
flex: 1 1 auto;
|
|
17804
17811
|
min-width: 0;
|
|
17805
17812
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { FC, ForwardRefExoticComponent, RefAttributes, ReactNode } from "react";
|
|
2
|
+
import { IMenuControlledItem } from "../menuItem";
|
|
3
|
+
import { IZenComponentProps } from "../../../commonHelpers/zenComponent";
|
|
4
|
+
import { IMenuSeparator } from "../menuSeparator";
|
|
5
|
+
import { TAlignment } from "../../../absolute/absolute";
|
|
6
|
+
export type TMenuListDirection = "vertical" | "horizontal";
|
|
7
|
+
export interface IControlledMenuList extends IZenComponentProps {
|
|
8
|
+
listClassName?: string;
|
|
9
|
+
ariaLabel?: string;
|
|
10
|
+
/** Default tooltip alignment injected into MenuItem children that don't specify their own. */
|
|
11
|
+
defaultTooltipAlignment?: TAlignment;
|
|
12
|
+
/** Default submenu alignment injected into MenuItem children that don't specify their own. */
|
|
13
|
+
defaultAlignment?: TAlignment;
|
|
14
|
+
/** Layout direction of the menu list. Defaults to "vertical". */
|
|
15
|
+
direction?: TMenuListDirection;
|
|
16
|
+
}
|
|
17
|
+
/** Internal props injected by ControlledMenu — not part of the public API. */
|
|
18
|
+
export interface IControlledMenuListInternal extends IControlledMenuList {
|
|
19
|
+
isOpen?: boolean;
|
|
20
|
+
setIsOpen?: (v: boolean) => void;
|
|
21
|
+
}
|
|
22
|
+
export declare const ControlledMenuList: ForwardRefExoticComponent<IControlledMenuList & {
|
|
23
|
+
children?: ReactNode;
|
|
24
|
+
} & RefAttributes<HTMLDivElement>> & {
|
|
25
|
+
Item: FC<IMenuControlledItem>;
|
|
26
|
+
Separator: FC<IMenuSeparator>;
|
|
27
|
+
};
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ControlledMenuList = void 0;
|
|
4
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
/* eslint-disable @typescript-eslint/naming-convention */
|
|
6
|
+
const react_1 = require("react");
|
|
7
|
+
const menuItem_1 = require("../menuItem");
|
|
8
|
+
const classNames_1 = require("../../../commonHelpers/classNames/classNames");
|
|
9
|
+
const findContent_1 = require("../../utils/findContent");
|
|
10
|
+
const getItemLabel_1 = require("../../utils/getItemLabel");
|
|
11
|
+
const normalizeSeparators_1 = require("../../utils/normalizeSeparators");
|
|
12
|
+
const useMenuPath_1 = require("../../utils/useMenuPath");
|
|
13
|
+
const useLastValidSheet_1 = require("../../utils/useLastValidSheet");
|
|
14
|
+
const useMenuListKeyboardNav_1 = require("../../utils/useMenuListKeyboardNav");
|
|
15
|
+
const menuButton_1 = require("../menuButton");
|
|
16
|
+
const iconArrowLeft_1 = require("../../../icons/iconArrowLeft");
|
|
17
|
+
const deviceType_1 = require("../../../commonHelpers/hooks/deviceType");
|
|
18
|
+
const useDeviceType_1 = require("../../../commonHelpers/hooks/useDeviceType");
|
|
19
|
+
const generateId_1 = require("../../../commonHelpers/generateId");
|
|
20
|
+
const pathProvider_1 = require("../../contexts/pathProvider");
|
|
21
|
+
const mobileSheet_1 = require("../../../mobileSheet/mobileSheet");
|
|
22
|
+
const menuSeparator_1 = require("../menuSeparator");
|
|
23
|
+
const ControlledMenuListBase = (0, react_1.forwardRef)(({ children, setIsOpen, isOpen, className = "", listClassName = "", ariaLabel, defaultTooltipAlignment, defaultAlignment, direction = "vertical" }, ref) => {
|
|
24
|
+
const [deviceType, setDeviceType] = (0, react_1.useState)(deviceType_1.DeviceType.Desktop);
|
|
25
|
+
const isMobile = deviceType === deviceType_1.DeviceType.Mobile;
|
|
26
|
+
(0, useDeviceType_1.useDeviceType)(setDeviceType);
|
|
27
|
+
// True when mounted inside ControlledMenu, which injects setIsOpen and isOpen.
|
|
28
|
+
// Standalone usage leaves both undefined.
|
|
29
|
+
const isEmbedded = setIsOpen !== undefined;
|
|
30
|
+
const { path, onOpenBranch, closeBranch, closeAll } = (0, useMenuPath_1.useMenuPath)(isOpen);
|
|
31
|
+
const navigatedViaKeyboardRef = (0, react_1.useRef)(false);
|
|
32
|
+
const keyboardActiveRef = (0, react_1.useRef)(false);
|
|
33
|
+
const internalRef = (0, react_1.useRef)(null);
|
|
34
|
+
const divRefCallback = (0, react_1.useCallback)((node) => {
|
|
35
|
+
internalRef.current = node;
|
|
36
|
+
if (typeof ref === "function") {
|
|
37
|
+
ref(node);
|
|
38
|
+
}
|
|
39
|
+
else if (ref) {
|
|
40
|
+
ref.current = node;
|
|
41
|
+
}
|
|
42
|
+
}, [ref]);
|
|
43
|
+
const effectiveSetIsOpen = (0, react_1.useCallback)((v) => {
|
|
44
|
+
setIsOpen === null || setIsOpen === void 0 ? void 0 : setIsOpen(v);
|
|
45
|
+
if (!v && !isEmbedded) {
|
|
46
|
+
closeAll();
|
|
47
|
+
}
|
|
48
|
+
}, [setIsOpen, closeAll, isEmbedded]);
|
|
49
|
+
const buildListItems = (0, react_1.useCallback)((childrenToProcess) => {
|
|
50
|
+
var _a;
|
|
51
|
+
const cont = [];
|
|
52
|
+
// If children is a Fragment, extract its children
|
|
53
|
+
let actualChildren = childrenToProcess;
|
|
54
|
+
if ((0, react_1.isValidElement)(childrenToProcess) && childrenToProcess.type === react_1.Fragment) {
|
|
55
|
+
actualChildren = (_a = childrenToProcess.props.children) !== null && _a !== void 0 ? _a : [];
|
|
56
|
+
}
|
|
57
|
+
react_1.Children.map(actualChildren, (child) => {
|
|
58
|
+
var _a, _b;
|
|
59
|
+
if (!child)
|
|
60
|
+
return;
|
|
61
|
+
if (typeof child === "string") {
|
|
62
|
+
cont.push((0, jsx_runtime_1.jsx)("li", { role: "presentation", className: (0, classNames_1.classNames)(["zen-menu-item__content"]), children: child }, (0, generateId_1.generateId)()));
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if ((0, react_1.isValidElement)(child) && (0, menuSeparator_1.isSeparator)(child)) {
|
|
66
|
+
cont.push((0, react_1.cloneElement)(child, { key: (_a = child.key) !== null && _a !== void 0 ? _a : (0, generateId_1.generateId)() }));
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
if ((0, react_1.isValidElement)(child) && (0, menuItem_1.isMenuItem)(child)) {
|
|
70
|
+
const childProps = child.props;
|
|
71
|
+
cont.push((0, react_1.cloneElement)(child, {
|
|
72
|
+
isMobile,
|
|
73
|
+
key: (_b = childProps.id) !== null && _b !== void 0 ? _b : (0, generateId_1.generateId)(),
|
|
74
|
+
setIsOpen: effectiveSetIsOpen,
|
|
75
|
+
onClick: childProps.onClick,
|
|
76
|
+
tooltipAlignment: childProps.tooltipAlignment || defaultTooltipAlignment || undefined,
|
|
77
|
+
alignment: childProps.alignment || defaultAlignment || undefined
|
|
78
|
+
}));
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const childProps = child.props;
|
|
82
|
+
cont.push((0, jsx_runtime_1.jsx)("li", { className: (0, classNames_1.classNames)(["zen-menu-item__content"]), role: "presentation", children: child }, childProps.id || childProps["data-id"] || (0, generateId_1.generateId)()));
|
|
83
|
+
});
|
|
84
|
+
return (0, normalizeSeparators_1.normalizeSeparators)(cont);
|
|
85
|
+
}, [isMobile, effectiveSetIsOpen, defaultTooltipAlignment, defaultAlignment]);
|
|
86
|
+
// Embedded (ControlledMenu): switch content based on path so the outer MobileSheet
|
|
87
|
+
// shows nested items with the back button as a list row.
|
|
88
|
+
// Standalone: always show top-level content; nested navigation uses its own MobileSheet.
|
|
89
|
+
const [content, parent] = (0, react_1.useMemo)(() => {
|
|
90
|
+
if (isEmbedded && isMobile && path.length > 0) {
|
|
91
|
+
const el = (0, findContent_1.findContent)(children, menuItem_1.isMenuItem, path[path.length - 1]);
|
|
92
|
+
if (el && (0, react_1.isValidElement)(el)) {
|
|
93
|
+
return [buildListItems(el.props.children), el];
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return [buildListItems(children), null];
|
|
97
|
+
}, [children, isMobile, path, isEmbedded, buildListItems]);
|
|
98
|
+
// Standalone mobile only: compute nested content for the inline MobileSheet.
|
|
99
|
+
const [nestedContent, nestedParent] = (0, react_1.useMemo)(() => {
|
|
100
|
+
if (isEmbedded || !isMobile || path.length === 0) {
|
|
101
|
+
return [null, null];
|
|
102
|
+
}
|
|
103
|
+
const el = (0, findContent_1.findContent)(children, menuItem_1.isMenuItem, path[path.length - 1]);
|
|
104
|
+
if (!el || !(0, react_1.isValidElement)(el)) {
|
|
105
|
+
return [null, null];
|
|
106
|
+
}
|
|
107
|
+
return [buildListItems(el.props.children), el];
|
|
108
|
+
}, [children, isMobile, path, isEmbedded, buildListItems]);
|
|
109
|
+
const isHorizontal = direction === "horizontal";
|
|
110
|
+
const { onKeyDown, onKeyDownVertical, onMouseDown } = (0, useMenuListKeyboardNav_1.useMenuListKeyboardNav)(keyboardActiveRef, navigatedViaKeyboardRef, isHorizontal);
|
|
111
|
+
const renderList = (listContent, backParent, listRef, applyDirection = true, keyDownHandler = onKeyDown) => ((0, jsx_runtime_1.jsx)("div", { ref: listRef, tabIndex: -1, onKeyDown: keyDownHandler, onMouseDown: onMouseDown, className: (0, classNames_1.classNames)(["zen-action-list", className]), children: (0, jsx_runtime_1.jsxs)("ul", { role: "menu", "aria-label": ariaLabel, className: (0, classNames_1.classNames)([
|
|
112
|
+
"zen-menu-item",
|
|
113
|
+
className,
|
|
114
|
+
listClassName,
|
|
115
|
+
applyDirection && direction === "horizontal" ? "zen-menu-item--horizontal" : ""
|
|
116
|
+
]), children: [backParent ? ((0, jsx_runtime_1.jsx)(menuButton_1.MenuButton, { id: "root", name: (0, getItemLabel_1.getItemLabel)(backParent), icon: iconArrowLeft_1.IconArrowLeft, onClick: closeBranch, hasChildren: false, disabled: false }, "root")) : null, listContent] }) }));
|
|
117
|
+
const { sheetContent, sheetParent, sheetPathLength, sheetParentName } = (0, useLastValidSheet_1.useLastValidSheet)(nestedContent, nestedParent, path, children);
|
|
118
|
+
return ((0, jsx_runtime_1.jsxs)(pathProvider_1.PathProvider, { path: path, onOpenBranch: onOpenBranch, closeBranch: closeBranch, navigatedViaKeyboardRef: navigatedViaKeyboardRef, keyboardActiveRef: keyboardActiveRef, children: [renderList(content, parent, divRefCallback), !isEmbedded && isMobile && ((0, jsx_runtime_1.jsxs)(mobileSheet_1.MobileSheet, { label: sheetParentName, isOpen: path.length > 0, triggerRef: internalRef, onHidePanel: closeAll, onCloseClick: closeAll, children: [(0, jsx_runtime_1.jsx)(mobileSheet_1.MobileSheet.Title, { children: sheetParentName }), (0, jsx_runtime_1.jsx)(mobileSheet_1.MobileSheet.Content, { children: renderList(sheetContent, sheetPathLength > 1 ? sheetParent : null, undefined, false, onKeyDownVertical) })] }))] }));
|
|
119
|
+
});
|
|
120
|
+
ControlledMenuListBase.displayName = "ControlledMenuList";
|
|
121
|
+
exports.ControlledMenuList = ControlledMenuListBase;
|
|
122
|
+
exports.ControlledMenuList.Item = menuItem_1.MenuItem;
|
|
123
|
+
exports.ControlledMenuList.Separator = menuSeparator_1.MenuSeparator;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { FC, ReactNode } from "react";
|
|
2
|
+
import { IControlledMenuListInternal } from "./controlledMenuList/controlledMenuList";
|
|
3
|
+
import { IMenuControlledItem } from "./menuItem";
|
|
4
|
+
import { IMenuSeparator } from "./menuSeparator";
|
|
5
|
+
export interface ICreateControlledMenuListRenderProps {
|
|
6
|
+
children: ReactNode;
|
|
7
|
+
}
|
|
8
|
+
type CreatedListComponent<T> = FC<T & IControlledMenuListInternal & {
|
|
9
|
+
children?: ReactNode;
|
|
10
|
+
}> & {
|
|
11
|
+
Item: FC<IMenuControlledItem>;
|
|
12
|
+
Separator: FC<IMenuSeparator>;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Factory that creates a typed ControlledMenuList component with custom props.
|
|
16
|
+
*
|
|
17
|
+
* The `renderContent` function receives all consumer props plus `children` (the built ControlledMenuList element),
|
|
18
|
+
* allowing the consumer to wrap the list with custom markup.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* interface INavSection { sectionTitle: string }
|
|
22
|
+
*
|
|
23
|
+
* const NavSection = createControlledMenuList<INavSection>(({ sectionTitle, children }) => (
|
|
24
|
+
* <div>
|
|
25
|
+
* <div className="section-header">{sectionTitle}</div>
|
|
26
|
+
* {children}
|
|
27
|
+
* </div>
|
|
28
|
+
* ));
|
|
29
|
+
*
|
|
30
|
+
* // Usage — accepts INavSection + IControlledMenuList props:
|
|
31
|
+
* <NavSection sectionTitle="Reports" setIsOpen={setIsOpen} isOpen={isOpen}>
|
|
32
|
+
* <NavSection.Item id="daily" name="Daily Reports" />
|
|
33
|
+
* <NavSection.Item id="weekly" name="Weekly Reports" />
|
|
34
|
+
* </NavSection>
|
|
35
|
+
*/
|
|
36
|
+
export declare function createControlledMenuList<T extends object>(renderContent: (props: T & ICreateControlledMenuListRenderProps) => ReactNode): CreatedListComponent<T>;
|
|
37
|
+
export {};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
3
|
+
var t = {};
|
|
4
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
5
|
+
t[p] = s[p];
|
|
6
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
7
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
8
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
9
|
+
t[p[i]] = s[p[i]];
|
|
10
|
+
}
|
|
11
|
+
return t;
|
|
12
|
+
};
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.createControlledMenuList = void 0;
|
|
15
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
16
|
+
const controlledMenuList_1 = require("./controlledMenuList/controlledMenuList");
|
|
17
|
+
const menuItem_1 = require("./menuItem");
|
|
18
|
+
const menuSeparator_1 = require("./menuSeparator");
|
|
19
|
+
/**
|
|
20
|
+
* Factory that creates a typed ControlledMenuList component with custom props.
|
|
21
|
+
*
|
|
22
|
+
* The `renderContent` function receives all consumer props plus `children` (the built ControlledMenuList element),
|
|
23
|
+
* allowing the consumer to wrap the list with custom markup.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* interface INavSection { sectionTitle: string }
|
|
27
|
+
*
|
|
28
|
+
* const NavSection = createControlledMenuList<INavSection>(({ sectionTitle, children }) => (
|
|
29
|
+
* <div>
|
|
30
|
+
* <div className="section-header">{sectionTitle}</div>
|
|
31
|
+
* {children}
|
|
32
|
+
* </div>
|
|
33
|
+
* ));
|
|
34
|
+
*
|
|
35
|
+
* // Usage — accepts INavSection + IControlledMenuList props:
|
|
36
|
+
* <NavSection sectionTitle="Reports" setIsOpen={setIsOpen} isOpen={isOpen}>
|
|
37
|
+
* <NavSection.Item id="daily" name="Daily Reports" />
|
|
38
|
+
* <NavSection.Item id="weekly" name="Weekly Reports" />
|
|
39
|
+
* </NavSection>
|
|
40
|
+
*/
|
|
41
|
+
function createControlledMenuList(renderContent) {
|
|
42
|
+
const CreatedList = allProps => {
|
|
43
|
+
const { setIsOpen, listClassName, isOpen, defaultTooltipAlignment, defaultAlignment, direction, className, children } = allProps, rest = __rest(allProps, ["setIsOpen", "listClassName", "isOpen", "defaultTooltipAlignment", "defaultAlignment", "direction", "className", "children"]);
|
|
44
|
+
// Cast required to pass internal props (isOpen, setIsOpen) not in the public interface.
|
|
45
|
+
const InternalList = controlledMenuList_1.ControlledMenuList;
|
|
46
|
+
const list = ((0, jsx_runtime_1.jsx)(InternalList, { setIsOpen: setIsOpen, listClassName: listClassName, isOpen: isOpen, defaultTooltipAlignment: defaultTooltipAlignment, defaultAlignment: defaultAlignment, direction: direction, className: className, children: children }));
|
|
47
|
+
return renderContent(Object.assign(Object.assign({}, rest), { children: list }));
|
|
48
|
+
};
|
|
49
|
+
CreatedList.displayName = "ControlledMenuList";
|
|
50
|
+
const result = CreatedList;
|
|
51
|
+
result.Item = menuItem_1.MenuItem;
|
|
52
|
+
result.Separator = menuSeparator_1.MenuSeparator;
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
55
|
+
exports.createControlledMenuList = createControlledMenuList;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { ReactNode, ForwardRefExoticComponent, RefAttributes, ForwardedRef } from "react";
|
|
2
|
+
import { TAlignment } from "../../absolute/absolute";
|
|
3
|
+
import { IMenuItem } from "./menuItem";
|
|
4
|
+
import "./menuButton.less";
|
|
5
|
+
export interface ICreateMenuItemRenderProps {
|
|
6
|
+
hasChildren: boolean;
|
|
7
|
+
isOpen: boolean;
|
|
8
|
+
isMobile: boolean;
|
|
9
|
+
}
|
|
10
|
+
export interface ICreateMenuItemWrapperProps {
|
|
11
|
+
role: "presentation";
|
|
12
|
+
className: string;
|
|
13
|
+
tabIndex?: number;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Factory that creates a typed Menu item component with full MenuItem functionality
|
|
17
|
+
* (nested submenus, keyboard navigation, mobile sheet support), but with a
|
|
18
|
+
* consumer-defined typed interface instead of the fixed MenuItem props.
|
|
19
|
+
*
|
|
20
|
+
* The `renderTrigger` function receives all consumer props plus `hasChildren` and
|
|
21
|
+
* `isOpen` so the trigger content can reflect submenu state.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* interface INavItem { label: string; count?: number }
|
|
25
|
+
*
|
|
26
|
+
* const NavMenuItem = createMenuItem<INavItem>(({ label, count, hasChildren, isOpen }) => (
|
|
27
|
+
* <>
|
|
28
|
+
* <span>{label}</span>
|
|
29
|
+
* {count !== undefined && <Badge>{count}</Badge>}
|
|
30
|
+
* {hasChildren && <ChevronRight rotated={isOpen} />}
|
|
31
|
+
* </>
|
|
32
|
+
* ));
|
|
33
|
+
*
|
|
34
|
+
* // Usage — supports nesting exactly like Menu.Item:
|
|
35
|
+
* <Menu title="Nav">
|
|
36
|
+
* <NavMenuItem id="reports" label="Reports" count={3}>
|
|
37
|
+
* <Menu.Item id="charts" name="Charts" />
|
|
38
|
+
* <Menu.Item id="tables" name="Tables" />
|
|
39
|
+
* </NavMenuItem>
|
|
40
|
+
* </Menu>
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* // renderWrapper: replace the default <li> with a custom host element
|
|
44
|
+
* <NavMenuItem
|
|
45
|
+
* id="settings"
|
|
46
|
+
* label="Settings"
|
|
47
|
+
* renderWrapper={(children, ref, defaultProps) => (
|
|
48
|
+
* <li ref={ref} {...defaultProps} data-testid="nav-settings">
|
|
49
|
+
* {children}
|
|
50
|
+
* </li>
|
|
51
|
+
* )}
|
|
52
|
+
* />
|
|
53
|
+
*/
|
|
54
|
+
type PublicMenuItemProps<T> = T & Pick<IMenuItem, "id" | "children" | "className" | "alignment" | "disabled" | "onClick" | "link" | "target" | "rel"> & {
|
|
55
|
+
ariaLabel?: string;
|
|
56
|
+
onOpenChange?: (isOpen: boolean) => void;
|
|
57
|
+
renderWrapper?: (children: ReactNode, ref: ForwardedRef<HTMLLIElement>, defaultProps: ICreateMenuItemWrapperProps) => ReactNode;
|
|
58
|
+
tabIndex?: number;
|
|
59
|
+
tooltip?: ReactNode;
|
|
60
|
+
tooltipAlignment?: TAlignment;
|
|
61
|
+
};
|
|
62
|
+
export interface ICreateMenuItemOptions<T> {
|
|
63
|
+
/** Hook-compatible function called during render to derive a default tooltip from item props. Can call useContext. */
|
|
64
|
+
renderTooltip?: (props: T) => ReactNode | undefined;
|
|
65
|
+
}
|
|
66
|
+
export declare function createMenuItem<T extends object>(renderTrigger: (props: T & ICreateMenuItemRenderProps) => ReactNode, options?: ICreateMenuItemOptions<T>): ForwardRefExoticComponent<PublicMenuItemProps<T> & RefAttributes<HTMLLIElement>>;
|
|
67
|
+
export {};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
3
|
+
var t = {};
|
|
4
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
5
|
+
t[p] = s[p];
|
|
6
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
7
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
8
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
9
|
+
t[p[i]] = s[p[i]];
|
|
10
|
+
}
|
|
11
|
+
return t;
|
|
12
|
+
};
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.createMenuItem = void 0;
|
|
15
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
16
|
+
const react_1 = require("react");
|
|
17
|
+
const controlledPopup_1 = require("../../controlledPopup/controlledPopup");
|
|
18
|
+
const classNames_1 = require("../../commonHelpers/classNames/classNames");
|
|
19
|
+
const useMenuItemCore_1 = require("../utils/useMenuItemCore");
|
|
20
|
+
const tooltip_1 = require("../../tooltip/tooltip");
|
|
21
|
+
const isSafeHref_1 = require("../utils/isSafeHref");
|
|
22
|
+
const getSafeRel_1 = require("../utils/getSafeRel");
|
|
23
|
+
const useDriveClassName_1 = require("../../utils/theme/useDriveClassName");
|
|
24
|
+
function createMenuItem(renderTrigger, options) {
|
|
25
|
+
var _a;
|
|
26
|
+
// Defined at factory level so it is always the same function reference —
|
|
27
|
+
// safe to call unconditionally inside the component (rules of hooks).
|
|
28
|
+
const resolveDefaultTooltip = (_a = options === null || options === void 0 ? void 0 : options.renderTooltip) !== null && _a !== void 0 ? _a : (() => undefined);
|
|
29
|
+
const CreatedItem = (0, react_1.forwardRef)((allProps, containerRef) => {
|
|
30
|
+
const { id, children, className, alignment, disabled, isMobile = false, setIsOpen, ariaLabel, onClick, onOpenChange, link, target, rel, renderWrapper, tabIndex, tooltip, tooltipAlignment } = allProps, rest = __rest(allProps, ["id", "children", "className", "alignment", "disabled", "isMobile", "setIsOpen", "ariaLabel", "onClick", "onOpenChange", "link", "target", "rel", "renderWrapper", "tabIndex", "tooltip", "tooltipAlignment"]);
|
|
31
|
+
const linkEvents = (0, react_1.useMemo)(() => ({
|
|
32
|
+
onKeyDown: (e) => {
|
|
33
|
+
if (e.key === " ") {
|
|
34
|
+
e.preventDefault();
|
|
35
|
+
const linkEl = e.currentTarget;
|
|
36
|
+
linkEl.dataset.spaceDown = "1";
|
|
37
|
+
linkEl.classList.add("zen-menu-button__action--active");
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
onKeyUp: (e) => {
|
|
41
|
+
if (e.key === " ") {
|
|
42
|
+
e.preventDefault();
|
|
43
|
+
const linkEl = e.currentTarget;
|
|
44
|
+
linkEl.classList.remove("zen-menu-button__action--active");
|
|
45
|
+
if (linkEl.dataset.spaceDown) {
|
|
46
|
+
delete linkEl.dataset.spaceDown;
|
|
47
|
+
linkEl.click();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}), []);
|
|
52
|
+
const { ref, isOpen, hasChildren, content, openedViaKeyboard, contentAlignment, path, handleDesktopActionClick, handleTriggerClick, handleOpenChange } = (0, useMenuItemCore_1.useMenuItemCore)({ id, children, className, alignment, isMobile, setIsOpen, onClick, onOpenChange });
|
|
53
|
+
const driveClass = (0, useDriveClassName_1.useDriveClassName)("zen-menu-button__action");
|
|
54
|
+
const liDefaultProps = Object.assign({ role: "presentation", className: (0, classNames_1.classNames)(["zen-menu-button", className !== null && className !== void 0 ? className : ""]) }, (tabIndex !== undefined && { tabIndex }));
|
|
55
|
+
// Resolved at component top level so renderTooltip can safely call hooks (e.g. useContext).
|
|
56
|
+
const defaultTooltip = resolveDefaultTooltip(Object.assign(Object.assign({}, rest), { isMobile }));
|
|
57
|
+
const effectiveTooltip = tooltip !== null && tooltip !== void 0 ? tooltip : defaultTooltip;
|
|
58
|
+
const effectiveTooltipAlignment = tooltipAlignment;
|
|
59
|
+
const wrapButton = (button) => {
|
|
60
|
+
// On mobile, tooltip wraps the <li> instead of the button so Tooltip doesn't
|
|
61
|
+
// intercept the button's onClick and shows on tap instead.
|
|
62
|
+
// Items with children are excluded — wrapping <li> would fire both the tooltip and
|
|
63
|
+
// the submenu drill-in on the same tap.
|
|
64
|
+
const isMobileTooltip = isMobile && !!effectiveTooltip && !hasChildren;
|
|
65
|
+
const wrappedButton = effectiveTooltip && !isMobileTooltip ? ((0, jsx_runtime_1.jsx)(tooltip_1.Tooltip, { trigger: button, alignment: effectiveTooltipAlignment, children: effectiveTooltip })) : (button);
|
|
66
|
+
const liElement = renderWrapper ? (renderWrapper(wrappedButton, containerRef, liDefaultProps)) : ((0, jsx_runtime_1.jsx)("li", Object.assign({ ref: containerRef }, liDefaultProps, { tabIndex: tabIndex, children: wrappedButton })));
|
|
67
|
+
// Wrap li with Tooltip on mobile
|
|
68
|
+
return isMobileTooltip ? ((0, jsx_runtime_1.jsx)(tooltip_1.Tooltip, { trigger: liElement, alignment: effectiveTooltipAlignment, children: effectiveTooltip })) : (liElement);
|
|
69
|
+
};
|
|
70
|
+
// Leaf node — no submenu
|
|
71
|
+
if (!hasChildren) {
|
|
72
|
+
const triggerContent = renderTrigger(Object.assign(Object.assign({}, rest), { hasChildren: false, isOpen: false, isMobile }));
|
|
73
|
+
return wrapButton(link ? ((0, jsx_runtime_1.jsx)("a", Object.assign({ role: "menuitem", "aria-label": ariaLabel, "aria-disabled": disabled, href: disabled || !(0, isSafeHref_1.isSafeHref)(link) ? undefined : link, target: target, rel: (0, getSafeRel_1.getSafeRel)(rel, target), className: (0, classNames_1.classNames)(["zen-menu-button__action", "zen-caption", driveClass !== null && driveClass !== void 0 ? driveClass : ""]), onClick: handleDesktopActionClick }, linkEvents, { children: triggerContent }))) : ((0, jsx_runtime_1.jsx)("button", { ref: ref, type: "button", role: "menuitem", "aria-label": ariaLabel, disabled: !!disabled, className: (0, classNames_1.classNames)(["zen-menu-button__action", "zen-button", "zen-caption", driveClass !== null && driveClass !== void 0 ? driveClass : ""]), onClick: handleDesktopActionClick, children: triggerContent })));
|
|
74
|
+
}
|
|
75
|
+
// Mobile — button drills into sub-level via PathContext
|
|
76
|
+
if (isMobile) {
|
|
77
|
+
return wrapButton((0, jsx_runtime_1.jsx)("button", { ref: ref, type: "button", role: "menuitem", "aria-label": ariaLabel, disabled: !!disabled, className: (0, classNames_1.classNames)([
|
|
78
|
+
"zen-menu-button__action",
|
|
79
|
+
"zen-button",
|
|
80
|
+
"zen-caption",
|
|
81
|
+
"zen-menu-button__action--has-children",
|
|
82
|
+
driveClass !== null && driveClass !== void 0 ? driveClass : ""
|
|
83
|
+
]), onClick: handleTriggerClick, children: renderTrigger(Object.assign(Object.assign({}, rest), { hasChildren: true, isOpen, isMobile })) }));
|
|
84
|
+
}
|
|
85
|
+
// Desktop with submenu
|
|
86
|
+
return ((0, jsx_runtime_1.jsxs)(react_1.Fragment, { children: [wrapButton((0, jsx_runtime_1.jsx)("button", { ref: ref, type: "button", role: "menuitem", "aria-label": ariaLabel, disabled: !!disabled, "aria-haspopup": "menu", "aria-expanded": isOpen, className: (0, classNames_1.classNames)([
|
|
87
|
+
"zen-menu-button__action",
|
|
88
|
+
"zen-button",
|
|
89
|
+
"zen-caption",
|
|
90
|
+
"zen-menu-button__action--has-children",
|
|
91
|
+
driveClass !== null && driveClass !== void 0 ? driveClass : ""
|
|
92
|
+
]), onClick: handleTriggerClick, children: renderTrigger(Object.assign(Object.assign({}, rest), { hasChildren: true, isOpen, isMobile: false })) })), (0, jsx_runtime_1.jsx)(controlledPopup_1.ControlledPopup, { className: (0, classNames_1.classNames)([`zen-controlled-menu-submenu--${path.length}`]), useTrapFocusWithTrigger: openedViaKeyboard ? "on" : "withTrigger", alignment: contentAlignment, triggerRef: ref, isOpen: isOpen, onOpenChange: handleOpenChange, ariaLabel: ariaLabel, recalculateOnScroll: true, children: (0, jsx_runtime_1.jsx)("ul", { role: "menu", className: "zen-menu-item", children: content }) })] }));
|
|
93
|
+
});
|
|
94
|
+
CreatedItem.displayName = "MenuItem";
|
|
95
|
+
return CreatedItem;
|
|
96
|
+
}
|
|
97
|
+
exports.createMenuItem = createMenuItem;
|
|
@@ -8,6 +8,8 @@ const iconChevronRight_1 = require("../../icons/iconChevronRight");
|
|
|
8
8
|
const useDriveClassName_1 = require("../../utils/theme/useDriveClassName");
|
|
9
9
|
const useDrive_1 = require("../../utils/theme/useDrive");
|
|
10
10
|
const getMenuButtonState_1 = require("../utils/getMenuButtonState");
|
|
11
|
+
const isSafeHref_1 = require("../utils/isSafeHref");
|
|
12
|
+
const getSafeRel_1 = require("../utils/getSafeRel");
|
|
11
13
|
const MenuButton = ({ id, onClick, hasChildren, disabled, icon, name, link, target, rel, className = "", active = null, ref }) => {
|
|
12
14
|
const { hasState, isActive } = (0, getMenuButtonState_1.getMenuButtonState)(active, disabled);
|
|
13
15
|
const driveMenuButtonActionClasses = (0, useDriveClassName_1.useDriveClassName)("zen-menu-button__action");
|
|
@@ -17,6 +19,7 @@ const MenuButton = ({ id, onClick, hasChildren, disabled, icon, name, link, targ
|
|
|
17
19
|
if (e.key === " ") {
|
|
18
20
|
e.preventDefault();
|
|
19
21
|
const linkEl = e.target;
|
|
22
|
+
linkEl.dataset.spaceDown = "1";
|
|
20
23
|
linkEl.classList.add("zen-menu-button__action--active");
|
|
21
24
|
}
|
|
22
25
|
},
|
|
@@ -25,7 +28,10 @@ const MenuButton = ({ id, onClick, hasChildren, disabled, icon, name, link, targ
|
|
|
25
28
|
e.preventDefault();
|
|
26
29
|
const linkEl = e.target;
|
|
27
30
|
linkEl.classList.remove("zen-menu-button__action--active");
|
|
28
|
-
linkEl.
|
|
31
|
+
if (linkEl.dataset.spaceDown) {
|
|
32
|
+
delete linkEl.dataset.spaceDown;
|
|
33
|
+
linkEl.click();
|
|
34
|
+
}
|
|
29
35
|
}
|
|
30
36
|
}
|
|
31
37
|
}), []);
|
|
@@ -49,7 +55,7 @@ const MenuButton = ({ id, onClick, hasChildren, disabled, icon, name, link, targ
|
|
|
49
55
|
"zen-caption",
|
|
50
56
|
disabled ? "zen-menu-button__action--disabled" : "",
|
|
51
57
|
driveMenuButtonActionClasses || ""
|
|
52
|
-
]), href: disabled ? undefined : link, "aria-disabled": disabled, onClick: onClickHandler, target: target, rel: rel }, linkEvents, { children: [!!icon &&
|
|
58
|
+
]), href: disabled || !(0, isSafeHref_1.isSafeHref)(link) ? undefined : link, "aria-disabled": disabled, onClick: onClickHandler, target: target, rel: (0, getSafeRel_1.getSafeRel)(rel, target) }, linkEvents, { children: [!!icon &&
|
|
53
59
|
(0, react_1.createElement)(icon, {
|
|
54
60
|
size: isDrive ? "huge" : "large",
|
|
55
61
|
className: "zen-caption__pre-content"
|
|
@@ -2,6 +2,7 @@ import { FC, ReactElement } from "react";
|
|
|
2
2
|
import { IMenuButton } from "./menuButton";
|
|
3
3
|
import "./menuItem.less";
|
|
4
4
|
import { TAlignment } from "../../absolute/absolute";
|
|
5
|
+
export { isMenuItem } from "../utils/isMenuItem";
|
|
5
6
|
interface IMenuItemInternal {
|
|
6
7
|
isMobile?: boolean;
|
|
7
8
|
setIsOpen?: (v: boolean) => void;
|
|
@@ -12,6 +13,4 @@ export interface IMenuControlledItem extends IMenuItem {
|
|
|
12
13
|
export interface IMenuItem extends IMenuButton {
|
|
13
14
|
alignment?: TAlignment;
|
|
14
15
|
}
|
|
15
|
-
export declare const isMenuItem: (element: ReactElement | undefined) => boolean;
|
|
16
16
|
export declare const MenuItem: FC<Omit<IMenuItem & IMenuControlledItem & IMenuItemInternal, "hasChildren">>;
|
|
17
|
-
export {};
|
|
@@ -2,37 +2,30 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.MenuItem = exports.isMenuItem = void 0;
|
|
4
4
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
-
/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
|
|
5
|
+
/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument */
|
|
6
6
|
const react_1 = require("react");
|
|
7
7
|
const menuButton_1 = require("./menuButton");
|
|
8
|
-
const controlledMenu_1 = require("../controlledMenu");
|
|
9
8
|
const controlledPopup_1 = require("../../controlledPopup/controlledPopup");
|
|
10
|
-
const pathContext_1 = require("../contexts/pathContext");
|
|
11
9
|
const classNames_1 = require("../../commonHelpers/classNames/classNames");
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const isMenuItem = (element) => {
|
|
16
|
-
if (!element || !element.type) {
|
|
17
|
-
return false;
|
|
18
|
-
}
|
|
19
|
-
if (element.type === controlledMenu_1.ControlledMenu.Item) {
|
|
20
|
-
return true;
|
|
21
|
-
}
|
|
22
|
-
if ((typeof element.type === "object" || typeof element.type === "function") && "displayName" in element.type) {
|
|
23
|
-
return element.type.displayName === "MenuItem";
|
|
24
|
-
}
|
|
25
|
-
return false;
|
|
26
|
-
};
|
|
27
|
-
exports.isMenuItem = isMenuItem;
|
|
10
|
+
const useMenuItemCore_1 = require("../utils/useMenuItemCore");
|
|
11
|
+
var isMenuItem_1 = require("../utils/isMenuItem");
|
|
12
|
+
Object.defineProperty(exports, "isMenuItem", { enumerable: true, get: function () { return isMenuItem_1.isMenuItem; } });
|
|
28
13
|
const MenuItem = ({ id, children, name, icon, disabled, onClick, link, target, rel, isMobile = false, setIsOpen, trigger, className, active, alignment }) => {
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
14
|
+
const { ref, isOpen, hasChildren, content, openedViaKeyboard, contentAlignment, path, onOpenBranch, handleOpenChange } = (0, useMenuItemCore_1.useMenuItemCore)({
|
|
15
|
+
id,
|
|
16
|
+
children,
|
|
17
|
+
className,
|
|
18
|
+
alignment,
|
|
19
|
+
isMobile,
|
|
20
|
+
setIsOpen,
|
|
21
|
+
onClick
|
|
22
|
+
});
|
|
23
|
+
// MenuButton.onClick signature is (id, e) — adapt the hook's (e)-only handler
|
|
24
|
+
const memoizedDesktopActionOnClick = (0, react_1.useCallback)((_, e) => {
|
|
33
25
|
setIsOpen === null || setIsOpen === void 0 ? void 0 : setIsOpen(false);
|
|
34
|
-
onClick === null || onClick === void 0 ? void 0 : onClick(
|
|
35
|
-
}, [setIsOpen, onClick]);
|
|
26
|
+
onClick === null || onClick === void 0 ? void 0 : onClick(id, e);
|
|
27
|
+
}, [setIsOpen, onClick, id]);
|
|
28
|
+
// MenuItem-specific callbacks (not provided by hook)
|
|
36
29
|
const memoizedMobileActionOnClick = (0, react_1.useCallback)((itemId, e) => {
|
|
37
30
|
onOpenBranch(id);
|
|
38
31
|
!link && (onClick === null || onClick === void 0 ? void 0 : onClick(itemId, e));
|
|
@@ -50,54 +43,7 @@ const MenuItem = ({ id, children, name, icon, disabled, onClick, link, target, r
|
|
|
50
43
|
}
|
|
51
44
|
onClick === null || onClick === void 0 ? void 0 : onClick(id, e);
|
|
52
45
|
}, [onClick, onOpenBranch, id, trigger]);
|
|
53
|
-
|
|
54
|
-
closeBranch();
|
|
55
|
-
}, [closeBranch]);
|
|
56
|
-
const ref = (0, react_1.useRef)(null);
|
|
57
|
-
const content = (0, react_1.useMemo)(() => {
|
|
58
|
-
const cont = [];
|
|
59
|
-
react_1.Children.map(children, (child) => {
|
|
60
|
-
if (!child) {
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
if (typeof child === "string") {
|
|
64
|
-
cont.push((0, jsx_runtime_1.jsx)("li", { className: (0, classNames_1.classNames)(["zen-menu-item__content", className !== null && className !== void 0 ? className : ""]), role: "presentation", children: child }, (0, generateId_1.generateId)()));
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
if ((0, react_1.isValidElement)(child) && (0, menuSeparator_1.isSeparator)(child)) {
|
|
68
|
-
const clone = (0, react_1.cloneElement)(child, {
|
|
69
|
-
key: child.props.key || (0, generateId_1.generateId)()
|
|
70
|
-
});
|
|
71
|
-
cont.push(clone);
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
if ((0, exports.isMenuItem)(child)) {
|
|
75
|
-
const childProps = child.props;
|
|
76
|
-
const clone = (0, react_1.cloneElement)(child, {
|
|
77
|
-
isMobile,
|
|
78
|
-
key: childProps.id,
|
|
79
|
-
setIsOpen
|
|
80
|
-
});
|
|
81
|
-
cont.push(clone);
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
const childProps = child.props;
|
|
85
|
-
cont.push((0, jsx_runtime_1.jsx)("li", { className: (0, classNames_1.classNames)(["zen-menu-item__content", className !== null && className !== void 0 ? className : ""]), role: "presentation", children: child }, childProps.id || childProps["data-id"] || (0, generateId_1.generateId)()));
|
|
86
|
-
});
|
|
87
|
-
return cont;
|
|
88
|
-
}, [children, isMobile, setIsOpen, className]);
|
|
89
|
-
const isOpen = (0, react_1.useMemo)(() => path.includes(id), [path, id]);
|
|
90
|
-
// Track previous isOpen state to detect when submenu opens
|
|
91
|
-
const wasOpenRef = (0, react_1.useRef)(false);
|
|
92
|
-
const localOpenedViaKeyboardRef = (0, react_1.useRef)(false);
|
|
93
|
-
// Capture keyboard navigation state synchronously when isOpen transitions to true
|
|
94
|
-
if (isOpen && !wasOpenRef.current && navigatedViaKeyboardRef) {
|
|
95
|
-
localOpenedViaKeyboardRef.current = navigatedViaKeyboardRef.current;
|
|
96
|
-
navigatedViaKeyboardRef.current = false; // Reset for next navigation
|
|
97
|
-
}
|
|
98
|
-
wasOpenRef.current = isOpen;
|
|
99
|
-
const openedViaKeyboard = localOpenedViaKeyboardRef.current;
|
|
100
|
-
if (content.length === 0) {
|
|
46
|
+
if (!hasChildren) {
|
|
101
47
|
return ((0, jsx_runtime_1.jsx)(menuButton_1.MenuButton, { id: id, name: name, icon: icon, disabled: disabled, link: link, target: target, rel: rel, onClick: memoizedDesktopActionOnClick, className: className, active: active, hasChildren: false }, id));
|
|
102
48
|
}
|
|
103
49
|
if (isMobile) {
|
|
@@ -113,7 +59,7 @@ const MenuItem = ({ id, children, name, icon, disabled, onClick, link, target, r
|
|
|
113
59
|
else {
|
|
114
60
|
popupTrigger = ((0, jsx_runtime_1.jsx)(menuButton_1.MenuButton, { id: id, ref: ref, name: name, icon: icon, disabled: disabled, hasChildren: true, onClick: memoizedTriggerOnClick, active: active }, id));
|
|
115
61
|
}
|
|
116
|
-
return ((0, jsx_runtime_1.jsxs)(react_1.Fragment, { children: [popupTrigger, (0, jsx_runtime_1.jsx)(controlledPopup_1.ControlledPopup, { className: (0, classNames_1.classNames)([`zen-controlled-menu-submenu--${path.length}`]), useTrapFocusWithTrigger: openedViaKeyboard ? "on" : "withTrigger", alignment: contentAlignment, triggerRef: ref, isOpen: isOpen, onOpenChange:
|
|
62
|
+
return ((0, jsx_runtime_1.jsxs)(react_1.Fragment, { children: [popupTrigger, (0, jsx_runtime_1.jsx)(controlledPopup_1.ControlledPopup, { className: (0, classNames_1.classNames)([`zen-controlled-menu-submenu--${path.length}`]), useTrapFocusWithTrigger: openedViaKeyboard ? "on" : "withTrigger", alignment: contentAlignment, triggerRef: ref, isOpen: isOpen, onOpenChange: handleOpenChange, ariaLabel: popupTrigger.props.name, recalculateOnScroll: true, children: (0, jsx_runtime_1.jsx)("ul", { role: "menu", className: "zen-menu-item", children: content }) })] }, id));
|
|
117
63
|
};
|
|
118
64
|
exports.MenuItem = MenuItem;
|
|
119
65
|
exports.MenuItem.displayName = "MenuItem";
|