@geotab/zenith 3.6.3 → 3.7.0
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 +15 -0
- package/dist/absolute/absolute.d.ts +3 -1
- package/dist/absolute/absolute.js +4 -4
- package/dist/checkbox/checkbox.d.ts +3 -1
- package/dist/checkbox/checkbox.js +5 -8
- package/dist/checkboxListWithAction/checkboxListWithAction.d.ts +3 -1
- package/dist/checkboxListWithAction/checkboxListWithAction.js +185 -3
- package/dist/controlledPopup/controlledPopup.d.ts +3 -1
- package/dist/controlledPopup/controlledPopup.js +2 -2
- package/dist/dropdown/dropdownPopup.d.ts +1 -1
- package/dist/dropdown/dropdownTrigger.d.ts +1 -1
- package/dist/dropdownRaw/dropdownList.d.ts +2 -1
- package/dist/dropdownRaw/dropdownList.js +15 -4
- package/dist/dropdownRaw/dropdownPopup.d.ts +3 -1
- package/dist/dropdownRaw/dropdownPopup.js +35 -5
- package/dist/dropdownRaw/dropdownRaw.js +82 -7
- package/dist/dropdownRaw/dropdownSearchableTrigger.d.ts +2 -1
- package/dist/dropdownRaw/dropdownSearchableTrigger.js +7 -2
- package/dist/dropdownRaw/dropdownTrigger.d.ts +2 -1
- package/dist/dropdownRaw/dropdownTrigger.js +3 -5
- package/dist/footerButtons/footerButtons.d.ts +6 -4
- package/dist/footerButtons/footerButtons.js +3 -8
- package/dist/groupsFilterRaw/groupsFilterCurrentlySelectedState.js +8 -2
- package/dist/groupsFilterRaw/groupsFilterInitialState.js +16 -0
- package/dist/groupsFilterRaw/groupsFilterRaw.js +43 -9
- package/dist/groupsFilterRaw/groupsFilterTrigger.d.ts +2 -1
- package/dist/groupsFilterRaw/groupsFilterTrigger.js +13 -2
- package/dist/index.css +222 -18
- package/dist/index.d.ts +1 -1
- package/dist/list/hooks/useDragAndDrop.d.ts +3 -1
- package/dist/list/hooks/useDragAndDrop.js +11 -4
- package/dist/menu/components/menuItem.js +12 -2
- package/dist/menu/contexts/pathContext.d.ts +3 -1
- package/dist/menu/contexts/pathProvider.d.ts +1 -1
- package/dist/menu/contexts/pathProvider.js +1 -1
- package/dist/menu/controlledMenu.js +97 -29
- package/dist/nav/nav.d.ts +2 -1
- package/dist/nav/nav.js +3 -2
- package/dist/nav/navEditList/navEditList.js +2 -1
- package/dist/nav/navItem/navItem.d.ts +1 -0
- package/dist/nav/navItem/navItem.js +4 -1
- package/dist/pillExpandable/pillContent.d.ts +2 -1
- package/dist/pillExpandable/pillContent.js +8 -3
- package/dist/pillExpandable/pillExpandable.d.ts +27 -4
- package/dist/pillExpandable/pillExpandable.js +426 -196
- package/dist/pillExpandable/pillExpandablePopoverContent.d.ts +19 -0
- package/dist/pillExpandable/pillExpandablePopoverContent.js +7 -0
- package/dist/pillExpandable/pillExpandableSimple.d.ts +5 -3
- package/dist/pillExpandable/pillExpandableSimple.js +12 -5
- package/dist/summary/summary.js +29 -1
- package/dist/summaryTile/summaryTile.d.ts +2 -1
- package/dist/summaryTile/summaryTile.js +175 -65
- package/dist/summaryTile/summaryTileTrigger.d.ts +1 -1
- package/dist/summaryTile/summaryTileTrigger.js +1 -1
- package/dist/toggleButtonRaw/toggleButtonRaw.js +2 -1
- package/dist/utils/localization/getSupportedLanguage.d.ts +2 -2
- package/dist/utils/localization/getSupportedLanguage.js +28 -8
- package/dist/utils/localization/languageContext.d.ts +1 -1
- package/dist/utils/localization/translations/cs.json +2 -1
- package/dist/utils/localization/translations/de.json +2 -1
- package/dist/utils/localization/translations/en.json +2 -1
- package/dist/utils/localization/translations/es.json +2 -1
- package/dist/utils/localization/translations/fr-FR.json +2 -1
- package/dist/utils/localization/translations/fr.json +2 -1
- package/dist/utils/localization/translations/id.json +2 -1
- package/dist/utils/localization/translations/it.json +2 -1
- package/dist/utils/localization/translations/ja.json +2 -1
- package/dist/utils/localization/translations/ms.json +2 -1
- package/dist/utils/localization/translations/nl.json +2 -1
- package/dist/utils/localization/translations/pl.json +2 -1
- package/dist/utils/localization/translations/pt-BR.json +2 -1
- package/dist/utils/localization/translations/sv.json +2 -1
- package/dist/utils/localization/translations/th.json +2 -1
- package/dist/utils/localization/translations/tr.json +2 -1
- package/dist/utils/localization/translations/zh-Hans.json +2 -1
- package/esm/absolute/absolute.d.ts +3 -1
- package/esm/absolute/absolute.js +4 -4
- package/esm/checkbox/checkbox.d.ts +3 -1
- package/esm/checkbox/checkbox.js +5 -8
- package/esm/checkboxListWithAction/checkboxListWithAction.d.ts +3 -1
- package/esm/checkboxListWithAction/checkboxListWithAction.js +186 -4
- package/esm/controlledPopup/controlledPopup.d.ts +3 -1
- package/esm/controlledPopup/controlledPopup.js +2 -2
- package/esm/dropdown/dropdownPopup.d.ts +1 -1
- package/esm/dropdown/dropdownTrigger.d.ts +1 -1
- package/esm/dropdownRaw/dropdownList.d.ts +2 -1
- package/esm/dropdownRaw/dropdownList.js +16 -5
- package/esm/dropdownRaw/dropdownPopup.d.ts +3 -1
- package/esm/dropdownRaw/dropdownPopup.js +37 -7
- package/esm/dropdownRaw/dropdownRaw.js +82 -7
- package/esm/dropdownRaw/dropdownSearchableTrigger.d.ts +2 -1
- package/esm/dropdownRaw/dropdownSearchableTrigger.js +7 -2
- package/esm/dropdownRaw/dropdownTrigger.d.ts +2 -1
- package/esm/dropdownRaw/dropdownTrigger.js +3 -5
- package/esm/footerButtons/footerButtons.d.ts +6 -4
- package/esm/footerButtons/footerButtons.js +3 -8
- package/esm/groupsFilterRaw/groupsFilterCurrentlySelectedState.js +9 -3
- package/esm/groupsFilterRaw/groupsFilterInitialState.js +16 -0
- package/esm/groupsFilterRaw/groupsFilterRaw.js +43 -9
- package/esm/groupsFilterRaw/groupsFilterTrigger.d.ts +2 -1
- package/esm/groupsFilterRaw/groupsFilterTrigger.js +13 -2
- package/esm/index.d.ts +1 -1
- package/esm/list/hooks/useDragAndDrop.d.ts +3 -1
- package/esm/list/hooks/useDragAndDrop.js +11 -4
- package/esm/menu/components/menuItem.js +12 -2
- package/esm/menu/contexts/pathContext.d.ts +3 -1
- package/esm/menu/contexts/pathProvider.d.ts +1 -1
- package/esm/menu/contexts/pathProvider.js +1 -1
- package/esm/menu/controlledMenu.js +98 -30
- package/esm/nav/nav.d.ts +2 -1
- package/esm/nav/nav.js +3 -2
- package/esm/nav/navEditList/navEditList.js +2 -1
- package/esm/nav/navItem/navItem.d.ts +1 -0
- package/esm/nav/navItem/navItem.js +5 -2
- package/esm/pillExpandable/pillContent.d.ts +2 -1
- package/esm/pillExpandable/pillContent.js +8 -3
- package/esm/pillExpandable/pillExpandable.d.ts +27 -4
- package/esm/pillExpandable/pillExpandable.js +427 -197
- package/esm/pillExpandable/pillExpandablePopoverContent.d.ts +19 -0
- package/esm/pillExpandable/pillExpandablePopoverContent.js +3 -0
- package/esm/pillExpandable/pillExpandableSimple.d.ts +5 -3
- package/esm/pillExpandable/pillExpandableSimple.js +12 -5
- package/esm/summary/summary.js +29 -1
- package/esm/summaryTile/summaryTile.d.ts +2 -1
- package/esm/summaryTile/summaryTile.js +140 -43
- package/esm/summaryTile/summaryTileTrigger.d.ts +1 -1
- package/esm/summaryTile/summaryTileTrigger.js +1 -1
- package/esm/toggleButtonRaw/toggleButtonRaw.js +2 -1
- package/esm/utils/localization/getSupportedLanguage.d.ts +2 -2
- package/esm/utils/localization/getSupportedLanguage.js +28 -8
- package/esm/utils/localization/languageContext.d.ts +1 -1
- package/esm/utils/localization/translations/cs.json +2 -1
- package/esm/utils/localization/translations/de.json +2 -1
- package/esm/utils/localization/translations/en.json +2 -1
- package/esm/utils/localization/translations/es.json +2 -1
- package/esm/utils/localization/translations/fr-FR.json +2 -1
- package/esm/utils/localization/translations/fr.json +2 -1
- package/esm/utils/localization/translations/id.json +2 -1
- package/esm/utils/localization/translations/it.json +2 -1
- package/esm/utils/localization/translations/ja.json +2 -1
- package/esm/utils/localization/translations/ms.json +2 -1
- package/esm/utils/localization/translations/nl.json +2 -1
- package/esm/utils/localization/translations/pl.json +2 -1
- package/esm/utils/localization/translations/pt-BR.json +2 -1
- package/esm/utils/localization/translations/sv.json +2 -1
- package/esm/utils/localization/translations/th.json +2 -1
- package/esm/utils/localization/translations/tr.json +2 -1
- package/esm/utils/localization/translations/zh-Hans.json +2 -1
- package/package.json +4 -7
|
@@ -24,7 +24,7 @@ export const isMenuItem = (element) => {
|
|
|
24
24
|
export const MenuItem = ({ id, children, name, icon, disabled, onClick, link, target, rel, isMobile = false, setIsOpen, trigger, className, active, alignment }) => {
|
|
25
25
|
const aligmentContext = useContext(MenuAlignmentContext);
|
|
26
26
|
const contentAlignment = alignment || aligmentContext.alignment || "right-top";
|
|
27
|
-
const { path, onOpenBranch, closeBranch } = useContext(PathContext);
|
|
27
|
+
const { path, onOpenBranch, closeBranch, navigatedViaKeyboardRef } = useContext(PathContext);
|
|
28
28
|
const memoizedDesktopActionOnClick = useCallback((itemId, e) => {
|
|
29
29
|
setIsOpen === null || setIsOpen === void 0 ? void 0 : setIsOpen(false);
|
|
30
30
|
onClick === null || onClick === void 0 ? void 0 : onClick(itemId, e);
|
|
@@ -83,6 +83,16 @@ export const MenuItem = ({ id, children, name, icon, disabled, onClick, link, ta
|
|
|
83
83
|
return cont;
|
|
84
84
|
}, [children, isMobile, setIsOpen, className]);
|
|
85
85
|
const isOpen = useMemo(() => path.includes(id), [path, id]);
|
|
86
|
+
// Track previous isOpen state to detect when submenu opens
|
|
87
|
+
const wasOpenRef = useRef(false);
|
|
88
|
+
const localOpenedViaKeyboardRef = useRef(false);
|
|
89
|
+
// Capture keyboard navigation state synchronously when isOpen transitions to true
|
|
90
|
+
if (isOpen && !wasOpenRef.current && navigatedViaKeyboardRef) {
|
|
91
|
+
localOpenedViaKeyboardRef.current = navigatedViaKeyboardRef.current;
|
|
92
|
+
navigatedViaKeyboardRef.current = false; // Reset for next navigation
|
|
93
|
+
}
|
|
94
|
+
wasOpenRef.current = isOpen;
|
|
95
|
+
const openedViaKeyboard = localOpenedViaKeyboardRef.current;
|
|
86
96
|
if (content.length === 0) {
|
|
87
97
|
return _jsx(MenuButton, { id: id, name: name, icon: icon, disabled: disabled, link: link, target: target, rel: rel, onClick: memoizedDesktopActionOnClick, className: className, active: active, hasChildren: false }, id);
|
|
88
98
|
}
|
|
@@ -99,6 +109,6 @@ export const MenuItem = ({ id, children, name, icon, disabled, onClick, link, ta
|
|
|
99
109
|
else {
|
|
100
110
|
popupTrigger = _jsx(MenuButton, { id: id, ref: ref, name: name, icon: icon, disabled: disabled, hasChildren: true, onClick: memoizedTriggerOnClick, active: active }, id);
|
|
101
111
|
}
|
|
102
|
-
return _jsxs(Fragment, { children: [popupTrigger, _jsx(ControlledPopup, { className: classNames([`zen-controlled-menu-submenu--${path.length}`]), useTrapFocusWithTrigger: "on", alignment: contentAlignment, triggerRef: ref, isOpen: isOpen, onOpenChange: memoizedOnOpenChange, ariaLabel: popupTrigger.props.name, recalculateOnScroll: true, children: _jsx("ul", { role: "menu", className: "zen-menu-item", children: content }) })] }, id);
|
|
112
|
+
return _jsxs(Fragment, { children: [popupTrigger, _jsx(ControlledPopup, { className: classNames([`zen-controlled-menu-submenu--${path.length}`]), useTrapFocusWithTrigger: openedViaKeyboard ? "on" : "withTrigger", alignment: contentAlignment, triggerRef: ref, isOpen: isOpen, onOpenChange: memoizedOnOpenChange, ariaLabel: popupTrigger.props.name, recalculateOnScroll: true, children: _jsx("ul", { role: "menu", className: "zen-menu-item", children: content }) })] }, id);
|
|
103
113
|
};
|
|
104
114
|
MenuItem.displayName = "MenuItem";
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
import { RefObject } from "react";
|
|
2
2
|
export interface IPathContext {
|
|
3
3
|
path: string[];
|
|
4
4
|
onOpenBranch: (id: string) => void;
|
|
5
5
|
closeBranch: () => void;
|
|
6
|
+
navigatedViaKeyboardRef?: RefObject<boolean>;
|
|
7
|
+
keyboardActiveRef?: RefObject<boolean>;
|
|
6
8
|
}
|
|
7
9
|
export declare const PathContext: import("react").Context<IPathContext>;
|
|
@@ -2,5 +2,5 @@ import { PropsWithChildren } from "react";
|
|
|
2
2
|
import { IPathContext } from "./pathContext";
|
|
3
3
|
interface IPathProvider extends IPathContext, PropsWithChildren {
|
|
4
4
|
}
|
|
5
|
-
export declare const PathProvider: ({ children, path, onOpenBranch, closeBranch }: IPathProvider) => import("react/jsx-runtime").JSX.Element;
|
|
5
|
+
export declare const PathProvider: ({ children, path, onOpenBranch, closeBranch, navigatedViaKeyboardRef, keyboardActiveRef }: IPathProvider) => import("react/jsx-runtime").JSX.Element;
|
|
6
6
|
export {};
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { PathContext } from "./pathContext";
|
|
3
|
-
export const PathProvider = ({ children, path, onOpenBranch, closeBranch }) => (_jsx(PathContext.Provider, { value: { path, onOpenBranch, closeBranch }, children: children }));
|
|
3
|
+
export const PathProvider = ({ children, path, onOpenBranch, closeBranch, navigatedViaKeyboardRef, keyboardActiveRef }) => (_jsx(PathContext.Provider, { value: { path, onOpenBranch, closeBranch, navigatedViaKeyboardRef, keyboardActiveRef }, children: children }));
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
/* eslint-disable @typescript-eslint/naming-convention, @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument */
|
|
3
|
-
import { Children, cloneElement, isValidElement, useState, useMemo, useCallback, useEffect } from "react";
|
|
3
|
+
import { Children, cloneElement, isValidElement, useState, useMemo, useCallback, useEffect, useRef } from "react";
|
|
4
4
|
import { MenuItem, isMenuItem } from "./components/menuItem";
|
|
5
5
|
import { classNames } from "../commonHelpers/classNames/classNames";
|
|
6
6
|
import { findContent } from "./utils/findContent";
|
|
@@ -19,17 +19,57 @@ import { useDeviceType } from "../commonHelpers/hooks/useDeviceType";
|
|
|
19
19
|
import { generateId } from "../commonHelpers/utils";
|
|
20
20
|
import { PathProvider } from "./contexts/pathProvider";
|
|
21
21
|
import { isSeparator, MenuSeparator } from "./components/menuSeparator";
|
|
22
|
+
import { FOCUSABLE_SELECTOR } from "../utils/focusableSelector";
|
|
22
23
|
export const ControlledMenu = ({ children, isOpen, setIsOpen, triggerRef, ariaLabel, ariaLabelledby, id, title, className = "", listClassName = "", paddingX = 0, paddingY = 0, alignment }) => {
|
|
23
24
|
const [deviceType, setDeviceType] = useState(DeviceType.Desktop);
|
|
24
25
|
const isMobile = deviceType === DeviceType.Mobile;
|
|
25
26
|
const memoizedOnChange = useCallback(setIsOpen, [setIsOpen]);
|
|
26
27
|
useDeviceType(setDeviceType);
|
|
28
|
+
const menuListRef = useRef(null);
|
|
27
29
|
const [path, setPath] = useState([]);
|
|
30
|
+
// Track if the trigger was activated via keyboard
|
|
31
|
+
const openedViaKeyboardRef = useRef(false);
|
|
32
|
+
// Track if submenu was navigated via keyboard (ArrowRight)
|
|
33
|
+
const navigatedViaKeyboardRef = useRef(false);
|
|
34
|
+
// Track if keyboard is actively being used for navigation (vs mouse/touch)
|
|
35
|
+
const keyboardActiveRef = useRef(false);
|
|
36
|
+
// Listen for keyboard activation on trigger
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
const trigger = triggerRef.current;
|
|
39
|
+
if (!trigger)
|
|
40
|
+
return undefined;
|
|
41
|
+
const handleKeyDown = (e) => {
|
|
42
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
43
|
+
openedViaKeyboardRef.current = true;
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
const handleMouseDown = () => {
|
|
47
|
+
openedViaKeyboardRef.current = false;
|
|
48
|
+
};
|
|
49
|
+
trigger.addEventListener("keydown", handleKeyDown);
|
|
50
|
+
trigger.addEventListener("mousedown", handleMouseDown);
|
|
51
|
+
return () => {
|
|
52
|
+
trigger.removeEventListener("keydown", handleKeyDown);
|
|
53
|
+
trigger.removeEventListener("mousedown", handleMouseDown);
|
|
54
|
+
};
|
|
55
|
+
}, [triggerRef]);
|
|
28
56
|
useEffect(() => {
|
|
29
57
|
if (path.length && !isOpen) {
|
|
30
58
|
setPath([]);
|
|
31
59
|
}
|
|
32
60
|
}, [isOpen, path, setPath]);
|
|
61
|
+
// Focus the menu list container or first item when menu opens (for keyboard navigation)
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
var _a;
|
|
64
|
+
if (isOpen && !isMobile && menuListRef.current) {
|
|
65
|
+
if (openedViaKeyboardRef.current) {
|
|
66
|
+
(_a = menuListRef.current.querySelector(FOCUSABLE_SELECTOR)) === null || _a === void 0 ? void 0 : _a.focus();
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
menuListRef.current.focus();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}, [isOpen, isMobile]);
|
|
33
73
|
const onOpenBranch = useCallback((branchId) => {
|
|
34
74
|
if (!branchId) {
|
|
35
75
|
return;
|
|
@@ -107,48 +147,76 @@ export const ControlledMenu = ({ children, isOpen, setIsOpen, triggerRef, ariaLa
|
|
|
107
147
|
});
|
|
108
148
|
return [cont, par];
|
|
109
149
|
}, [children, isMobile, path, setIsOpen]);
|
|
150
|
+
// Handle keyboard navigation when no menu item is focused (e.g., when menu just opened)
|
|
151
|
+
const handleUnfocusedKeyDown = (e, menuList) => {
|
|
152
|
+
var _a, _b;
|
|
153
|
+
if (e.key === "ArrowDown" || e.key === "Home") {
|
|
154
|
+
e.preventDefault();
|
|
155
|
+
(_a = findFirstFocusable(menuList)) === null || _a === void 0 ? void 0 : _a.focus();
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
if (e.key === "ArrowUp" || e.key === "End") {
|
|
159
|
+
e.preventDefault();
|
|
160
|
+
(_b = findLastFocusable(menuList)) === null || _b === void 0 ? void 0 : _b.focus();
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
return false;
|
|
164
|
+
};
|
|
165
|
+
const onMouseDown = () => {
|
|
166
|
+
keyboardActiveRef.current = false;
|
|
167
|
+
};
|
|
110
168
|
const onKeyDown = (e) => {
|
|
111
169
|
var _a, _b, _c, _d;
|
|
170
|
+
keyboardActiveRef.current = true;
|
|
112
171
|
const target = e.target;
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
e.preventDefault();
|
|
121
|
-
(_b = findPrevFocusable(target)) === null || _b === void 0 ? void 0 : _b.focus();
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
if (e.key === "Home") {
|
|
125
|
-
e.preventDefault();
|
|
126
|
-
(_c = findFirstFocusable(target)) === null || _c === void 0 ? void 0 : _c.focus();
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
if (e.key === "End") {
|
|
130
|
-
e.preventDefault();
|
|
131
|
-
(_d = findLastFocusable(target)) === null || _d === void 0 ? void 0 : _d.focus();
|
|
132
|
-
return;
|
|
172
|
+
const currentTarget = e.currentTarget;
|
|
173
|
+
if (!isButton(target) && !isLink(target)) {
|
|
174
|
+
if (target === currentTarget) {
|
|
175
|
+
const menuList = currentTarget.querySelector("ul");
|
|
176
|
+
if (menuList) {
|
|
177
|
+
handleUnfocusedKeyDown(e, menuList);
|
|
178
|
+
}
|
|
133
179
|
}
|
|
180
|
+
return;
|
|
134
181
|
}
|
|
135
|
-
if (
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
182
|
+
if (e.key === "ArrowDown") {
|
|
183
|
+
e.preventDefault();
|
|
184
|
+
(_a = findNextFocusable(target)) === null || _a === void 0 ? void 0 : _a.focus();
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
if (e.key === "ArrowUp") {
|
|
188
|
+
e.preventDefault();
|
|
189
|
+
(_b = findPrevFocusable(target)) === null || _b === void 0 ? void 0 : _b.focus();
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
if (e.key === "Home") {
|
|
193
|
+
e.preventDefault();
|
|
194
|
+
(_c = findFirstFocusable(target)) === null || _c === void 0 ? void 0 : _c.focus();
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
if (e.key === "End") {
|
|
198
|
+
e.preventDefault();
|
|
199
|
+
(_d = findLastFocusable(target)) === null || _d === void 0 ? void 0 : _d.focus();
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
if (isButton(target) && (e.key === "ArrowRight" || e.key === "Enter" || e.key === " ") && target.classList.contains("zen-menu-button__action--has-children")) {
|
|
203
|
+
e.preventDefault();
|
|
204
|
+
navigatedViaKeyboardRef.current = true;
|
|
205
|
+
target.click();
|
|
141
206
|
}
|
|
142
207
|
};
|
|
143
|
-
const renderMenuList = () => _jsx("div", { onKeyDown: onKeyDown, className: classNames(["zen-action-list", className]), children: _jsxs("ul", { role: "menu", className: classNames(["zen-menu-item", className, listClassName]), children: [parent ? _jsx(MenuButton, { id: "root", name: parent.props.name || "", icon: IconArrowLeft, onClick: closeBranch, hasChildren: false, disabled: false }, "root") : null, content] }) });
|
|
208
|
+
const renderMenuList = () => _jsx("div", { ref: menuListRef, tabIndex: -1, onKeyDown: onKeyDown, onMouseDown: onMouseDown, className: classNames(["zen-action-list", className]), children: _jsxs("ul", { role: "menu", className: classNames(["zen-menu-item", className, listClassName]), children: [parent ? _jsx(MenuButton, { id: "root", name: parent.props.name || "", icon: IconArrowLeft, onClick: closeBranch, hasChildren: false, disabled: false }, "root") : null, content] }) });
|
|
144
209
|
const hideMenu = useCallback(() => {
|
|
145
210
|
closeBranch();
|
|
146
211
|
setIsOpen(false);
|
|
147
212
|
}, [closeBranch, setIsOpen]);
|
|
148
213
|
if (isMobile) {
|
|
149
|
-
return _jsx(_Fragment, { children: _jsx(PathProvider, { path: path, onOpenBranch: onOpenBranch, closeBranch: closeBranch, children: _jsxs(MobileSheet, { label: title, isOpen: isOpen, triggerRef: triggerRef, onHidePanel: hideMenu, onCloseClick: hideMenu, children: [_jsx(MobileSheet.Title, { children: title }), _jsx(MobileSheet.Content, { children: renderMenuList() })] }) }) });
|
|
214
|
+
return _jsx(_Fragment, { children: _jsx(PathProvider, { path: path, onOpenBranch: onOpenBranch, closeBranch: closeBranch, navigatedViaKeyboardRef: navigatedViaKeyboardRef, keyboardActiveRef: keyboardActiveRef, children: _jsxs(MobileSheet, { label: title, isOpen: isOpen, triggerRef: triggerRef, onHidePanel: hideMenu, onCloseClick: hideMenu, children: [_jsx(MobileSheet.Title, { children: title }), _jsx(MobileSheet.Content, { children: renderMenuList() })] }) }) });
|
|
150
215
|
}
|
|
151
|
-
return _jsx(_Fragment, { children: _jsx(PathProvider, { path: path, onOpenBranch: onOpenBranch, closeBranch: closeBranch, children: _jsx(ControlledPopup, { id: id, useTrapFocusWithTrigger: "on", className: classNames(["zen-controlled-menu", className]), onOpenChange: memoizedOnChange, isOpen: isOpen, triggerRef: triggerRef, paddingX: paddingX, paddingY: paddingY, alignment: alignment, ariaLabelledby: ariaLabelledby, ariaLabel: ariaLabel || title, recalculateOnScroll: true,
|
|
216
|
+
return _jsx(_Fragment, { children: _jsx(PathProvider, { path: path, onOpenBranch: onOpenBranch, closeBranch: closeBranch, navigatedViaKeyboardRef: navigatedViaKeyboardRef, keyboardActiveRef: keyboardActiveRef, children: _jsx(ControlledPopup, { id: id, useTrapFocusWithTrigger: "on", className: classNames(["zen-controlled-menu", className]), onOpenChange: memoizedOnChange, isOpen: isOpen, triggerRef: triggerRef, paddingX: paddingX, paddingY: paddingY, alignment: alignment, ariaLabelledby: ariaLabelledby, ariaLabel: ariaLabel || title, recalculateOnScroll: true,
|
|
217
|
+
// focusOnOpen is false - ControlledMenu handles focus based on input method
|
|
218
|
+
// (keyboard vs mouse) in its own useEffect
|
|
219
|
+
focusOnOpen: false, children: renderMenuList() }) }) });
|
|
152
220
|
};
|
|
153
221
|
ControlledMenu.Item = MenuItem;
|
|
154
222
|
ControlledMenu.Separator = MenuSeparator;
|
package/esm/nav/nav.d.ts
CHANGED
|
@@ -7,8 +7,9 @@ export interface INav extends IZenComponentProps {
|
|
|
7
7
|
onMenuVisibilityToggle?: (isOpen: boolean) => void;
|
|
8
8
|
isMobileMenuOpen?: boolean;
|
|
9
9
|
onIsMobileMenuOpenToggle?: (isOpen: boolean) => void;
|
|
10
|
+
onEditModeToggle?: (isEdit: boolean) => void;
|
|
10
11
|
}
|
|
11
12
|
/**
|
|
12
13
|
* @beta This component is not fully ready yet and may change in future releases.
|
|
13
14
|
*/
|
|
14
|
-
export declare const Nav: ({ children, className, collapsed, onCollapseToggle, onMenuVisibilityToggle, mobileBarContainer, isMobileMenuOpen, onIsMobileMenuOpenToggle }: INav) => import("react/jsx-runtime").JSX.Element;
|
|
15
|
+
export declare const Nav: ({ children, className, collapsed, onCollapseToggle, onMenuVisibilityToggle, mobileBarContainer, isMobileMenuOpen, onIsMobileMenuOpenToggle, onEditModeToggle }: INav) => import("react/jsx-runtime").JSX.Element;
|
package/esm/nav/nav.js
CHANGED
|
@@ -51,7 +51,7 @@ const NavContent = ({ children }) => {
|
|
|
51
51
|
/**
|
|
52
52
|
* @beta This component is not fully ready yet and may change in future releases.
|
|
53
53
|
*/
|
|
54
|
-
export const Nav = ({ children, className, collapsed = false, onCollapseToggle, onMenuVisibilityToggle, mobileBarContainer, isMobileMenuOpen = false, onIsMobileMenuOpenToggle
|
|
54
|
+
export const Nav = ({ children, className, collapsed = false, onCollapseToggle, onMenuVisibilityToggle, mobileBarContainer, isMobileMenuOpen = false, onIsMobileMenuOpenToggle, onEditModeToggle
|
|
55
55
|
// eslint-disable-next-line complexity
|
|
56
56
|
}) => {
|
|
57
57
|
const menuRef = useRef(null);
|
|
@@ -94,7 +94,8 @@ export const Nav = ({ children, className, collapsed = false, onCollapseToggle,
|
|
|
94
94
|
const onEditStateToggle = useCallback((isEdit) => {
|
|
95
95
|
isEdit && !isMobile && onCollapseToggle && onCollapseToggle(false);
|
|
96
96
|
editListItems && editFooter && setIsEditState(isEdit);
|
|
97
|
-
|
|
97
|
+
onEditModeToggle && onEditModeToggle(isEdit);
|
|
98
|
+
}, [onCollapseToggle, setIsEditState, editListItems, editFooter, isMobile, onEditModeToggle]);
|
|
98
99
|
useEffect(() => {
|
|
99
100
|
const wasMobile = prevIsMobileRef.current;
|
|
100
101
|
prevIsMobileRef.current = isMobile;
|
|
@@ -9,10 +9,11 @@ const marksClassNames = {
|
|
|
9
9
|
dragging: "zen-nav-edit-list__item--dragging",
|
|
10
10
|
dragged: "zen-nav-edit-list__item--dragged"
|
|
11
11
|
};
|
|
12
|
+
const EDIT_ITEM_GAP = 10;
|
|
12
13
|
export const NavEditList = ({ children, className, onItemsChangeOrder, onBeforeItemDrag }) => {
|
|
13
14
|
const appsListRef = useRef(null);
|
|
14
15
|
const dragHandler = onItemsChangeOrder ? onItemsChangeOrder : () => { };
|
|
15
|
-
const { onDragStart } = useDragAndDrop(appsListRef, dragHandler, marksClassNames, onBeforeItemDrag);
|
|
16
|
+
const { onDragStart } = useDragAndDrop(appsListRef, dragHandler, marksClassNames, onBeforeItemDrag, EDIT_ITEM_GAP);
|
|
16
17
|
const childrenArray = useMemo(() => onItemsChangeOrder ? Children.map(children, child => attachDndHandler(child, onDragStart)) : children, [children, onItemsChangeOrder, onDragStart]);
|
|
17
18
|
return _jsx("div", { className: classNames(["zen-nav-edit-list", className || ""]), ref: appsListRef, children: childrenArray });
|
|
18
19
|
};
|
|
@@ -27,6 +27,7 @@ export interface ILinkNavItem extends INavItemCommon {
|
|
|
27
27
|
export interface IButtonNavItem extends INavItemCommon {
|
|
28
28
|
onClick?: () => void;
|
|
29
29
|
menuAlignment?: TAlignment;
|
|
30
|
+
onDropdownStateChange?: (isOpen: boolean) => void;
|
|
30
31
|
}
|
|
31
32
|
interface INavItemContext {
|
|
32
33
|
parentLevel?: number;
|
|
@@ -11,7 +11,7 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
11
11
|
};
|
|
12
12
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
13
13
|
import { classNames } from "../../commonHelpers/classNames/classNames";
|
|
14
|
-
import { useRef, useState, Children, cloneElement, createContext, useContext } from "react";
|
|
14
|
+
import { useRef, useState, useEffect, Children, cloneElement, createContext, useContext } from "react";
|
|
15
15
|
import { IconBeta } from "../../icons/iconBeta";
|
|
16
16
|
import { IconChevronRight } from "../../icons/iconChevronRight";
|
|
17
17
|
import { IconOpen2 } from "../../icons/iconOpen2";
|
|
@@ -70,9 +70,12 @@ const LinkNavItem = (_a) => {
|
|
|
70
70
|
};
|
|
71
71
|
LinkNavItem.displayName = "LinkNavItem";
|
|
72
72
|
const ButtonNavItem = (_a) => {
|
|
73
|
-
var { className, title, onClick, collapsed, active, children, tabIndex, menuAlignment = "right-top", isMenuItem = true, tooltipAlignment = defaultTooltipAlignment, actionIcon, actionIconTitle, onActionIconClick } = _a, rest = __rest(_a, ["className", "title", "onClick", "collapsed", "active", "children", "tabIndex", "menuAlignment", "isMenuItem", "tooltipAlignment", "actionIcon", "actionIconTitle", "onActionIconClick"]);
|
|
73
|
+
var { className, title, onClick, collapsed, active, children, tabIndex, menuAlignment = "right-top", isMenuItem = true, tooltipAlignment = defaultTooltipAlignment, actionIcon, actionIconTitle, onActionIconClick, onDropdownStateChange } = _a, rest = __rest(_a, ["className", "title", "onClick", "collapsed", "active", "children", "tabIndex", "menuAlignment", "isMenuItem", "tooltipAlignment", "actionIcon", "actionIconTitle", "onActionIconClick", "onDropdownStateChange"]);
|
|
74
74
|
const [menuOpen, setMenuOpen] = useState(false);
|
|
75
75
|
const { parentLevel = 0, closeParentMenu } = useContext(NavItemContext);
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
onDropdownStateChange === null || onDropdownStateChange === void 0 ? void 0 : onDropdownStateChange(menuOpen);
|
|
78
|
+
}, [menuOpen, onDropdownStateChange]);
|
|
76
79
|
const triggerRef = useRef(null);
|
|
77
80
|
const currentLevel = parentLevel + 1;
|
|
78
81
|
const validChildren = Children.toArray(children).filter(child => {
|
|
@@ -7,10 +7,11 @@ interface IPillContent {
|
|
|
7
7
|
descriptionText?: TLazyContent<ReactNode>;
|
|
8
8
|
mainAction?: TLazyContent<IPillExpandableAction>;
|
|
9
9
|
secondaryAction?: TLazyContent<IPillExpandableAction>;
|
|
10
|
+
tertiaryAction?: TLazyContent<IPillExpandableAction>;
|
|
10
11
|
onActionClick: () => void;
|
|
11
12
|
isVisible?: boolean;
|
|
12
13
|
errorHandler: (err: any) => ReactNode | undefined;
|
|
13
14
|
descriptionId?: string;
|
|
14
15
|
}
|
|
15
|
-
export declare const PillContent: ({ date, count, descriptionText, mainAction, secondaryAction, onActionClick, isVisible, errorHandler, descriptionId }: IPillContent) => import("react/jsx-runtime").JSX.Element;
|
|
16
|
+
export declare const PillContent: ({ date, count, descriptionText, mainAction, secondaryAction, tertiaryAction, onActionClick, isVisible, errorHandler, descriptionId }: IPillContent) => import("react/jsx-runtime").JSX.Element;
|
|
16
17
|
export {};
|
|
@@ -4,7 +4,7 @@ import { Button } from "../button/button";
|
|
|
4
4
|
import { classNames } from "../commonHelpers/classNames/classNames";
|
|
5
5
|
import { useDriveClassName } from "../utils/theme/useDriveClassName";
|
|
6
6
|
import { LazyContent } from "../skeleton/lazyContent";
|
|
7
|
-
export const PillContent = ({ date, count, descriptionText, mainAction, secondaryAction, onActionClick, isVisible = true, errorHandler, descriptionId }) => {
|
|
7
|
+
export const PillContent = ({ date, count, descriptionText, mainAction, secondaryAction, tertiaryAction, onActionClick, isVisible = true, errorHandler, descriptionId }) => {
|
|
8
8
|
const headerDriveClassName = useDriveClassName("zen-status-pill-popup__header-text");
|
|
9
9
|
const descriptionDriveClassName = useDriveClassName("zen-status-pill-popup__description");
|
|
10
10
|
const mainActionClick = useCallback((onClick) => () => {
|
|
@@ -15,11 +15,16 @@ export const PillContent = ({ date, count, descriptionText, mainAction, secondar
|
|
|
15
15
|
onClick && onClick();
|
|
16
16
|
onActionClick();
|
|
17
17
|
}, [onActionClick]);
|
|
18
|
+
const tertiaryActionClick = useCallback((onClick) => () => {
|
|
19
|
+
onClick && onClick();
|
|
20
|
+
onActionClick();
|
|
21
|
+
}, [onActionClick]);
|
|
18
22
|
const renderMainButton = useCallback((data) => _jsx(Button, Object.assign({ link: data.link }, data.linkOptions, { onClick: mainActionClick(data.onClick), type: "primary", children: data.text })), [mainActionClick]);
|
|
19
|
-
const renderSecondaryButton = useCallback((data) => _jsx(Button, Object.assign({ link: data.link }, data.linkOptions, { onClick: secondaryActionClick(data.onClick), type: "
|
|
23
|
+
const renderSecondaryButton = useCallback((data) => _jsx(Button, Object.assign({ link: data.link }, data.linkOptions, { onClick: secondaryActionClick(data.onClick), type: "secondary", children: data.text })), [secondaryActionClick]);
|
|
24
|
+
const renderTertiaryButton = useCallback((data) => _jsx(Button, Object.assign({ link: data.link }, data.linkOptions, { onClick: tertiaryActionClick(data.onClick), type: "tertiary", children: data.text })), [tertiaryActionClick]);
|
|
20
25
|
const renderDescription = useCallback(data => _jsx("div", { id: descriptionId, className: classNames(["zen-status-pill-popup__description", descriptionDriveClassName || ""]), children: data }), [descriptionDriveClassName, descriptionId]);
|
|
21
26
|
const renderCount = useCallback((data) => data ? _jsx("div", { className: classNames(["zen-status-pill-popup__header-text zen-status-pill-popup__header-text--left", headerDriveClassName || ""]), children: data }) : null, [headerDriveClassName]);
|
|
22
27
|
const renderDate = useCallback((data) => data ? _jsx("div", { className: classNames(["zen-status-pill-popup__header-text", headerDriveClassName || ""]), children: data }) : null, [headerDriveClassName]);
|
|
23
28
|
return _jsxs(_Fragment, { children: [(date || count) && isVisible && _jsxs("div", { className: "zen-status-pill-popup__content-header", children: [date && _jsx(LazyContent, { width: 80, type: "text", content: date, isVisible: isVisible, errorHandler: errorHandler, render: renderDate }), count && _jsx(LazyContent, { width: 80, type: "text", content: count, isVisible: isVisible, errorHandler: errorHandler, render: renderCount })] }), descriptionText &&
|
|
24
|
-
_jsx(LazyContent, { width: 200, type: "text", content: descriptionText, isVisible: isVisible, errorHandler: errorHandler, render: renderDescription }), (mainAction || secondaryAction) && isVisible && _jsxs("div", { className: "zen-status-pill-popup__actions", children: [mainAction && _jsx(LazyContent, { type: "button", content: mainAction, errorHandler: errorHandler, render: renderMainButton, isVisible: isVisible }), secondaryAction && _jsx(LazyContent, { type: "button", content: secondaryAction, errorHandler: errorHandler, render: renderSecondaryButton, isVisible: isVisible })] })] });
|
|
29
|
+
_jsx(LazyContent, { width: 200, type: "text", content: descriptionText, isVisible: isVisible, errorHandler: errorHandler, render: renderDescription }), (mainAction || secondaryAction || tertiaryAction) && isVisible && _jsxs("div", { className: "zen-status-pill-popup__actions", children: [mainAction && _jsx(LazyContent, { type: "button", content: mainAction, errorHandler: errorHandler, render: renderMainButton, isVisible: isVisible }), secondaryAction && _jsx(LazyContent, { type: "button", content: secondaryAction, errorHandler: errorHandler, render: renderSecondaryButton, isVisible: isVisible }), tertiaryAction && _jsx(LazyContent, { type: "button", content: tertiaryAction, errorHandler: errorHandler, render: renderTertiaryButton, isVisible: isVisible })] })] });
|
|
25
30
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import "./pillExpandable.less";
|
|
2
2
|
import { IZenComponentProps } from "../commonHelpers/zenComponent";
|
|
3
|
+
import { Exclusive } from "../commonHelpers/types/exclusive";
|
|
3
4
|
import React, { FC, ReactNode } from "react";
|
|
4
5
|
import { TViewMoreButton } from "./getProps";
|
|
5
6
|
import { IIcon } from "../icons/icon";
|
|
@@ -20,6 +21,7 @@ export interface IBaseData {
|
|
|
20
21
|
count?: TLazyContent<number>;
|
|
21
22
|
mainAction?: TLazyContent<IPillExpandableAction>;
|
|
22
23
|
secondaryAction?: TLazyContent<IPillExpandableAction>;
|
|
24
|
+
tertiaryAction?: TLazyContent<IPillExpandableAction>;
|
|
23
25
|
getData?: undefined;
|
|
24
26
|
}
|
|
25
27
|
export interface IPromisedBaseData {
|
|
@@ -29,16 +31,20 @@ export interface IPromisedBaseData {
|
|
|
29
31
|
count?: undefined;
|
|
30
32
|
mainAction?: undefined;
|
|
31
33
|
secondaryAction?: undefined;
|
|
34
|
+
tertiaryAction?: undefined;
|
|
32
35
|
}
|
|
33
36
|
type TProps = IBaseData | IPromisedBaseData;
|
|
34
|
-
export
|
|
37
|
+
export type TPopoverVariant = "default" | "placeholder";
|
|
38
|
+
export type TPopoverSize = "small" | "medium" | "large";
|
|
39
|
+
type TPillTypeBase = "error" | "success" | "warning" | "info" | "default";
|
|
40
|
+
export type TPillType = TPillTypeBase | "upsell";
|
|
41
|
+
/** Base props shared by all PillExpandable variants */
|
|
42
|
+
export interface IPillExpandableBase extends IZenComponentProps {
|
|
35
43
|
uniquePills?: number;
|
|
36
44
|
text: string;
|
|
37
|
-
type?: "error" | "success" | "warning" | "info" | "default";
|
|
38
45
|
viewMoreAction?: TViewMoreButton;
|
|
39
46
|
className?: string;
|
|
40
47
|
popupClassName?: string;
|
|
41
|
-
icon?: FC<IIcon> | false;
|
|
42
48
|
secondaryIcon?: FC<IIcon> | false;
|
|
43
49
|
isFlat?: boolean;
|
|
44
50
|
isLoading?: boolean;
|
|
@@ -47,8 +53,25 @@ export interface IPillExpandable extends IZenComponentProps {
|
|
|
47
53
|
isBeta?: boolean;
|
|
48
54
|
hideCounterNumber?: boolean;
|
|
49
55
|
autoExpandChildren?: boolean;
|
|
56
|
+
/** Popover size: small (220px), medium (320px), large (420px). Default: "medium" */
|
|
57
|
+
popoverSize?: TPopoverSize;
|
|
58
|
+
/** Title displayed in the popover title bar. If provided, the title bar will be shown. */
|
|
59
|
+
title?: string;
|
|
60
|
+
}
|
|
61
|
+
/** Props for standard PillExpandable (non-upsell) */
|
|
62
|
+
interface IPillExpandableStandard extends IPillExpandableBase {
|
|
63
|
+
type?: TPillTypeBase;
|
|
64
|
+
icon?: FC<IIcon> | false;
|
|
65
|
+
}
|
|
66
|
+
/** Props for upsell PillExpandable - icon and children are not allowed */
|
|
67
|
+
interface IPillExpandableUpsell extends IPillExpandableBase {
|
|
68
|
+
type: "upsell";
|
|
69
|
+
icon?: never;
|
|
70
|
+
children?: never;
|
|
50
71
|
}
|
|
72
|
+
/** Combined type using Exclusive to prevent mixing upsell with standard props */
|
|
73
|
+
export type IPillExpandable = Exclusive<IPillExpandableStandard, IPillExpandableUpsell>;
|
|
51
74
|
type TPillExpandable = IPillExpandable & TProps;
|
|
52
|
-
export declare const PillExpandable: React.MemoExoticComponent<({ children, errorHandler, getData, text, description, date, count, mainAction, secondaryAction, viewMoreAction, uniquePills, type, className, popupClassName, isLoading, isFlat, icon, secondaryIcon, isBeta, loadingWidth, hideCounterNumber, autoExpandChildren }: TPillExpandable) => import("react/jsx-runtime").JSX.Element>;
|
|
75
|
+
export declare const PillExpandable: React.MemoExoticComponent<({ children, errorHandler, getData, text, description, date, count, mainAction, secondaryAction, tertiaryAction, viewMoreAction, uniquePills, type, className, popupClassName, isLoading, isFlat, icon, secondaryIcon, isBeta, loadingWidth, hideCounterNumber, autoExpandChildren, popoverSize, title }: TPillExpandable) => import("react/jsx-runtime").JSX.Element>;
|
|
53
76
|
export declare const TRANSLATIONS: string[];
|
|
54
77
|
export {};
|