@geotab/zenith 3.6.3 → 3.7.0-beta.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.
Files changed (75) hide show
  1. package/README.md +14 -0
  2. package/dist/absolute/absolute.d.ts +3 -1
  3. package/dist/absolute/absolute.js +4 -4
  4. package/dist/controlledPopup/controlledPopup.d.ts +3 -1
  5. package/dist/controlledPopup/controlledPopup.js +2 -2
  6. package/dist/groupsFilterRaw/groupsFilterCurrentlySelectedState.js +8 -2
  7. package/dist/groupsFilterRaw/groupsFilterRaw.js +43 -9
  8. package/dist/groupsFilterRaw/groupsFilterTrigger.d.ts +2 -1
  9. package/dist/groupsFilterRaw/groupsFilterTrigger.js +9 -2
  10. package/dist/index.css +208 -18
  11. package/dist/index.d.ts +1 -1
  12. package/dist/list/hooks/useDragAndDrop.d.ts +3 -1
  13. package/dist/list/hooks/useDragAndDrop.js +11 -4
  14. package/dist/menu/components/menuItem.js +12 -2
  15. package/dist/menu/contexts/pathContext.d.ts +3 -1
  16. package/dist/menu/contexts/pathProvider.d.ts +1 -1
  17. package/dist/menu/contexts/pathProvider.js +1 -1
  18. package/dist/menu/controlledMenu.js +97 -29
  19. package/dist/nav/nav.d.ts +2 -1
  20. package/dist/nav/nav.js +3 -2
  21. package/dist/nav/navEditList/navEditList.js +2 -1
  22. package/dist/pillExpandable/pillContent.d.ts +2 -1
  23. package/dist/pillExpandable/pillContent.js +8 -3
  24. package/dist/pillExpandable/pillExpandable.d.ts +27 -4
  25. package/dist/pillExpandable/pillExpandable.js +426 -196
  26. package/dist/pillExpandable/pillExpandablePopoverContent.d.ts +19 -0
  27. package/dist/pillExpandable/pillExpandablePopoverContent.js +7 -0
  28. package/dist/pillExpandable/pillExpandableSimple.d.ts +5 -3
  29. package/dist/pillExpandable/pillExpandableSimple.js +12 -5
  30. package/dist/summary/summary.js +29 -1
  31. package/dist/summaryTile/summaryTile.d.ts +2 -1
  32. package/dist/summaryTile/summaryTile.js +175 -65
  33. package/dist/summaryTile/summaryTileTrigger.d.ts +1 -1
  34. package/dist/summaryTile/summaryTileTrigger.js +1 -1
  35. package/dist/toggleButtonRaw/toggleButtonRaw.js +2 -1
  36. package/dist/utils/localization/getSupportedLanguage.d.ts +2 -2
  37. package/dist/utils/localization/getSupportedLanguage.js +28 -8
  38. package/dist/utils/localization/languageContext.d.ts +1 -1
  39. package/esm/absolute/absolute.d.ts +3 -1
  40. package/esm/absolute/absolute.js +4 -4
  41. package/esm/controlledPopup/controlledPopup.d.ts +3 -1
  42. package/esm/controlledPopup/controlledPopup.js +2 -2
  43. package/esm/groupsFilterRaw/groupsFilterCurrentlySelectedState.js +9 -3
  44. package/esm/groupsFilterRaw/groupsFilterRaw.js +43 -9
  45. package/esm/groupsFilterRaw/groupsFilterTrigger.d.ts +2 -1
  46. package/esm/groupsFilterRaw/groupsFilterTrigger.js +9 -2
  47. package/esm/index.d.ts +1 -1
  48. package/esm/list/hooks/useDragAndDrop.d.ts +3 -1
  49. package/esm/list/hooks/useDragAndDrop.js +11 -4
  50. package/esm/menu/components/menuItem.js +12 -2
  51. package/esm/menu/contexts/pathContext.d.ts +3 -1
  52. package/esm/menu/contexts/pathProvider.d.ts +1 -1
  53. package/esm/menu/contexts/pathProvider.js +1 -1
  54. package/esm/menu/controlledMenu.js +98 -30
  55. package/esm/nav/nav.d.ts +2 -1
  56. package/esm/nav/nav.js +3 -2
  57. package/esm/nav/navEditList/navEditList.js +2 -1
  58. package/esm/pillExpandable/pillContent.d.ts +2 -1
  59. package/esm/pillExpandable/pillContent.js +8 -3
  60. package/esm/pillExpandable/pillExpandable.d.ts +27 -4
  61. package/esm/pillExpandable/pillExpandable.js +427 -197
  62. package/esm/pillExpandable/pillExpandablePopoverContent.d.ts +19 -0
  63. package/esm/pillExpandable/pillExpandablePopoverContent.js +3 -0
  64. package/esm/pillExpandable/pillExpandableSimple.d.ts +5 -3
  65. package/esm/pillExpandable/pillExpandableSimple.js +12 -5
  66. package/esm/summary/summary.js +29 -1
  67. package/esm/summaryTile/summaryTile.d.ts +2 -1
  68. package/esm/summaryTile/summaryTile.js +140 -43
  69. package/esm/summaryTile/summaryTileTrigger.d.ts +1 -1
  70. package/esm/summaryTile/summaryTileTrigger.js +1 -1
  71. package/esm/toggleButtonRaw/toggleButtonRaw.js +2 -1
  72. package/esm/utils/localization/getSupportedLanguage.d.ts +2 -2
  73. package/esm/utils/localization/getSupportedLanguage.js +28 -8
  74. package/esm/utils/localization/languageContext.d.ts +1 -1
  75. package/package.json +4 -4
@@ -17,9 +17,11 @@ const zen_1 = require("../../utils/zen");
17
17
  * If not provided, defaults to 'zen-list-item' based classes
18
18
  * @param onBeforeDragOver - Optional callback function called before placing the placeholder at a new position.
19
19
  * Receives the target position and should return false to prevent the drag operation.
20
+ * @param itemsGap - Optional gap size in pixels between list items. Used to adjust placeholder positioning
21
+ * calculations during drag operations. Defaults to 0.
20
22
  * @returns An object containing the `onDragStart` event handler to be attached to draggable elements
21
23
  */
22
- const useDragAndDrop = (containerRef, onChangeOrder, marksClassNames, onBeforeDragOver) => {
24
+ const useDragAndDrop = (containerRef, onChangeOrder, marksClassNames, onBeforeDragOver, itemsGap = 0) => {
23
25
  const dragImageRef = (0, react_1.useRef)(null);
24
26
  const dragMoveRef = (0, react_1.useRef)(undefined);
25
27
  const { moveScroll, cancelMoveScroll } = (0, useContainerScroll_1.useContainerScroll)();
@@ -50,8 +52,12 @@ const useDragAndDrop = (containerRef, onChangeOrder, marksClassNames, onBeforeDr
50
52
  if (!containerRef.current) {
51
53
  return;
52
54
  }
53
- const items = containerRef.current.querySelectorAll(`.${cssMarks.general}`);
54
55
  const rect = element.getBoundingClientRect();
56
+ // preventing placeholder alignment when dragged element is not properly positioned yet
57
+ if (rect.top < 0) {
58
+ return;
59
+ }
60
+ const items = containerRef.current.querySelectorAll(`.${cssMarks.general}`);
55
61
  const listRect = containerRef.current.getBoundingClientRect();
56
62
  if (rect.top < listRect.top) {
57
63
  if (onBeforeDragOver && !onBeforeDragOver(0)) {
@@ -79,13 +85,14 @@ const useDragAndDrop = (containerRef, onChangeOrder, marksClassNames, onBeforeDr
79
85
  return;
80
86
  }
81
87
  const dragItemTopOffset = 4;
88
+ const halfGap = itemsGap / 2;
82
89
  for (let i = 0; i < items.length; i++) {
83
90
  const item = items[i];
84
91
  if (item.classList.contains(cssMarks.placeholder) || item.classList.contains(cssMarks.dragging)) {
85
92
  continue;
86
93
  }
87
94
  const itemRect = item.getBoundingClientRect();
88
- if (item !== element && rect.top > itemRect.top + dragItemTopOffset && rect.top <= itemRect.bottom) {
95
+ if (item !== element && rect.top > itemRect.top + dragItemTopOffset - halfGap && rect.top <= itemRect.bottom + halfGap) {
89
96
  if (onBeforeDragOver && !onBeforeDragOver(i + 1)) {
90
97
  return;
91
98
  }
@@ -101,7 +108,7 @@ const useDragAndDrop = (containerRef, onChangeOrder, marksClassNames, onBeforeDr
101
108
  return;
102
109
  }
103
110
  }
104
- }, [containerRef, getPlaceholder, removePlaceholders, onBeforeDragOver, cssMarks.general, cssMarks.placeholder, cssMarks.dragging]);
111
+ }, [containerRef, getPlaceholder, removePlaceholders, onBeforeDragOver, cssMarks.general, cssMarks.placeholder, cssMarks.dragging, itemsGap]);
105
112
  const onDragStart = (0, react_1.useCallback)((e) => {
106
113
  var _a, _b, _c, _d, _e;
107
114
  e.preventDefault();
@@ -28,7 +28,7 @@ exports.isMenuItem = isMenuItem;
28
28
  const MenuItem = ({ id, children, name, icon, disabled, onClick, link, target, rel, isMobile = false, setIsOpen, trigger, className, active, alignment }) => {
29
29
  const aligmentContext = (0, react_1.useContext)(headerContext_1.MenuAlignmentContext);
30
30
  const contentAlignment = alignment || aligmentContext.alignment || "right-top";
31
- const { path, onOpenBranch, closeBranch } = (0, react_1.useContext)(pathContext_1.PathContext);
31
+ const { path, onOpenBranch, closeBranch, navigatedViaKeyboardRef } = (0, react_1.useContext)(pathContext_1.PathContext);
32
32
  const memoizedDesktopActionOnClick = (0, react_1.useCallback)((itemId, e) => {
33
33
  setIsOpen === null || setIsOpen === void 0 ? void 0 : setIsOpen(false);
34
34
  onClick === null || onClick === void 0 ? void 0 : onClick(itemId, e);
@@ -87,6 +87,16 @@ const MenuItem = ({ id, children, name, icon, disabled, onClick, link, target, r
87
87
  return cont;
88
88
  }, [children, isMobile, setIsOpen, className]);
89
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;
90
100
  if (content.length === 0) {
91
101
  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);
92
102
  }
@@ -103,7 +113,7 @@ const MenuItem = ({ id, children, name, icon, disabled, onClick, link, target, r
103
113
  else {
104
114
  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);
105
115
  }
106
- 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: "on", alignment: contentAlignment, triggerRef: ref, isOpen: isOpen, onOpenChange: memoizedOnOpenChange, ariaLabel: popupTrigger.props.name, recalculateOnScroll: true, children: (0, jsx_runtime_1.jsx)("ul", { role: "menu", className: "zen-menu-item", children: content }) })] }, id);
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: memoizedOnOpenChange, ariaLabel: popupTrigger.props.name, recalculateOnScroll: true, children: (0, jsx_runtime_1.jsx)("ul", { role: "menu", className: "zen-menu-item", children: content }) })] }, id);
107
117
  };
108
118
  exports.MenuItem = MenuItem;
109
119
  exports.MenuItem.displayName = "MenuItem";
@@ -1,7 +1,9 @@
1
- /// <reference types="react" />
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 {};
@@ -3,5 +3,5 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.PathProvider = void 0;
4
4
  const jsx_runtime_1 = require("react/jsx-runtime");
5
5
  const pathContext_1 = require("./pathContext");
6
- const PathProvider = ({ children, path, onOpenBranch, closeBranch }) => ((0, jsx_runtime_1.jsx)(pathContext_1.PathContext.Provider, { value: { path, onOpenBranch, closeBranch }, children: children }));
6
+ const PathProvider = ({ children, path, onOpenBranch, closeBranch, navigatedViaKeyboardRef, keyboardActiveRef }) => ((0, jsx_runtime_1.jsx)(pathContext_1.PathContext.Provider, { value: { path, onOpenBranch, closeBranch, navigatedViaKeyboardRef, keyboardActiveRef }, children: children }));
7
7
  exports.PathProvider = PathProvider;
@@ -22,17 +22,57 @@ const useDeviceType_1 = require("../commonHelpers/hooks/useDeviceType");
22
22
  const utils_1 = require("../commonHelpers/utils");
23
23
  const pathProvider_1 = require("./contexts/pathProvider");
24
24
  const menuSeparator_1 = require("./components/menuSeparator");
25
+ const focusableSelector_1 = require("../utils/focusableSelector");
25
26
  const ControlledMenu = ({ children, isOpen, setIsOpen, triggerRef, ariaLabel, ariaLabelledby, id, title, className = "", listClassName = "", paddingX = 0, paddingY = 0, alignment }) => {
26
27
  const [deviceType, setDeviceType] = (0, react_1.useState)(deviceType_1.DeviceType.Desktop);
27
28
  const isMobile = deviceType === deviceType_1.DeviceType.Mobile;
28
29
  const memoizedOnChange = (0, react_1.useCallback)(setIsOpen, [setIsOpen]);
29
30
  (0, useDeviceType_1.useDeviceType)(setDeviceType);
31
+ const menuListRef = (0, react_1.useRef)(null);
30
32
  const [path, setPath] = (0, react_1.useState)([]);
33
+ // Track if the trigger was activated via keyboard
34
+ const openedViaKeyboardRef = (0, react_1.useRef)(false);
35
+ // Track if submenu was navigated via keyboard (ArrowRight)
36
+ const navigatedViaKeyboardRef = (0, react_1.useRef)(false);
37
+ // Track if keyboard is actively being used for navigation (vs mouse/touch)
38
+ const keyboardActiveRef = (0, react_1.useRef)(false);
39
+ // Listen for keyboard activation on trigger
40
+ (0, react_1.useEffect)(() => {
41
+ const trigger = triggerRef.current;
42
+ if (!trigger)
43
+ return undefined;
44
+ const handleKeyDown = (e) => {
45
+ if (e.key === "Enter" || e.key === " ") {
46
+ openedViaKeyboardRef.current = true;
47
+ }
48
+ };
49
+ const handleMouseDown = () => {
50
+ openedViaKeyboardRef.current = false;
51
+ };
52
+ trigger.addEventListener("keydown", handleKeyDown);
53
+ trigger.addEventListener("mousedown", handleMouseDown);
54
+ return () => {
55
+ trigger.removeEventListener("keydown", handleKeyDown);
56
+ trigger.removeEventListener("mousedown", handleMouseDown);
57
+ };
58
+ }, [triggerRef]);
31
59
  (0, react_1.useEffect)(() => {
32
60
  if (path.length && !isOpen) {
33
61
  setPath([]);
34
62
  }
35
63
  }, [isOpen, path, setPath]);
64
+ // Focus the menu list container or first item when menu opens (for keyboard navigation)
65
+ (0, react_1.useEffect)(() => {
66
+ var _a;
67
+ if (isOpen && !isMobile && menuListRef.current) {
68
+ if (openedViaKeyboardRef.current) {
69
+ (_a = menuListRef.current.querySelector(focusableSelector_1.FOCUSABLE_SELECTOR)) === null || _a === void 0 ? void 0 : _a.focus();
70
+ }
71
+ else {
72
+ menuListRef.current.focus();
73
+ }
74
+ }
75
+ }, [isOpen, isMobile]);
36
76
  const onOpenBranch = (0, react_1.useCallback)((branchId) => {
37
77
  if (!branchId) {
38
78
  return;
@@ -110,48 +150,76 @@ const ControlledMenu = ({ children, isOpen, setIsOpen, triggerRef, ariaLabel, ar
110
150
  });
111
151
  return [cont, par];
112
152
  }, [children, isMobile, path, setIsOpen]);
153
+ // Handle keyboard navigation when no menu item is focused (e.g., when menu just opened)
154
+ const handleUnfocusedKeyDown = (e, menuList) => {
155
+ var _a, _b;
156
+ if (e.key === "ArrowDown" || e.key === "Home") {
157
+ e.preventDefault();
158
+ (_a = (0, findFirstFocusable_1.findFirstFocusable)(menuList)) === null || _a === void 0 ? void 0 : _a.focus();
159
+ return true;
160
+ }
161
+ if (e.key === "ArrowUp" || e.key === "End") {
162
+ e.preventDefault();
163
+ (_b = (0, findLastFocusable_1.findLastFocusable)(menuList)) === null || _b === void 0 ? void 0 : _b.focus();
164
+ return true;
165
+ }
166
+ return false;
167
+ };
168
+ const onMouseDown = () => {
169
+ keyboardActiveRef.current = false;
170
+ };
113
171
  const onKeyDown = (e) => {
114
172
  var _a, _b, _c, _d;
173
+ keyboardActiveRef.current = true;
115
174
  const target = e.target;
116
- if ((0, isButton_1.isButton)(target) || (0, isLink_1.isLink)(target)) {
117
- if (e.key === "ArrowDown") {
118
- e.preventDefault();
119
- (_a = (0, findNextFocusable_1.findNextFocusable)(target)) === null || _a === void 0 ? void 0 : _a.focus();
120
- return;
121
- }
122
- if (e.key === "ArrowUp") {
123
- e.preventDefault();
124
- (_b = (0, findPrevFocusable_1.findPrevFocusable)(target)) === null || _b === void 0 ? void 0 : _b.focus();
125
- return;
126
- }
127
- if (e.key === "Home") {
128
- e.preventDefault();
129
- (_c = (0, findFirstFocusable_1.findFirstFocusable)(target)) === null || _c === void 0 ? void 0 : _c.focus();
130
- return;
131
- }
132
- if (e.key === "End") {
133
- e.preventDefault();
134
- (_d = (0, findLastFocusable_1.findLastFocusable)(target)) === null || _d === void 0 ? void 0 : _d.focus();
135
- return;
175
+ const currentTarget = e.currentTarget;
176
+ if (!(0, isButton_1.isButton)(target) && !(0, isLink_1.isLink)(target)) {
177
+ if (target === currentTarget) {
178
+ const menuList = currentTarget.querySelector("ul");
179
+ if (menuList) {
180
+ handleUnfocusedKeyDown(e, menuList);
181
+ }
136
182
  }
183
+ return;
137
184
  }
138
- if ((0, isButton_1.isButton)(target)) {
139
- if (e.key === "ArrowRight" && target.classList.contains("zen-menu-button__action--has-children")) {
140
- e.preventDefault();
141
- target.click();
142
- return;
143
- }
185
+ if (e.key === "ArrowDown") {
186
+ e.preventDefault();
187
+ (_a = (0, findNextFocusable_1.findNextFocusable)(target)) === null || _a === void 0 ? void 0 : _a.focus();
188
+ return;
189
+ }
190
+ if (e.key === "ArrowUp") {
191
+ e.preventDefault();
192
+ (_b = (0, findPrevFocusable_1.findPrevFocusable)(target)) === null || _b === void 0 ? void 0 : _b.focus();
193
+ return;
194
+ }
195
+ if (e.key === "Home") {
196
+ e.preventDefault();
197
+ (_c = (0, findFirstFocusable_1.findFirstFocusable)(target)) === null || _c === void 0 ? void 0 : _c.focus();
198
+ return;
199
+ }
200
+ if (e.key === "End") {
201
+ e.preventDefault();
202
+ (_d = (0, findLastFocusable_1.findLastFocusable)(target)) === null || _d === void 0 ? void 0 : _d.focus();
203
+ return;
204
+ }
205
+ if ((0, isButton_1.isButton)(target) && (e.key === "ArrowRight" || e.key === "Enter" || e.key === " ") && target.classList.contains("zen-menu-button__action--has-children")) {
206
+ e.preventDefault();
207
+ navigatedViaKeyboardRef.current = true;
208
+ target.click();
144
209
  }
145
210
  };
146
- const renderMenuList = () => (0, jsx_runtime_1.jsx)("div", { onKeyDown: onKeyDown, className: (0, classNames_1.classNames)(["zen-action-list", className]), children: (0, jsx_runtime_1.jsxs)("ul", { role: "menu", className: (0, classNames_1.classNames)(["zen-menu-item", className, listClassName]), children: [parent ? (0, jsx_runtime_1.jsx)(menuButton_1.MenuButton, { id: "root", name: parent.props.name || "", icon: iconArrowLeft_1.IconArrowLeft, onClick: closeBranch, hasChildren: false, disabled: false }, "root") : null, content] }) });
211
+ const renderMenuList = () => (0, jsx_runtime_1.jsx)("div", { ref: menuListRef, tabIndex: -1, onKeyDown: onKeyDown, onMouseDown: onMouseDown, className: (0, classNames_1.classNames)(["zen-action-list", className]), children: (0, jsx_runtime_1.jsxs)("ul", { role: "menu", className: (0, classNames_1.classNames)(["zen-menu-item", className, listClassName]), children: [parent ? (0, jsx_runtime_1.jsx)(menuButton_1.MenuButton, { id: "root", name: parent.props.name || "", icon: iconArrowLeft_1.IconArrowLeft, onClick: closeBranch, hasChildren: false, disabled: false }, "root") : null, content] }) });
147
212
  const hideMenu = (0, react_1.useCallback)(() => {
148
213
  closeBranch();
149
214
  setIsOpen(false);
150
215
  }, [closeBranch, setIsOpen]);
151
216
  if (isMobile) {
152
- return (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: (0, jsx_runtime_1.jsx)(pathProvider_1.PathProvider, { path: path, onOpenBranch: onOpenBranch, closeBranch: closeBranch, children: (0, jsx_runtime_1.jsxs)(mobileSheet_1.MobileSheet, { label: title, isOpen: isOpen, triggerRef: triggerRef, onHidePanel: hideMenu, onCloseClick: hideMenu, children: [(0, jsx_runtime_1.jsx)(mobileSheet_1.MobileSheet.Title, { children: title }), (0, jsx_runtime_1.jsx)(mobileSheet_1.MobileSheet.Content, { children: renderMenuList() })] }) }) });
217
+ return (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: (0, jsx_runtime_1.jsx)(pathProvider_1.PathProvider, { path: path, onOpenBranch: onOpenBranch, closeBranch: closeBranch, navigatedViaKeyboardRef: navigatedViaKeyboardRef, keyboardActiveRef: keyboardActiveRef, children: (0, jsx_runtime_1.jsxs)(mobileSheet_1.MobileSheet, { label: title, isOpen: isOpen, triggerRef: triggerRef, onHidePanel: hideMenu, onCloseClick: hideMenu, children: [(0, jsx_runtime_1.jsx)(mobileSheet_1.MobileSheet.Title, { children: title }), (0, jsx_runtime_1.jsx)(mobileSheet_1.MobileSheet.Content, { children: renderMenuList() })] }) }) });
153
218
  }
154
- return (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: (0, jsx_runtime_1.jsx)(pathProvider_1.PathProvider, { path: path, onOpenBranch: onOpenBranch, closeBranch: closeBranch, children: (0, jsx_runtime_1.jsx)(controlledPopup_1.ControlledPopup, { id: id, useTrapFocusWithTrigger: "on", className: (0, classNames_1.classNames)(["zen-controlled-menu", className]), onOpenChange: memoizedOnChange, isOpen: isOpen, triggerRef: triggerRef, paddingX: paddingX, paddingY: paddingY, alignment: alignment, ariaLabelledby: ariaLabelledby, ariaLabel: ariaLabel || title, recalculateOnScroll: true, children: renderMenuList() }) }) });
219
+ return (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: (0, jsx_runtime_1.jsx)(pathProvider_1.PathProvider, { path: path, onOpenBranch: onOpenBranch, closeBranch: closeBranch, navigatedViaKeyboardRef: navigatedViaKeyboardRef, keyboardActiveRef: keyboardActiveRef, children: (0, jsx_runtime_1.jsx)(controlledPopup_1.ControlledPopup, { id: id, useTrapFocusWithTrigger: "on", className: (0, classNames_1.classNames)(["zen-controlled-menu", className]), onOpenChange: memoizedOnChange, isOpen: isOpen, triggerRef: triggerRef, paddingX: paddingX, paddingY: paddingY, alignment: alignment, ariaLabelledby: ariaLabelledby, ariaLabel: ariaLabel || title, recalculateOnScroll: true,
220
+ // focusOnOpen is false - ControlledMenu handles focus based on input method
221
+ // (keyboard vs mouse) in its own useEffect
222
+ focusOnOpen: false, children: renderMenuList() }) }) });
155
223
  };
156
224
  exports.ControlledMenu = ControlledMenu;
157
225
  exports.ControlledMenu.Item = menuItem_1.MenuItem;
package/dist/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/dist/nav/nav.js CHANGED
@@ -54,7 +54,7 @@ const NavContent = ({ children }) => {
54
54
  /**
55
55
  * @beta This component is not fully ready yet and may change in future releases.
56
56
  */
57
- const Nav = ({ children, className, collapsed = false, onCollapseToggle, onMenuVisibilityToggle, mobileBarContainer, isMobileMenuOpen = false, onIsMobileMenuOpenToggle
57
+ const Nav = ({ children, className, collapsed = false, onCollapseToggle, onMenuVisibilityToggle, mobileBarContainer, isMobileMenuOpen = false, onIsMobileMenuOpenToggle, onEditModeToggle
58
58
  // eslint-disable-next-line complexity
59
59
  }) => {
60
60
  const menuRef = (0, react_1.useRef)(null);
@@ -97,7 +97,8 @@ const Nav = ({ children, className, collapsed = false, onCollapseToggle, onMenuV
97
97
  const onEditStateToggle = (0, react_1.useCallback)((isEdit) => {
98
98
  isEdit && !isMobile && onCollapseToggle && onCollapseToggle(false);
99
99
  editListItems && editFooter && setIsEditState(isEdit);
100
- }, [onCollapseToggle, setIsEditState, editListItems, editFooter, isMobile]);
100
+ onEditModeToggle && onEditModeToggle(isEdit);
101
+ }, [onCollapseToggle, setIsEditState, editListItems, editFooter, isMobile, onEditModeToggle]);
101
102
  (0, react_1.useEffect)(() => {
102
103
  const wasMobile = prevIsMobileRef.current;
103
104
  prevIsMobileRef.current = isMobile;
@@ -12,10 +12,11 @@ const marksClassNames = {
12
12
  dragging: "zen-nav-edit-list__item--dragging",
13
13
  dragged: "zen-nav-edit-list__item--dragged"
14
14
  };
15
+ const EDIT_ITEM_GAP = 10;
15
16
  const NavEditList = ({ children, className, onItemsChangeOrder, onBeforeItemDrag }) => {
16
17
  const appsListRef = (0, react_1.useRef)(null);
17
18
  const dragHandler = onItemsChangeOrder ? onItemsChangeOrder : () => { };
18
- const { onDragStart } = (0, useDragAndDrop_1.useDragAndDrop)(appsListRef, dragHandler, marksClassNames, onBeforeItemDrag);
19
+ const { onDragStart } = (0, useDragAndDrop_1.useDragAndDrop)(appsListRef, dragHandler, marksClassNames, onBeforeItemDrag, EDIT_ITEM_GAP);
19
20
  const childrenArray = (0, react_1.useMemo)(() => onItemsChangeOrder ? react_1.Children.map(children, child => (0, navEditListUtils_1.attachDndHandler)(child, onDragStart)) : children, [children, onItemsChangeOrder, onDragStart]);
20
21
  return (0, jsx_runtime_1.jsx)("div", { className: (0, classNames_1.classNames)(["zen-nav-edit-list", className || ""]), ref: appsListRef, children: childrenArray });
21
22
  };
@@ -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 {};
@@ -7,7 +7,7 @@ const button_1 = require("../button/button");
7
7
  const classNames_1 = require("../commonHelpers/classNames/classNames");
8
8
  const useDriveClassName_1 = require("../utils/theme/useDriveClassName");
9
9
  const lazyContent_1 = require("../skeleton/lazyContent");
10
- const PillContent = ({ date, count, descriptionText, mainAction, secondaryAction, onActionClick, isVisible = true, errorHandler, descriptionId }) => {
10
+ const PillContent = ({ date, count, descriptionText, mainAction, secondaryAction, tertiaryAction, onActionClick, isVisible = true, errorHandler, descriptionId }) => {
11
11
  const headerDriveClassName = (0, useDriveClassName_1.useDriveClassName)("zen-status-pill-popup__header-text");
12
12
  const descriptionDriveClassName = (0, useDriveClassName_1.useDriveClassName)("zen-status-pill-popup__description");
13
13
  const mainActionClick = (0, react_1.useCallback)((onClick) => () => {
@@ -18,12 +18,17 @@ const PillContent = ({ date, count, descriptionText, mainAction, secondaryAction
18
18
  onClick && onClick();
19
19
  onActionClick();
20
20
  }, [onActionClick]);
21
+ const tertiaryActionClick = (0, react_1.useCallback)((onClick) => () => {
22
+ onClick && onClick();
23
+ onActionClick();
24
+ }, [onActionClick]);
21
25
  const renderMainButton = (0, react_1.useCallback)((data) => (0, jsx_runtime_1.jsx)(button_1.Button, Object.assign({ link: data.link }, data.linkOptions, { onClick: mainActionClick(data.onClick), type: "primary", children: data.text })), [mainActionClick]);
22
- const renderSecondaryButton = (0, react_1.useCallback)((data) => (0, jsx_runtime_1.jsx)(button_1.Button, Object.assign({ link: data.link }, data.linkOptions, { onClick: secondaryActionClick(data.onClick), type: "tertiary", children: data.text })), [secondaryActionClick]);
26
+ const renderSecondaryButton = (0, react_1.useCallback)((data) => (0, jsx_runtime_1.jsx)(button_1.Button, Object.assign({ link: data.link }, data.linkOptions, { onClick: secondaryActionClick(data.onClick), type: "secondary", children: data.text })), [secondaryActionClick]);
27
+ const renderTertiaryButton = (0, react_1.useCallback)((data) => (0, jsx_runtime_1.jsx)(button_1.Button, Object.assign({ link: data.link }, data.linkOptions, { onClick: tertiaryActionClick(data.onClick), type: "tertiary", children: data.text })), [tertiaryActionClick]);
23
28
  const renderDescription = (0, react_1.useCallback)(data => (0, jsx_runtime_1.jsx)("div", { id: descriptionId, className: (0, classNames_1.classNames)(["zen-status-pill-popup__description", descriptionDriveClassName || ""]), children: data }), [descriptionDriveClassName, descriptionId]);
24
29
  const renderCount = (0, react_1.useCallback)((data) => data ? (0, jsx_runtime_1.jsx)("div", { className: (0, classNames_1.classNames)(["zen-status-pill-popup__header-text zen-status-pill-popup__header-text--left", headerDriveClassName || ""]), children: data }) : null, [headerDriveClassName]);
25
30
  const renderDate = (0, react_1.useCallback)((data) => data ? (0, jsx_runtime_1.jsx)("div", { className: (0, classNames_1.classNames)(["zen-status-pill-popup__header-text", headerDriveClassName || ""]), children: data }) : null, [headerDriveClassName]);
26
31
  return (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(date || count) && isVisible && (0, jsx_runtime_1.jsxs)("div", { className: "zen-status-pill-popup__content-header", children: [date && (0, jsx_runtime_1.jsx)(lazyContent_1.LazyContent, { width: 80, type: "text", content: date, isVisible: isVisible, errorHandler: errorHandler, render: renderDate }), count && (0, jsx_runtime_1.jsx)(lazyContent_1.LazyContent, { width: 80, type: "text", content: count, isVisible: isVisible, errorHandler: errorHandler, render: renderCount })] }), descriptionText &&
27
- (0, jsx_runtime_1.jsx)(lazyContent_1.LazyContent, { width: 200, type: "text", content: descriptionText, isVisible: isVisible, errorHandler: errorHandler, render: renderDescription }), (mainAction || secondaryAction) && isVisible && (0, jsx_runtime_1.jsxs)("div", { className: "zen-status-pill-popup__actions", children: [mainAction && (0, jsx_runtime_1.jsx)(lazyContent_1.LazyContent, { type: "button", content: mainAction, errorHandler: errorHandler, render: renderMainButton, isVisible: isVisible }), secondaryAction && (0, jsx_runtime_1.jsx)(lazyContent_1.LazyContent, { type: "button", content: secondaryAction, errorHandler: errorHandler, render: renderSecondaryButton, isVisible: isVisible })] })] });
32
+ (0, jsx_runtime_1.jsx)(lazyContent_1.LazyContent, { width: 200, type: "text", content: descriptionText, isVisible: isVisible, errorHandler: errorHandler, render: renderDescription }), (mainAction || secondaryAction || tertiaryAction) && isVisible && (0, jsx_runtime_1.jsxs)("div", { className: "zen-status-pill-popup__actions", children: [mainAction && (0, jsx_runtime_1.jsx)(lazyContent_1.LazyContent, { type: "button", content: mainAction, errorHandler: errorHandler, render: renderMainButton, isVisible: isVisible }), secondaryAction && (0, jsx_runtime_1.jsx)(lazyContent_1.LazyContent, { type: "button", content: secondaryAction, errorHandler: errorHandler, render: renderSecondaryButton, isVisible: isVisible }), tertiaryAction && (0, jsx_runtime_1.jsx)(lazyContent_1.LazyContent, { type: "button", content: tertiaryAction, errorHandler: errorHandler, render: renderTertiaryButton, isVisible: isVisible })] })] });
28
33
  };
29
34
  exports.PillContent = PillContent;
@@ -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 interface IPillExpandable extends IZenComponentProps {
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 {};