@mezzanine-ui/react 1.0.0-beta.3 → 1.0.0-beta.5

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 (146) hide show
  1. package/AutoComplete/AutoComplete.d.ts +23 -0
  2. package/AutoComplete/AutoComplete.js +39 -9
  3. package/Breadcrumb/Breadcrumb.js +16 -21
  4. package/Breadcrumb/BreadcrumbDropdown.d.ts +11 -0
  5. package/Breadcrumb/BreadcrumbDropdown.js +22 -0
  6. package/Breadcrumb/BreadcrumbItem.d.ts +2 -3
  7. package/Breadcrumb/BreadcrumbItem.js +13 -31
  8. package/Breadcrumb/BreadcrumbOverflowMenu.d.ts +7 -0
  9. package/Breadcrumb/BreadcrumbOverflowMenu.js +77 -0
  10. package/Breadcrumb/BreadcrumbOverflowMenuDropdown.d.ts +11 -0
  11. package/Breadcrumb/BreadcrumbOverflowMenuDropdown.js +21 -0
  12. package/Breadcrumb/BreadcrumbOverflowMenuItem.d.ts +3 -0
  13. package/Breadcrumb/BreadcrumbOverflowMenuItem.js +27 -0
  14. package/Breadcrumb/typings.d.ts +21 -39
  15. package/Calendar/Calendar.js +2 -6
  16. package/Calendar/CalendarCell.d.ts +22 -0
  17. package/Calendar/CalendarCell.js +6 -2
  18. package/Calendar/CalendarControls.js +1 -1
  19. package/Calendar/CalendarDayOfWeek.js +3 -2
  20. package/Calendar/CalendarDays.js +5 -1
  21. package/Calendar/CalendarHalfYears.js +13 -7
  22. package/Calendar/CalendarMonths.js +13 -6
  23. package/Calendar/CalendarQuarters.js +13 -7
  24. package/Calendar/CalendarWeeks.js +87 -34
  25. package/Calendar/CalendarYears.js +13 -12
  26. package/Calendar/useCalendarControlModifiers.d.ts +1 -1
  27. package/Calendar/useCalendarControlModifiers.js +12 -12
  28. package/Calendar/useCalendarControls.d.ts +4 -4
  29. package/Calendar/useCalendarControls.js +33 -19
  30. package/Calendar/useRangeCalendarControls.d.ts +8 -8
  31. package/Calendar/useRangeCalendarControls.js +42 -31
  32. package/Checkbox/index.d.ts +4 -5
  33. package/Checkbox/index.js +1 -5
  34. package/ContentHeader/ContentHeader.d.ts +160 -0
  35. package/ContentHeader/ContentHeader.js +54 -0
  36. package/ContentHeader/index.d.ts +2 -0
  37. package/ContentHeader/index.js +1 -0
  38. package/ContentHeader/utils.d.ts +23 -0
  39. package/ContentHeader/utils.js +215 -0
  40. package/DateRangePicker/useDateRangeCalendarControls.js +8 -2
  41. package/Dropdown/Dropdown.d.ts +48 -0
  42. package/Dropdown/Dropdown.js +16 -2
  43. package/Dropdown/DropdownItem.d.ts +42 -0
  44. package/Dropdown/DropdownItem.js +144 -13
  45. package/Dropdown/DropdownItemCard.d.ts +7 -2
  46. package/Dropdown/DropdownItemCard.js +12 -9
  47. package/Empty/Empty.js +2 -1
  48. package/Empty/icons/EmptyMainNotificationIcon.d.ts +4 -0
  49. package/Empty/icons/EmptyMainNotificationIcon.js +9 -0
  50. package/Empty/typings.d.ts +2 -2
  51. package/FilterArea/Filter.d.ts +32 -0
  52. package/FilterArea/Filter.js +23 -0
  53. package/FilterArea/FilterArea.d.ts +58 -0
  54. package/FilterArea/FilterArea.js +31 -0
  55. package/FilterArea/FilterLine.d.ts +11 -0
  56. package/FilterArea/FilterLine.js +13 -0
  57. package/FilterArea/index.d.ts +6 -0
  58. package/FilterArea/index.js +3 -0
  59. package/Form/useSelectValueControl.d.ts +3 -4
  60. package/Form/useSelectValueControl.js +51 -39
  61. package/Input/Input.d.ts +6 -4
  62. package/Input/Input.js +28 -10
  63. package/Input/index.d.ts +1 -1
  64. package/Modal/MediaPreviewModal.d.ts +54 -0
  65. package/Modal/MediaPreviewModal.js +158 -0
  66. package/Modal/Modal.js +1 -1
  67. package/Modal/index.d.ts +2 -0
  68. package/Modal/index.js +1 -0
  69. package/Navigation/Navigation.js +6 -5
  70. package/Navigation/NavigationOption.d.ts +6 -2
  71. package/Navigation/NavigationOption.js +19 -9
  72. package/Navigation/NavigationOverflowMenu.d.ts +6 -0
  73. package/Navigation/NavigationOverflowMenu.js +90 -0
  74. package/Navigation/NavigationOverflowMenuOption.d.ts +7 -0
  75. package/Navigation/NavigationOverflowMenuOption.js +68 -0
  76. package/Navigation/NavigationUserMenu.d.ts +4 -2
  77. package/Navigation/NavigationUserMenu.js +13 -5
  78. package/Navigation/context.d.ts +3 -2
  79. package/NotificationCenter/NotificationCenter.d.ts +1 -1
  80. package/NotificationCenter/NotificationCenter.js +34 -14
  81. package/NotificationCenter/NotificationCenterDrawer.d.ts +20 -0
  82. package/PageHeader/PageHeader.d.ts +32 -25
  83. package/PageHeader/PageHeader.js +49 -35
  84. package/Popper/Popper.js +2 -1
  85. package/ResultState/ResultState.d.ts +9 -0
  86. package/ResultState/ResultState.js +36 -4
  87. package/Scrollbar/Scrollbar.d.ts +9 -0
  88. package/Scrollbar/Scrollbar.js +79 -0
  89. package/Scrollbar/index.d.ts +2 -0
  90. package/Scrollbar/index.js +1 -0
  91. package/Scrollbar/typings.d.ts +47 -0
  92. package/Select/Select.d.ts +37 -18
  93. package/Select/Select.js +165 -51
  94. package/Select/SelectTrigger.js +5 -4
  95. package/Select/index.d.ts +8 -9
  96. package/Select/index.js +3 -3
  97. package/Select/typings.d.ts +6 -1
  98. package/Selection/Selection.js +1 -1
  99. package/Selection/SelectionGroup.d.ts +28 -0
  100. package/Table/Table.d.ts +2 -120
  101. package/Table/Table.js +148 -53
  102. package/Table/TableContext.d.ts +11 -12
  103. package/Table/components/TableActionsCell.js +12 -4
  104. package/Table/components/TableBody.js +2 -1
  105. package/Table/components/TableColGroup.d.ts +1 -4
  106. package/Table/components/TableColGroup.js +15 -16
  107. package/Table/components/TableCollectableCell.d.ts +17 -0
  108. package/Table/components/TableCollectableCell.js +54 -0
  109. package/Table/components/TableDragOrPinHandleCell.d.ts +20 -0
  110. package/Table/components/TableDragOrPinHandleCell.js +58 -0
  111. package/Table/components/TableExpandedRow.js +11 -2
  112. package/Table/components/TableHeader.js +12 -10
  113. package/Table/components/TableRow.js +38 -13
  114. package/Table/components/TableSelectionCell.js +1 -1
  115. package/Table/components/TableToggleableCell.d.ts +16 -0
  116. package/Table/components/TableToggleableCell.js +51 -0
  117. package/Table/components/index.d.ts +4 -1
  118. package/Table/components/index.js +3 -0
  119. package/Table/hooks/typings.d.ts +18 -4
  120. package/Table/hooks/useTableExpansion.d.ts +2 -2
  121. package/Table/hooks/useTableExpansion.js +5 -5
  122. package/Table/hooks/useTableFixedOffsets.d.ts +6 -2
  123. package/Table/hooks/useTableFixedOffsets.js +58 -24
  124. package/Table/hooks/useTableScroll.d.ts +9 -3
  125. package/Table/hooks/useTableScroll.js +34 -7
  126. package/Table/hooks/useTableVirtualization.d.ts +2 -1
  127. package/Table/hooks/useTableVirtualization.js +2 -8
  128. package/Table/index.d.ts +4 -3
  129. package/Table/index.js +3 -0
  130. package/Table/typings.d.ts +172 -0
  131. package/Transition/Slide.d.ts +9 -2
  132. package/Transition/Slide.js +7 -4
  133. package/Tree/TreeNode.js +1 -1
  134. package/index.d.ts +4 -2
  135. package/index.js +6 -3
  136. package/package.json +6 -4
  137. package/Navigation/CollapsedMenu.d.ts +0 -6
  138. package/Navigation/CollapsedMenu.js +0 -16
  139. package/PageToolbar/PageToolbar.d.ts +0 -110
  140. package/PageToolbar/PageToolbar.js +0 -23
  141. package/PageToolbar/index.d.ts +0 -2
  142. package/PageToolbar/index.js +0 -1
  143. package/PageToolbar/utils.d.ts +0 -23
  144. package/PageToolbar/utils.js +0 -157
  145. package/Table/components/TableDragHandleCell.d.ts +0 -11
  146. package/Table/components/TableDragHandleCell.js +0 -44
@@ -162,5 +162,53 @@ export interface DropdownProps extends DropdownItemSharedProps {
162
162
  * The icon of the dropdown empty status.
163
163
  */
164
164
  emptyIcon?: IconDefinition;
165
+ /**
166
+ * Whether to disable portal.
167
+ * This prop is only relevant when `inputPosition` is set to 'outside'.
168
+ * Controls whether the dropdown content is rendered within the current hierarchy or portaled to the body.
169
+ * @default false
170
+ */
171
+ disablePortal?: boolean;
172
+ /**
173
+ * Callback fired when the dropdown list reaches the bottom.
174
+ * Only fires when `maxHeight` is set and the list is scrollable.
175
+ */
176
+ onReachBottom?: () => void;
177
+ /**
178
+ * Callback fired when the dropdown list leaves the bottom.
179
+ * Only fires when `maxHeight` is set and the list is scrollable.
180
+ */
181
+ onLeaveBottom?: () => void;
182
+ /**
183
+ * Callback fired when the dropdown list is scrolled.
184
+ * Receives the scroll event and computed scroll information.
185
+ */
186
+ onScroll?: (computed: {
187
+ scrollTop: number;
188
+ maxScrollTop: number;
189
+ }, target: HTMLDivElement) => void;
190
+ /**
191
+ * Whether to defer the initialization of OverlayScrollbars.
192
+ * This can improve initial render performance.
193
+ * @default true
194
+ */
195
+ scrollbarDefer?: boolean | object;
196
+ /**
197
+ * Whether to disable the custom scrollbar component.
198
+ * When false (default), Scrollbar component will be used when maxHeight is set.
199
+ * When true, falls back to native div scrolling (backward compatible).
200
+ * @default false
201
+ */
202
+ scrollbarDisabled?: boolean;
203
+ /**
204
+ * The maximum width of the scrollable container.
205
+ * Can be a CSS value string (e.g., '500px', '100%') or a number (treated as pixels).
206
+ */
207
+ scrollbarMaxWidth?: number | string;
208
+ /**
209
+ * Additional options to pass to OverlayScrollbars.
210
+ * @see https://kingsora.github.io/OverlayScrollbars/#!documentation/options
211
+ */
212
+ scrollbarOptions?: import('overlayscrollbars').PartialOptions;
165
213
  }
166
214
  export default function Dropdown(props: DropdownProps): import("react/jsx-runtime").JSX.Element;
@@ -14,7 +14,7 @@ import DropdownItem from './DropdownItem.js';
14
14
  import Popper from '../Popper/Popper.js';
15
15
 
16
16
  function Dropdown(props) {
17
- const { activeIndex: activeIndexProp, id, children, options = [], type = 'default', maxHeight, disabled = false, showDropdownActions = false, actionCancelText, actionConfirmText, actionText, actionClearText, actionCustomButtonProps, showActionShowTopBar, isMatchInputValue = false, inputPosition = 'outside', placement = 'bottom', customWidth, sameWidth = false, listboxId: listboxIdProp, listboxLabel, onClose, onOpen, open: openProp, onVisibilityChange, onSelect, onActionConfirm, onActionCancel, onActionCustom, onActionClear, onItemHover, zIndex, status, loadingText, emptyText, emptyIcon, followText: followTextProp, mode, value, } = props;
17
+ const { activeIndex: activeIndexProp, id, children, options = [], type = 'default', maxHeight, disabled = false, showDropdownActions = false, actionCancelText, actionConfirmText, actionText, actionClearText, actionCustomButtonProps, showActionShowTopBar, isMatchInputValue = false, inputPosition = 'outside', placement = 'bottom', customWidth, sameWidth = false, listboxId: listboxIdProp, listboxLabel, onClose, onOpen, open: openProp, onVisibilityChange, onSelect, onActionConfirm, onActionCancel, onActionCustom, onActionClear, onItemHover, zIndex, status, loadingText, emptyText, emptyIcon, followText: followTextProp, disablePortal = false, onReachBottom, onLeaveBottom, onScroll, mode, value, scrollbarDefer, scrollbarDisabled, scrollbarMaxWidth, scrollbarOptions, } = props;
18
18
  const isInline = inputPosition === 'inside';
19
19
  const inputId = useId();
20
20
  const defaultListboxId = `${inputId}-listbox`;
@@ -229,6 +229,9 @@ function Dropdown(props) {
229
229
  sameWidth,
230
230
  onHover: handleItemHover,
231
231
  onSelect,
232
+ onReachBottom,
233
+ onLeaveBottom,
234
+ onScroll,
232
235
  options,
233
236
  type,
234
237
  status,
@@ -237,6 +240,10 @@ function Dropdown(props) {
237
240
  emptyIcon,
238
241
  mode,
239
242
  value,
243
+ scrollbarDefer,
244
+ scrollbarDisabled,
245
+ scrollbarMaxWidth,
246
+ scrollbarOptions,
240
247
  }), [
241
248
  actionConfig,
242
249
  mergedActiveIndex,
@@ -248,6 +255,9 @@ function Dropdown(props) {
248
255
  sameWidth,
249
256
  handleItemHover,
250
257
  onSelect,
258
+ onReachBottom,
259
+ onLeaveBottom,
260
+ onScroll,
251
261
  options,
252
262
  type,
253
263
  status,
@@ -256,6 +266,10 @@ function Dropdown(props) {
256
266
  emptyIcon,
257
267
  mode,
258
268
  value,
269
+ scrollbarDefer,
270
+ scrollbarDisabled,
271
+ scrollbarMaxWidth,
272
+ scrollbarOptions,
259
273
  ]);
260
274
  const triggerElement = useMemo(() => {
261
275
  const childWithRef = children;
@@ -349,7 +363,7 @@ function Dropdown(props) {
349
363
  }, [isInline, isOpen, setOpen]);
350
364
  return (jsxs("div", { id: id, ref: containerRef, className: cx(dropdownClasses.root, dropdownClasses.inputPosition(inputPosition)), children: [isInline && (jsxs(TransitionGroup, { component: null, children: [!isOpen && inlineTriggerElement && (createElement(Translate, { ...translateProps, from: translateFrom, key: "inline-trigger", in: true },
351
365
  jsx("div", { children: inlineTriggerElement }))), isOpen && (createElement(Translate, { ...translateProps, from: translateFrom, key: "inline-list", in: true },
352
- jsx("div", { children: jsx(DropdownItem, { ...baseDropdownItemProps, headerContent: inlineTriggerElement }) })))] })), !isInline && (jsx(Popper, { ref: popperRef, anchor: anchorRef, controllerRef: popperControllerRef, open: isOpen, disablePortal: true, options: {
366
+ jsx("div", { children: jsx(DropdownItem, { ...baseDropdownItemProps, headerContent: inlineTriggerElement }) })))] })), !isInline && (jsx(Popper, { ref: popperRef, anchor: anchorRef, className: dropdownClasses.popperWithPortal, controllerRef: popperControllerRef, open: isOpen, disablePortal: disablePortal, options: {
353
367
  placement: popoverPlacement,
354
368
  middleware: [
355
369
  offsetMiddleware,
@@ -1,6 +1,7 @@
1
1
  import { ReactNode } from 'react';
2
2
  import { DropdownItemSharedProps, DropdownOptionsByType, DropdownStatus as DropdownStatusType, DropdownType } from '@mezzanine-ui/core/dropdown/dropdown';
3
3
  import { type IconDefinition } from '@mezzanine-ui/icons';
4
+ import type { PartialOptions } from 'overlayscrollbars';
4
5
  import { type DropdownActionProps } from './DropdownAction';
5
6
  export interface DropdownItemProps<T extends DropdownType | undefined = DropdownType> extends Omit<DropdownItemSharedProps, 'type'> {
6
7
  /**
@@ -73,5 +74,46 @@ export interface DropdownItemProps<T extends DropdownType | undefined = Dropdown
73
74
  * The icon of the dropdown empty status.
74
75
  */
75
76
  emptyIcon?: IconDefinition;
77
+ /**
78
+ * Callback fired when the dropdown list reaches the bottom.
79
+ * Only fires when `maxHeight` is set and the list is scrollable.
80
+ */
81
+ onReachBottom?: () => void;
82
+ /**
83
+ * Callback fired when the dropdown list leaves the bottom.
84
+ * Only fires when `maxHeight` is set and the list is scrollable.
85
+ */
86
+ onLeaveBottom?: () => void;
87
+ /**
88
+ * Callback fired when the dropdown list is scrolled.
89
+ * Receives the scroll event and computed scroll information.
90
+ */
91
+ onScroll?: (computed: {
92
+ scrollTop: number;
93
+ maxScrollTop: number;
94
+ }, target: HTMLDivElement) => void;
95
+ /**
96
+ * Whether to defer the initialization of OverlayScrollbars.
97
+ * This can improve initial render performance.
98
+ * @default true
99
+ */
100
+ scrollbarDefer?: boolean | object;
101
+ /**
102
+ * Whether to disable the custom scrollbar component.
103
+ * When false (default), Scrollbar component will be used when maxHeight is set.
104
+ * When true, falls back to native div scrolling (backward compatible).
105
+ * @default false
106
+ */
107
+ scrollbarDisabled?: boolean;
108
+ /**
109
+ * The maximum width of the scrollable container.
110
+ * Can be a CSS value string (e.g., '500px', '100%') or a number (treated as pixels).
111
+ */
112
+ scrollbarMaxWidth?: number | string;
113
+ /**
114
+ * Additional options to pass to OverlayScrollbars.
115
+ * @see https://kingsora.github.io/OverlayScrollbars/#!documentation/options
116
+ */
117
+ scrollbarOptions?: PartialOptions;
76
118
  }
77
119
  export default function DropdownItem<T extends DropdownType | undefined = DropdownType>(props: DropdownItemProps<T>): import("react/jsx-runtime").JSX.Element;
@@ -10,7 +10,19 @@ import DropdownAction from './DropdownAction.js';
10
10
  import DropdownItemCard from './DropdownItemCard.js';
11
11
  import DropdownStatus from './DropdownStatus.js';
12
12
  import { shortcutTextHandler } from './shortcutTextHandler.js';
13
+ import Scrollbar from '../Scrollbar/Scrollbar.js';
13
14
 
15
+ // Helper function to recursively get all descendant IDs from a tree option (excluding the option itself)
16
+ function getAllDescendantIds(option) {
17
+ const ids = [];
18
+ if (option.children && option.children.length > 0) {
19
+ option.children.forEach((child) => {
20
+ ids.push(String(child.id));
21
+ ids.push(...getAllDescendantIds(child));
22
+ });
23
+ }
24
+ return ids;
25
+ }
14
26
  /**
15
27
  * Limits DropdownOption array to a maximum depth, truncating extra children levels and showing error message if exceeded.
16
28
  * @param input - The original DropdownOption array
@@ -57,12 +69,16 @@ function truncateArrayDepth(input, maxDepth = 3, warn = true) {
57
69
  return truncate(input);
58
70
  }
59
71
  function DropdownItem(props) {
60
- const { activeIndex, disabled = false, listboxId, listboxLabel, mode = 'single', options, value, type, maxHeight, actionConfig, onHover, onSelect, followText, headerContent, status, loadingText, emptyText, emptyIcon, } = props;
72
+ const { activeIndex, disabled = false, listboxId, listboxLabel, mode = 'single', options, value, type, maxHeight, actionConfig, onHover, onSelect, followText, headerContent, status, loadingText, emptyText, emptyIcon, onReachBottom, onLeaveBottom, onScroll, scrollbarDefer = true, scrollbarDisabled = false, scrollbarMaxWidth, scrollbarOptions, } = props;
61
73
  const optionsContent = truncateArrayDepth(options, 3);
62
74
  const listRef = useRef(null);
75
+ const listWrapperRef = useRef(null);
76
+ const viewportRef = useRef(null);
77
+ const wasAtBottomRef = useRef(false);
63
78
  const [expandedNodes, setExpandedNodes] = useState(new Set());
64
79
  const hasActions = Boolean(actionConfig === null || actionConfig === void 0 ? void 0 : actionConfig.showActions);
65
80
  const hasHeader = Boolean(headerContent);
81
+ const shouldUseScrollbar = maxHeight && !scrollbarDisabled;
66
82
  // Use custom hook to measure element heights
67
83
  const [actionRef, actionHeight] = useElementHeight(hasActions && !!maxHeight);
68
84
  const [headerRef, headerHeight] = useElementHeight(hasHeader && !!maxHeight);
@@ -199,17 +215,39 @@ function DropdownItem(props) {
199
215
  });
200
216
  return { elements, nextIndex: currentIndex };
201
217
  };
218
+ const calculateNodeSelectionState = useCallback((option, selectedIds) => {
219
+ if (!option.children || option.children.length === 0) {
220
+ const isSelected = selectedIds.includes(String(option.id));
221
+ return { checked: isSelected, indeterminate: false };
222
+ }
223
+ // Get all descendant IDs (excluding the parent node itself)
224
+ const allDescendantIds = getAllDescendantIds(option);
225
+ const selectedDescendants = allDescendantIds.filter((id) => selectedIds.includes(id));
226
+ const totalDescendants = allDescendantIds.length;
227
+ if (totalDescendants === 0) {
228
+ // No descendants, check if parent itself is selected
229
+ const isSelected = selectedIds.includes(String(option.id));
230
+ return { checked: isSelected, indeterminate: false };
231
+ }
232
+ if (selectedDescendants.length === 0) {
233
+ return { checked: false, indeterminate: false };
234
+ }
235
+ if (selectedDescendants.length === totalDescendants) {
236
+ // All descendants are selected
237
+ return { checked: true, indeterminate: false };
238
+ }
239
+ // Some but not all descendants are selected
240
+ return { checked: false, indeterminate: true };
241
+ }, []);
202
242
  const renderTreeOptions = (optionList, depth, startIndex) => {
203
243
  let currentIndex = startIndex;
244
+ const selectedIds = Array.isArray(value) ? value.map((id) => String(id)) : value ? [String(value)] : [];
204
245
  const elements = (optionList !== null && optionList !== void 0 ? optionList : []).flatMap((option) => {
205
246
  var _a, _b, _c;
206
247
  currentIndex += 1;
207
248
  const optionIndex = currentIndex;
208
249
  const level = Math.min(depth, 2);
209
250
  const isActive = optionIndex === activeIndex;
210
- const isSelected = Array.isArray(value)
211
- ? value.includes(option.id)
212
- : value === option.id;
213
251
  const hasChildren = Boolean(option.children && option.children.length > 0);
214
252
  const isExpanded = hasChildren && expandedNodes.has(option.id);
215
253
  let prependIcon = undefined;
@@ -220,13 +258,31 @@ function DropdownItem(props) {
220
258
  const shortcutText = option.shortcutText
221
259
  ? option.shortcutText
222
260
  : shortcutTextHandler((_a = option.shortcutKeys) !== null && _a !== void 0 ? _a : []);
223
- const card = (jsx(DropdownItemCard, { active: isActive, checked: isSelected, disabled: disabled, id: `${listboxId}-option-${optionIndex}`, label: option.name, level: level, mode: mode, name: option.name, onClick: () => {
261
+ const selectionState = hasChildren && mode === 'multiple'
262
+ ? calculateNodeSelectionState(option, selectedIds)
263
+ : {
264
+ checked: selectedIds.includes(String(option.id)),
265
+ indeterminate: false,
266
+ };
267
+ const card = (jsx(DropdownItemCard, { active: isActive, checked: selectionState.checked, indeterminate: selectionState.indeterminate, disabled: disabled, id: `${listboxId}-option-${optionIndex}`, label: option.name, level: level, mode: mode, name: option.name, onClick: () => {
224
268
  if (disabled)
225
269
  return;
226
- if (hasChildren && type === 'tree') {
270
+ if (hasChildren && type === 'tree' && mode === 'multiple' && option.showCheckbox) {
271
+ toggleExpand(option.id);
272
+ }
273
+ else if (hasChildren && type === 'tree') {
227
274
  toggleExpand(option.id);
228
275
  }
229
276
  else {
277
+ // In `tree` + `multiple` mode, `DropdownItemCard` already triggers selection via
278
+ // `onCheckedChange` when row is clicked (it toggles checked first, then calls `onClick`),
279
+ // so calling `onSelect` here would cause it to fire twice for leaf nodes.
280
+ if (!(type === 'tree' && mode === 'multiple')) {
281
+ onSelect === null || onSelect === void 0 ? void 0 : onSelect(option);
282
+ }
283
+ }
284
+ }, onCheckedChange: () => {
285
+ if (!disabled) {
230
286
  onSelect === null || onSelect === void 0 ? void 0 : onSelect(option);
231
287
  }
232
288
  }, followText: followText, checkSite: checkSite, onMouseEnter: () => onHover === null || onHover === void 0 ? void 0 : onHover(optionIndex), prependIcon: prependIcon, showUnderline: (_b = option.showUnderline) !== null && _b !== void 0 ? _b : false, validate: (_c = option.validate) !== null && _c !== void 0 ? _c : 'default', appendContent: shortcutText }, option.id));
@@ -242,7 +298,7 @@ function DropdownItem(props) {
242
298
  const renderDefaultOptions = (optionList, startIndex) => {
243
299
  let currentIndex = startIndex;
244
300
  const elements = (optionList !== null && optionList !== void 0 ? optionList : []).flatMap((option) => {
245
- var _a, _b, _c;
301
+ var _a, _b, _c, _d;
246
302
  currentIndex += 1;
247
303
  const optionIndex = currentIndex;
248
304
  const isSelected = Array.isArray(value)
@@ -252,15 +308,12 @@ function DropdownItem(props) {
252
308
  const shortcutText = option.shortcutText
253
309
  ? option.shortcutText
254
310
  : shortcutTextHandler((_a = option.shortcutKeys) !== null && _a !== void 0 ? _a : []);
255
- let checkSite = 'none';
256
- if (option === null || option === void 0 ? void 0 : option.checkSite) {
257
- checkSite = option.checkSite;
258
- }
311
+ const checkSite = (_b = option === null || option === void 0 ? void 0 : option.checkSite) !== null && _b !== void 0 ? _b : 'none';
259
312
  return (jsx(DropdownItemCard, { followText: followText, active: isActive, checked: isSelected, disabled: disabled, id: `${listboxId}-option-${optionIndex}`, label: option.name, mode: mode, name: option.name, onClick: () => {
260
313
  if (disabled)
261
314
  return;
262
315
  onSelect === null || onSelect === void 0 ? void 0 : onSelect(option);
263
- }, onMouseEnter: () => onHover === null || onHover === void 0 ? void 0 : onHover(optionIndex), prependIcon: option.icon, validate: (_b = option.validate) !== null && _b !== void 0 ? _b : 'default', showUnderline: (_c = option.showUnderline) !== null && _c !== void 0 ? _c : false, checkSite: checkSite, appendContent: shortcutText }, option.id));
316
+ }, onMouseEnter: () => onHover === null || onHover === void 0 ? void 0 : onHover(optionIndex), prependIcon: option.icon, validate: (_c = option.validate) !== null && _c !== void 0 ? _c : 'default', showUnderline: (_d = option.showUnderline) !== null && _d !== void 0 ? _d : false, checkSite: checkSite, appendContent: shortcutText }, option.id));
264
317
  });
265
318
  return { elements, nextIndex: currentIndex };
266
319
  };
@@ -294,6 +347,10 @@ function DropdownItem(props) {
294
347
  maxHeight: `${availableHeight}px`,
295
348
  };
296
349
  }, [maxHeight, actionHeight, headerHeight]);
350
+ const getIsAtBottom = useCallback((viewport) => {
351
+ const { scrollTop, scrollHeight, clientHeight } = viewport;
352
+ return scrollTop + clientHeight >= scrollHeight - 1;
353
+ }, []);
297
354
  useEffect(() => {
298
355
  const listElement = listRef.current;
299
356
  if (!listElement || disabled) {
@@ -323,8 +380,82 @@ function DropdownItem(props) {
323
380
  listElement.removeEventListener('keydown', handleKeyDown);
324
381
  };
325
382
  }, [disabled, matchShortcut, onSelect, type, toggleExpand, visibleShortcutOptions]);
383
+ const handleViewportReady = useCallback((viewport) => {
384
+ viewportRef.current = viewport;
385
+ listWrapperRef.current = viewport;
386
+ wasAtBottomRef.current = getIsAtBottom(viewport);
387
+ }, [getIsAtBottom]);
388
+ useEffect(() => {
389
+ if (shouldUseScrollbar) {
390
+ return;
391
+ }
392
+ const listWrapperElement = listWrapperRef.current;
393
+ if (!listWrapperElement || !maxHeight || (!onReachBottom && !onLeaveBottom && !onScroll)) {
394
+ return;
395
+ }
396
+ // Initialize wasAtBottom state by checking current position
397
+ const checkInitialState = () => {
398
+ const { scrollTop, scrollHeight, clientHeight } = listWrapperElement;
399
+ return scrollTop + clientHeight >= scrollHeight - 1;
400
+ };
401
+ let wasAtBottom = checkInitialState();
402
+ const handleScroll = () => {
403
+ const { scrollTop, scrollHeight, clientHeight } = listWrapperElement;
404
+ const maxScrollTop = scrollHeight - clientHeight;
405
+ // Call onScroll callback if provided
406
+ if (onScroll) {
407
+ onScroll({
408
+ scrollTop,
409
+ maxScrollTop,
410
+ }, listWrapperElement);
411
+ }
412
+ // Check if scrolled to bottom (with 1px threshold for rounding errors)
413
+ const isAtBottom = scrollTop + clientHeight >= scrollHeight - 1;
414
+ // Trigger onReachBottom when entering bottom state
415
+ if (isAtBottom && !wasAtBottom) {
416
+ onReachBottom === null || onReachBottom === void 0 ? void 0 : onReachBottom();
417
+ }
418
+ // Trigger onLeaveBottom when leaving bottom state
419
+ if (!isAtBottom && wasAtBottom) {
420
+ onLeaveBottom === null || onLeaveBottom === void 0 ? void 0 : onLeaveBottom();
421
+ }
422
+ wasAtBottom = isAtBottom;
423
+ };
424
+ listWrapperElement.addEventListener('scroll', handleScroll);
425
+ return () => {
426
+ listWrapperElement.removeEventListener('scroll', handleScroll);
427
+ };
428
+ }, [maxHeight, onReachBottom, onLeaveBottom, onScroll, shouldUseScrollbar]);
429
+ const scrollbarEvents = useMemo(() => {
430
+ if (!shouldUseScrollbar || (!onReachBottom && !onLeaveBottom && !onScroll)) {
431
+ return undefined;
432
+ }
433
+ return {
434
+ scroll: (_instance, _event) => {
435
+ const viewport = viewportRef.current;
436
+ if (!viewport)
437
+ return;
438
+ const { scrollTop, scrollHeight, clientHeight } = viewport;
439
+ const maxScrollTop = scrollHeight - clientHeight;
440
+ if (onScroll) {
441
+ onScroll({
442
+ scrollTop,
443
+ maxScrollTop,
444
+ }, viewport);
445
+ }
446
+ const isAtBottom = getIsAtBottom(viewport);
447
+ if (isAtBottom && !wasAtBottomRef.current) {
448
+ onReachBottom === null || onReachBottom === void 0 ? void 0 : onReachBottom();
449
+ }
450
+ if (!isAtBottom && wasAtBottomRef.current) {
451
+ onLeaveBottom === null || onLeaveBottom === void 0 ? void 0 : onLeaveBottom();
452
+ }
453
+ wasAtBottomRef.current = isAtBottom;
454
+ },
455
+ };
456
+ }, [getIsAtBottom, shouldUseScrollbar, onReachBottom, onLeaveBottom, onScroll]);
326
457
  return (jsxs("ul", { "aria-label": listboxLabel || (optionsContent.length === 0 ? 'Dropdown options' : undefined), className: dropdownClasses.list, id: listboxId, ref: listRef, role: "listbox", style: listStyle, tabIndex: -1, children: [hasHeader && (jsx("li", { className: dropdownClasses.listHeader, role: "presentation", ref: headerRef, children: jsx("div", { className: dropdownClasses.listHeaderInner, children: headerContent }) })), maxHeight
327
- ? (jsx("div", { className: dropdownClasses.listWrapper, style: listWrapperStyle, children: shouldShowStatus ? (jsx(DropdownStatus, { status: status, loadingText: loadingText, emptyText: emptyText, emptyIcon: emptyIcon })) : (renderedOptions) }))
458
+ ? (shouldUseScrollbar ? (jsx(Scrollbar, { className: dropdownClasses.listWrapper, defer: scrollbarDefer, disabled: false, events: scrollbarEvents, maxHeight: listWrapperStyle === null || listWrapperStyle === void 0 ? void 0 : listWrapperStyle.maxHeight, maxWidth: scrollbarMaxWidth, onViewportReady: handleViewportReady, options: scrollbarOptions, children: shouldShowStatus ? (jsx(DropdownStatus, { status: status, loadingText: loadingText, emptyText: emptyText, emptyIcon: emptyIcon })) : (renderedOptions) })) : (jsx("div", { ref: listWrapperRef, className: dropdownClasses.listWrapper, style: listWrapperStyle, children: shouldShowStatus ? (jsx(DropdownStatus, { status: status, loadingText: loadingText, emptyText: emptyText, emptyIcon: emptyIcon })) : (renderedOptions) })))
328
459
  : shouldShowStatus ? (jsx(DropdownStatus, { status: status, loadingText: loadingText, emptyText: emptyText, emptyIcon: emptyIcon })) : (renderedOptions), hasActions && (jsx("div", { ref: actionRef, children: jsx(DropdownAction, { ...actionConfig }) }))] }));
329
460
  }
330
461
 
@@ -1,5 +1,5 @@
1
- import { DropdownCheckPosition, DropdownItemLevel, DropdownItemValidate, DropdownMode } from "@mezzanine-ui/core/dropdown/dropdown";
2
- import { type IconDefinition } from "@mezzanine-ui/icons";
1
+ import { DropdownCheckPosition, DropdownItemLevel, DropdownItemValidate, DropdownMode } from '@mezzanine-ui/core/dropdown/dropdown';
2
+ import { type IconDefinition } from '@mezzanine-ui/icons';
3
3
  export interface DropdownItemCardProps {
4
4
  /**
5
5
  * Whether the option is currently active (highlighted by keyboard navigation).
@@ -25,6 +25,11 @@ export interface DropdownItemCardProps {
25
25
  * When provided, the state is controlled externally.
26
26
  */
27
27
  checked?: boolean;
28
+ /**
29
+ * Whether the checkbox is in indeterminate state.
30
+ * Used in tree mode when some but not all children are selected.
31
+ */
32
+ indeterminate?: boolean;
28
33
  /**
29
34
  * Additional className for the list item.
30
35
  */
@@ -4,13 +4,13 @@ import cx from 'clsx';
4
4
  import { useMemo, useState } from 'react';
5
5
  import { dropdownClasses } from '@mezzanine-ui/core/dropdown/dropdown';
6
6
  import { CheckedIcon } from '@mezzanine-ui/icons';
7
- import Checkbox from '../Checkbox/Checkbox.js';
8
7
  import Typography from '../Typography/Typography.js';
9
8
  import { highlightText } from './highlightText.js';
10
9
  import Icon from '../Icon/Icon.js';
10
+ import Checkbox from '../Checkbox/Checkbox.js';
11
11
 
12
12
  function DropdownItemCard(props) {
13
- const { active = false, appendIcon, appendContent, followText, id, label, level: levelProp, mode, name: _name, prependIcon, subTitle, validate, disabled, checked, defaultChecked, checkSite, onCheckedChange, onClick, className, onMouseEnter, showUnderline, } = props;
13
+ const { active = false, appendIcon, appendContent, followText, id, label, level: levelProp, mode, name: _name, prependIcon, subTitle, validate, disabled, checked, defaultChecked, indeterminate = false, checkSite, onCheckedChange, onClick, className, onMouseEnter, showUnderline, } = props;
14
14
  const cardLabel = label || '';
15
15
  const cardName = _name || cardLabel;
16
16
  const level = levelProp || 0;
@@ -51,8 +51,8 @@ function DropdownItemCard(props) {
51
51
  : [
52
52
  {
53
53
  text: cardLabel,
54
- highlight: false
55
- }
54
+ highlight: false,
55
+ },
56
56
  ];
57
57
  }, [cardLabel, followText]);
58
58
  const showPrependContent = useMemo(() => {
@@ -68,13 +68,15 @@ function DropdownItemCard(props) {
68
68
  ? [
69
69
  {
70
70
  text: subTitle,
71
- highlight: false
72
- }
71
+ highlight: false,
72
+ },
73
73
  ]
74
74
  : [];
75
75
  }, [subTitle, followText]);
76
76
  const renderHighlightedText = (parts, className, id) => {
77
- return (jsx(Typography, { className: className, id: id, children: parts.map((part, index) => (jsx("span", { className: part.highlight && validate !== 'danger' ? dropdownClasses.cardHighlightedText : '', children: part.text }, index))) }));
77
+ return (jsx(Typography, { className: className, id: id, children: parts.map((part, index) => (jsx("span", { className: part.highlight && validate !== 'danger'
78
+ ? dropdownClasses.cardHighlightedText
79
+ : '', children: part.text }, index))) }));
78
80
  };
79
81
  const toggleChecked = () => {
80
82
  if (disabled)
@@ -109,8 +111,9 @@ function DropdownItemCard(props) {
109
111
  [dropdownClasses.cardActive]: active || isChecked,
110
112
  [dropdownClasses.cardDisabled]: disabled,
111
113
  [dropdownClasses.cardDanger]: validate === 'danger',
112
- }, className), id: id, role: "option", tabIndex: -1, onMouseEnter: onMouseEnter, onClick: handleClick, onKeyDown: handleKeyDown, children: jsxs("div", { className: dropdownClasses.cardContainer, children: [showPrependContent && (jsxs("div", { className: dropdownClasses.cardPrependContent, children: [prependIcon
113
- && jsx(Icon, { icon: prependIcon, color: iconColor }), checkSite === 'prefix' && mode === 'multiple' && (jsx(Checkbox, { checked: isChecked, disabled: disabled, onChange: handleCheckboxChange }))] })), jsxs("div", { className: dropdownClasses.cardBody, children: [cardLabel && renderHighlightedText(labelParts, dropdownClasses.cardTitle, labelId), subTitleParts.length > 0 && renderHighlightedText(subTitleParts, dropdownClasses.cardDescription)] }), showAppendContent && (jsxs("div", { className: dropdownClasses.cardAppendContent, children: [appendContent && jsx(Typography, { color: "text-neutral-light", children: appendContent }), appendIcon && jsx(Icon, { icon: appendIcon, color: iconColor }), checkSite === 'suffix' && isChecked && jsx(Icon, { icon: CheckedIcon, color: appendIconColor, size: 16 })] }))] }) }), showUnderline && jsx("div", { className: dropdownClasses.cardUnderline })] }));
114
+ }, className), id: id, role: "option", tabIndex: -1, onMouseEnter: onMouseEnter, onClick: handleClick, onKeyDown: handleKeyDown, children: jsxs("div", { className: dropdownClasses.cardContainer, children: [showPrependContent && (jsxs("div", { className: dropdownClasses.cardPrependContent, children: [prependIcon && jsx(Icon, { icon: prependIcon, color: iconColor }), checkSite === 'prefix' && mode === 'multiple' && (jsx(Checkbox, { checked: isChecked, disabled: disabled, indeterminate: indeterminate, onChange: handleCheckboxChange }))] })), jsxs("div", { className: dropdownClasses.cardBody, children: [cardLabel &&
115
+ renderHighlightedText(labelParts, dropdownClasses.cardTitle, labelId), subTitleParts.length > 0 &&
116
+ renderHighlightedText(subTitleParts, dropdownClasses.cardDescription)] }), showAppendContent && (jsxs("div", { className: dropdownClasses.cardAppendContent, children: [appendContent && (jsx(Typography, { color: "text-neutral-light", children: appendContent })), appendIcon && jsx(Icon, { icon: appendIcon, color: iconColor }), checkSite === 'suffix' && isChecked && (jsx(Icon, { icon: CheckedIcon, color: appendIconColor, size: 16 }))] }))] }) }), showUnderline && jsx("div", { className: dropdownClasses.cardUnderline })] }));
114
117
  }
115
118
 
116
119
  export { DropdownItemCard as default };
package/Empty/Empty.js CHANGED
@@ -8,6 +8,7 @@ import { EmptyMainInitialDataIcon } from './icons/EmptyMainInitialDataIcon.js';
8
8
  import { EmptyMainResultIcon } from './icons/EmptyMainResultIcon.js';
9
9
  import { EmptyMainSystemIcon } from './icons/EmptyMainSystemIcon.js';
10
10
  import { flattenChildren } from '../utils/flatten-children.js';
11
+ import { EmptyMainNotificationIcon } from './icons/EmptyMainNotificationIcon.js';
11
12
  import Icon from '../Icon/Icon.js';
12
13
  import cx from 'clsx';
13
14
 
@@ -21,7 +22,7 @@ const iconMap = {
21
22
  const mainIconMap = {
22
23
  custom: null,
23
24
  'initial-data': jsx(EmptyMainInitialDataIcon, { className: emptyClasses.icon }),
24
- notification: null,
25
+ notification: jsx(EmptyMainNotificationIcon, { className: emptyClasses.icon }),
25
26
  result: jsx(EmptyMainResultIcon, { className: emptyClasses.icon }),
26
27
  system: jsx(EmptyMainSystemIcon, { className: emptyClasses.icon }),
27
28
  };
@@ -0,0 +1,4 @@
1
+ export interface EmptyMainNotificationIconProps extends React.SVGProps<SVGSVGElement> {
2
+ size?: number;
3
+ }
4
+ export declare const EmptyMainNotificationIcon: import("react").ForwardRefExoticComponent<Omit<EmptyMainNotificationIconProps, "ref"> & import("react").RefAttributes<SVGSVGElement>>;
@@ -0,0 +1,9 @@
1
+ import { jsxs, jsx } from 'react/jsx-runtime';
2
+ import { forwardRef } from 'react';
3
+
4
+ const EmptyMainNotificationIcon = forwardRef(function EmptyMainNotificationIcon(props, ref) {
5
+ const { className, size = 64, ...rest } = props;
6
+ return (jsxs("svg", { ...rest, className: className, fill: "none", height: size, ref: ref, viewBox: "0 0 44 47", width: size, xmlns: "http://www.w3.org/2000/svg", children: [jsxs("defs", { children: [jsxs("linearGradient", { gradientUnits: "userSpaceOnUse", id: "paint0_linear_8482_10235", x1: "22", x2: "20.7012", y1: "33", y2: "46.8785", children: [jsx("stop", { stopColor: "#9DA4AE" }), jsx("stop", { offset: "1", stopColor: "#E5E7EB" })] }), jsxs("linearGradient", { gradientUnits: "userSpaceOnUse", id: "paint1_linear_8482_10235", x1: "44", x2: "-3.06232", y1: "-1.43647e-06", y2: "35.9963", children: [jsx("stop", { stopColor: "#E5E7EB" }), jsx("stop", { offset: "1", stopColor: "#9DA4AE" })] })] }), jsx("circle", { cx: "22", cy: "40", fill: "url(#paint0_linear_8482_10235)", r: "7" }), jsx("path", { d: "M6.69446 15.305C6.69446 6.85228 13.5467 0 21.9995 0C30.4522 0 37.3045 6.85228 37.3045 15.305V26.5731C37.3045 26.6145 37.3141 26.6552 37.3326 26.6922L43.2746 38.5521C43.6077 39.217 43.1242 40 42.3805 40H1.61938C0.875674 40 0.392197 39.2171 0.725289 38.5521L6.66632 26.6922C6.68483 26.6552 6.69446 26.6145 6.69446 26.5731V15.305Z", fill: "url(#paint1_linear_8482_10235)" })] }));
7
+ });
8
+
9
+ export { EmptyMainNotificationIcon };
@@ -13,14 +13,14 @@ export interface PresetPictogramEmptyProps {
13
13
  * The type of empty state, which determines the icon and color theme.
14
14
  * @default 'initial-data'
15
15
  */
16
- type?: 'initial-data' | 'result' | 'system';
16
+ type?: 'initial-data' | 'result' | 'system' | 'notification';
17
17
  /**
18
18
  * Custom pictogram element.
19
19
  */
20
20
  pictogram?: ReactNode;
21
21
  }
22
22
  export interface CustomPictogramEmptyProps {
23
- type?: 'notification' | 'custom';
23
+ type?: 'custom';
24
24
  pictogram?: ReactNode;
25
25
  }
26
26
  export interface MainOrSubEmptyProps {
@@ -0,0 +1,32 @@
1
+ import { ReactElement } from 'react';
2
+ import { FilterAlign, FilterSpan } from '@mezzanine-ui/core/filter-area';
3
+ import { NativeElementPropsWithoutKeyAndRef } from '../utils/jsx-types';
4
+ import { FormFieldProps } from '../Form';
5
+ export interface FilterProps extends Omit<NativeElementPropsWithoutKeyAndRef<'div'>, 'children'> {
6
+ /**
7
+ * Layout control - Vertical alignment of the field.
8
+ * @default 'stretch'
9
+ */
10
+ align?: FilterAlign;
11
+ /**
12
+ * The content of the filter field.
13
+ */
14
+ children: ReactElement<FormFieldProps> | ReactElement<FormFieldProps>[];
15
+ /**
16
+ * Layout control - Whether the field should automatically expand to fill the entire row (equivalent to span={12}).
17
+ * @default false
18
+ */
19
+ grow?: boolean;
20
+ /**
21
+ * Layout control - Minimum width of the field.
22
+ */
23
+ minWidth?: string | number;
24
+ /**
25
+ * Layout control - Number of columns the field occupies in the Grid (1-12, Grid has 12 columns total).
26
+ * This property is ignored when grow is true.
27
+ * @default 2
28
+ */
29
+ span?: FilterSpan;
30
+ }
31
+ declare const Filter: import("react").ForwardRefExoticComponent<FilterProps & import("react").RefAttributes<HTMLDivElement>>;
32
+ export default Filter;
@@ -0,0 +1,23 @@
1
+ 'use client';
2
+ import { jsx } from 'react/jsx-runtime';
3
+ import { forwardRef, useMemo } from 'react';
4
+ import { filterAreaClasses, filterAreaPrefix } from '@mezzanine-ui/core/filter-area';
5
+ import cx from 'clsx';
6
+
7
+ const Filter = forwardRef(function Filter(props, ref) {
8
+ const { align = 'stretch', children, className, grow = false, minWidth, span = 2, ...rest } = props;
9
+ const filterClassName = useMemo(() => cx(filterAreaClasses.filter, {
10
+ [filterAreaClasses.filterGrow]: grow,
11
+ [filterAreaClasses.filterAlign(align)]: align,
12
+ }, className), [align, className, grow]);
13
+ const style = useMemo(() => ({
14
+ ...(minWidth && { minWidth: typeof minWidth === 'number' ? `${minWidth}px` : minWidth }),
15
+ ...(!grow && {
16
+ [`--${filterAreaPrefix}-filter-span`]: span,
17
+ }),
18
+ ...rest.style,
19
+ }), [grow, minWidth, rest.style, span]);
20
+ return (jsx("div", { ...rest, ref: ref, className: filterClassName, style: style, children: children }));
21
+ });
22
+
23
+ export { Filter as default };