@mezzanine-ui/react 1.0.0-alpha.0 → 1.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (180) hide show
  1. package/Breadcrumb/Breadcrumb.js +40 -12
  2. package/Breadcrumb/typings.d.ts +8 -3
  3. package/Drawer/Drawer.d.ts +47 -6
  4. package/Drawer/Drawer.js +36 -11
  5. package/Dropdown/Dropdown.d.ts +116 -15
  6. package/Dropdown/Dropdown.js +235 -32
  7. package/Dropdown/DropdownAction.d.ts +50 -0
  8. package/Dropdown/DropdownAction.js +26 -0
  9. package/Dropdown/DropdownItem.d.ts +60 -0
  10. package/Dropdown/DropdownItem.js +318 -0
  11. package/Dropdown/DropdownItemCard.d.ts +96 -0
  12. package/Dropdown/DropdownItemCard.js +115 -0
  13. package/Dropdown/DropdownStatus.d.ts +22 -0
  14. package/Dropdown/dropdownKeydownHandler.d.ts +15 -0
  15. package/Dropdown/highlightText.d.ts +9 -0
  16. package/Dropdown/highlightText.js +32 -0
  17. package/Dropdown/index.d.ts +1 -1
  18. package/Empty/Empty.js +26 -3
  19. package/Empty/typings.d.ts +16 -7
  20. package/Navigation/Navigation.d.ts +11 -17
  21. package/Navigation/Navigation.js +58 -41
  22. package/Navigation/NavigationFooter.d.ts +10 -0
  23. package/Navigation/NavigationFooter.js +26 -0
  24. package/Navigation/NavigationHeader.d.ts +8 -0
  25. package/Navigation/NavigationHeader.js +13 -0
  26. package/Navigation/NavigationIconButton.d.ts +15 -0
  27. package/Navigation/NavigationIconButton.js +12 -0
  28. package/Navigation/NavigationOption.d.ts +35 -0
  29. package/Navigation/NavigationOption.js +60 -0
  30. package/Navigation/NavigationOptionCategory.d.ts +6 -0
  31. package/Navigation/NavigationOptionCategory.js +12 -0
  32. package/Navigation/NavigationUserMenu.d.ts +8 -0
  33. package/Navigation/NavigationUserMenu.js +18 -0
  34. package/Navigation/context.d.ts +13 -0
  35. package/Navigation/context.js +7 -0
  36. package/Navigation/index.d.ts +12 -6
  37. package/Navigation/index.js +6 -3
  38. package/Navigation/useCurrentPathname.d.ts +1 -0
  39. package/Navigation/useCurrentPathname.js +14 -0
  40. package/PageHeader/PageHeader.d.ts +5 -1
  41. package/PageHeader/PageHeader.js +8 -3
  42. package/PageToolbar/PageToolbar.d.ts +73 -26
  43. package/PageToolbar/PageToolbar.js +10 -101
  44. package/PageToolbar/utils.d.ts +23 -0
  45. package/PageToolbar/utils.js +165 -0
  46. package/Pagination/PaginationItem.js +1 -3
  47. package/Pagination/usePagination.js +0 -18
  48. package/Radio/Radio.d.ts +36 -3
  49. package/Radio/Radio.js +21 -11
  50. package/Radio/RadioGroup.d.ts +36 -7
  51. package/Radio/RadioGroup.js +5 -4
  52. package/Radio/RadioGroupContext.d.ts +2 -1
  53. package/Radio/index.d.ts +3 -3
  54. package/Slider/useSlider.js +1 -1
  55. package/Tab/Tab.d.ts +32 -0
  56. package/Tab/Tab.js +57 -0
  57. package/Tab/TabItem.d.ts +27 -0
  58. package/Tab/TabItem.js +18 -0
  59. package/Tab/index.d.ts +4 -0
  60. package/Tab/index.js +2 -0
  61. package/Table/Table.d.ts +75 -94
  62. package/Table/Table.js +216 -161
  63. package/Table/TableContext.d.ts +114 -51
  64. package/Table/TableContext.js +21 -3
  65. package/Table/components/TableBody.d.ts +5 -0
  66. package/Table/components/TableBody.js +102 -0
  67. package/Table/components/TableCell.d.ts +17 -0
  68. package/Table/components/TableCell.js +74 -0
  69. package/Table/components/TableColGroup.d.ts +4 -0
  70. package/Table/components/TableColGroup.js +206 -0
  71. package/Table/components/TableDragHandleCell.d.ts +9 -0
  72. package/Table/components/TableDragHandleCell.js +37 -0
  73. package/Table/components/TableExpandCell.d.ts +11 -0
  74. package/Table/components/TableExpandCell.js +44 -0
  75. package/Table/components/TableExpandedRow.d.ts +9 -0
  76. package/Table/components/TableExpandedRow.js +46 -0
  77. package/Table/components/TableHeader.d.ts +4 -0
  78. package/Table/components/TableHeader.js +125 -0
  79. package/Table/components/TablePagination.d.ts +3 -0
  80. package/Table/components/TablePagination.js +11 -0
  81. package/Table/components/TableResizeHandle.d.ts +13 -0
  82. package/Table/components/TableResizeHandle.js +115 -0
  83. package/Table/components/TableRow.d.ts +12 -0
  84. package/Table/components/TableRow.js +126 -0
  85. package/Table/components/TableSelectionCell.d.ts +13 -0
  86. package/Table/components/TableSelectionCell.js +35 -0
  87. package/Table/components/index.d.ts +10 -0
  88. package/Table/components/index.js +10 -0
  89. package/Table/hooks/index.d.ts +9 -0
  90. package/Table/hooks/index.js +8 -0
  91. package/Table/hooks/typings.d.ts +14 -0
  92. package/Table/hooks/useTableColumns.d.ts +8 -0
  93. package/Table/hooks/useTableColumns.js +91 -0
  94. package/Table/hooks/useTableDataSource.d.ts +57 -0
  95. package/Table/hooks/useTableDataSource.js +183 -0
  96. package/Table/hooks/useTableExpansion.d.ts +7 -0
  97. package/Table/hooks/useTableExpansion.js +52 -0
  98. package/Table/hooks/useTableFixedOffsets.d.ts +29 -0
  99. package/Table/hooks/useTableFixedOffsets.js +241 -0
  100. package/Table/hooks/useTableScroll.d.ts +12 -0
  101. package/Table/hooks/useTableScroll.js +58 -0
  102. package/Table/hooks/useTableSelection.d.ts +7 -0
  103. package/Table/hooks/useTableSelection.js +94 -0
  104. package/Table/hooks/useTableSorting.d.ts +6 -0
  105. package/Table/hooks/useTableSorting.js +32 -0
  106. package/Table/hooks/useTableVirtualization.d.ts +22 -0
  107. package/Table/hooks/useTableVirtualization.js +115 -0
  108. package/Table/index.d.ts +7 -10
  109. package/Table/index.js +22 -6
  110. package/Table/utils/index.d.ts +2 -0
  111. package/Table/utils/index.js +1 -0
  112. package/Table/utils/useTableRowSelection.d.ts +18 -0
  113. package/Table/utils/useTableRowSelection.js +63 -0
  114. package/_internal/InputCheck/InputCheck.d.ts +15 -1
  115. package/_internal/InputCheck/InputCheck.js +6 -2
  116. package/_internal/InputCheck/InputCheckGroup.d.ts +11 -1
  117. package/_internal/InputCheck/InputCheckGroup.js +4 -2
  118. package/_internal/SlideFadeOverlay/SlideFadeOverlay.d.ts +1 -1
  119. package/_internal/SlideFadeOverlay/SlideFadeOverlay.js +1 -1
  120. package/hooks/useElementHeight.d.ts +8 -0
  121. package/hooks/useElementHeight.js +41 -0
  122. package/index.d.ts +9 -7
  123. package/index.js +6 -11
  124. package/package.json +6 -4
  125. package/utils/flatten-children.d.ts +12 -0
  126. package/utils/flatten-children.js +37 -0
  127. package/utils/get-css-variable-value.d.ts +1 -0
  128. package/utils/get-css-variable-value.js +4 -1
  129. package/Navigation/NavigationContext.d.ts +0 -5
  130. package/Navigation/NavigationContext.js +0 -8
  131. package/Navigation/NavigationItem.d.ts +0 -31
  132. package/Navigation/NavigationItem.js +0 -23
  133. package/Navigation/NavigationSubMenu.d.ts +0 -22
  134. package/Navigation/NavigationSubMenu.js +0 -50
  135. package/Table/TableBody.d.ts +0 -10
  136. package/Table/TableBody.js +0 -31
  137. package/Table/TableBodyRow.d.ts +0 -11
  138. package/Table/TableBodyRow.js +0 -65
  139. package/Table/TableCell.d.ts +0 -19
  140. package/Table/TableCell.js +0 -24
  141. package/Table/TableExpandedTable.d.ts +0 -11
  142. package/Table/TableExpandedTable.js +0 -29
  143. package/Table/TableHeader.d.ts +0 -3
  144. package/Table/TableHeader.js +0 -36
  145. package/Table/draggable/useTableDraggable.d.ts +0 -14
  146. package/Table/draggable/useTableDraggable.js +0 -64
  147. package/Table/editable/TableEditRenderWrapper.d.ts +0 -7
  148. package/Table/editable/TableEditRenderWrapper.js +0 -16
  149. package/Table/expandable/TableExpandable.d.ts +0 -27
  150. package/Table/expandable/TableExpandable.js +0 -24
  151. package/Table/pagination/TablePagination.d.ts +0 -10
  152. package/Table/pagination/TablePagination.js +0 -26
  153. package/Table/pagination/useTablePagination.d.ts +0 -8
  154. package/Table/pagination/useTablePagination.js +0 -30
  155. package/Table/refresh/TableRefresh.d.ts +0 -10
  156. package/Table/refresh/TableRefresh.js +0 -22
  157. package/Table/rowSelection/TableRowSelection.d.ts +0 -18
  158. package/Table/rowSelection/TableRowSelection.js +0 -93
  159. package/Table/rowSelection/useTableRowSelection.d.ts +0 -6
  160. package/Table/rowSelection/useTableRowSelection.js +0 -53
  161. package/Table/sorting/TableSortingIcon.d.ts +0 -10
  162. package/Table/sorting/TableSortingIcon.js +0 -33
  163. package/Table/sorting/useTableSorting.d.ts +0 -11
  164. package/Table/sorting/useTableSorting.js +0 -121
  165. package/Table/useTableFetchMore.d.ts +0 -10
  166. package/Table/useTableFetchMore.js +0 -50
  167. package/Table/useTableLoading.d.ts +0 -5
  168. package/Table/useTableLoading.js +0 -19
  169. package/Table/useTableScroll.d.ts +0 -592
  170. package/Table/useTableScroll.js +0 -296
  171. package/Tabs/Tab.d.ts +0 -18
  172. package/Tabs/Tab.js +0 -16
  173. package/Tabs/TabPane.d.ts +0 -14
  174. package/Tabs/TabPane.js +0 -19
  175. package/Tabs/Tabs.d.ts +0 -39
  176. package/Tabs/Tabs.js +0 -52
  177. package/Tabs/index.d.ts +0 -6
  178. package/Tabs/index.js +0 -3
  179. package/Tabs/useTabsOverflow.d.ts +0 -8
  180. package/Tabs/useTabsOverflow.js +0 -62
@@ -0,0 +1,50 @@
1
+ import { ButtonProps } from "../Button";
2
+ export interface DropdownActionProps {
3
+ /**
4
+ * The text of the custom action button.
5
+ */
6
+ actionText?: string;
7
+ /**
8
+ * The text of the cancel button.
9
+ */
10
+ cancelText?: string;
11
+ /**
12
+ * The text of the clear button.
13
+ */
14
+ clearText?: string;
15
+ /**
16
+ * The text of the confirm button.
17
+ */
18
+ confirmText?: string;
19
+ /**
20
+ * The custom action button props of the dropdown.
21
+ */
22
+ customActionButtonProps?: ButtonProps;
23
+ /**
24
+ * Click handler for cancel button.
25
+ */
26
+ onCancel?: () => void;
27
+ /**
28
+ * Click handler for clear button.
29
+ */
30
+ onClear?: () => void;
31
+ /**
32
+ * Click handler for confirm button.
33
+ */
34
+ onConfirm?: () => void;
35
+ /**
36
+ * Click handler for custom action button.
37
+ */
38
+ onClick?: () => void;
39
+ /**
40
+ * Whether to show the actions.
41
+ * @default false
42
+ */
43
+ showActions?: boolean;
44
+ /**
45
+ * If true, display a bar at the top of the dropdown action area.
46
+ * @default false
47
+ */
48
+ showTopBar?: boolean;
49
+ }
50
+ export default function DropdownAction(props: DropdownActionProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,26 @@
1
+ 'use client';
2
+ import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
3
+ import { dropdownClasses } from '@mezzanine-ui/core/dropdown/dropdown';
4
+ import { CloseIcon } from '@mezzanine-ui/icons';
5
+ import Button from '../Button/Button.js';
6
+
7
+ const actionButtonSize = 'minor';
8
+ function DropdownAction(props) {
9
+ const { showActions = false, showTopBar, customActionButtonProps, cancelText, confirmText, actionText, clearText, onCancel, onConfirm, onClick, onClear, } = props;
10
+ const cancelLabel = cancelText || 'Cancel';
11
+ const confirmLabel = confirmText || 'Confirm';
12
+ const actionLabel = actionText || 'Custom Action';
13
+ const clearLabel = clearText || 'Clear Options';
14
+ const hasAnyEvent = Boolean(onCancel || onConfirm || onClick || onClear);
15
+ const isClearMode = Boolean(onClear && !onClick);
16
+ const isCustomMode = Boolean(onClick && !onClear);
17
+ const isDefaultMode = !isClearMode && !isCustomMode;
18
+ const hasCancel = Boolean(onCancel && isDefaultMode);
19
+ const hasConfirm = Boolean(onConfirm && isDefaultMode);
20
+ return (jsx(Fragment, { children: showActions && hasAnyEvent && (jsxs("div", { className: dropdownClasses.action, children: [showTopBar && jsx("i", { className: dropdownClasses.actionTopBar }), jsxs("div", { className: dropdownClasses.actionTools, children: [hasCancel && (jsx(Button, { variant: "base-ghost", size: actionButtonSize, onClick: onCancel, children: cancelLabel })), hasConfirm && (jsx(Button, { size: actionButtonSize, style: hasCancel ? undefined : { marginLeft: 'auto' }, onClick: onConfirm, children: confirmLabel })), isCustomMode && (jsx(Button, { size: actionButtonSize, variant: "base-ghost", ...customActionButtonProps, onClick: onClick, children: actionLabel })), isClearMode && (jsx(Button, { size: actionButtonSize, variant: "base-ghost", icon: {
21
+ position: 'leading',
22
+ src: CloseIcon,
23
+ }, onClick: onClear, children: clearLabel }))] })] })) }));
24
+ }
25
+
26
+ export { DropdownAction as default };
@@ -0,0 +1,60 @@
1
+ import { ReactNode } from 'react';
2
+ import { DropdownItemSharedProps, DropdownOptionsByType, dropdownType } from '@mezzanine-ui/core/dropdown/dropdown';
3
+ import { type DropdownActionProps } from './DropdownAction';
4
+ export interface DropdownItemProps<T extends dropdownType | undefined = dropdownType> extends Omit<DropdownItemSharedProps, 'type'> {
5
+ /**
6
+ * The action configuration for the dropdown.
7
+ */
8
+ actionConfig?: DropdownActionProps;
9
+ /**
10
+ * The active option index for hover/focus state.
11
+ */
12
+ activeIndex: number | null;
13
+ /**
14
+ * The text to follow.
15
+ */
16
+ followText?: string;
17
+ /**
18
+ * Custom content rendered before options (e.g. inline trigger).
19
+ */
20
+ headerContent?: ReactNode;
21
+ /**
22
+ * The listbox id for aria usage.
23
+ */
24
+ listboxId: string;
25
+ /**
26
+ * The aria-label for the listbox.
27
+ * If not provided, a default label will be used when there are no options.
28
+ */
29
+ listboxLabel?: string;
30
+ /**
31
+ * The max height of the dropdown list.
32
+ */
33
+ maxHeight?: number | string;
34
+ /**
35
+ * Whether to set the same width as its anchor element.
36
+ * @default false
37
+ */
38
+ sameWidth?: boolean;
39
+ /**
40
+ * Callback when hovering option index changes.
41
+ */
42
+ onHover?: (index: number) => void;
43
+ /**
44
+ * Options to render.
45
+ * The structure is constrained based on the `type` prop:
46
+ * - 'default': flat array (no children allowed)
47
+ * - 'grouped': array with one level of children (children cannot have children)
48
+ * - 'tree': array with nested children up to 3 levels (strictly enforced at compile time)
49
+ */
50
+ options: DropdownOptionsByType<T>;
51
+ /**
52
+ * The type of the dropdown.
53
+ * This prop determines the structure of the `options` array:
54
+ * - 'default': flat array (no children allowed)
55
+ * - 'grouped': array with one level of children (children cannot have children)
56
+ * - 'tree': array with nested children up to 3 levels
57
+ */
58
+ type?: dropdownType;
59
+ }
60
+ export default function DropdownItem<T extends dropdownType | undefined = dropdownType>(props: DropdownItemProps<T>): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,318 @@
1
+ 'use client';
2
+ import { jsxs, jsx } from 'react/jsx-runtime';
3
+ import keycode from 'keycode';
4
+ import { useRef, useState, useCallback, useMemo, useEffect } from 'react';
5
+ import { dropdownClasses } from '@mezzanine-ui/core/dropdown/dropdown';
6
+ import { CaretDownIcon, CaretRightIcon } from '@mezzanine-ui/icons';
7
+ import { useElementHeight } from '../hooks/useElementHeight.js';
8
+ import Typography from '../Typography/Typography.js';
9
+ import DropdownAction from './DropdownAction.js';
10
+ import DropdownItemCard from './DropdownItemCard.js';
11
+
12
+ /**
13
+ * Limits DropdownOption array to a maximum depth, truncating extra children levels and showing error message if exceeded.
14
+ * @param input - The original DropdownOption array
15
+ * @param maxDepth - Maximum depth (default: 3)
16
+ * @param warn - Whether to show warning (default: true)
17
+ * @returns DropdownOption array with at most the specified depth
18
+ */
19
+ function truncateArrayDepth(input, maxDepth = 3, warn = true) {
20
+ // Internal recursive function: truncates children to specified depth
21
+ const truncate = (options, currentDepth = 1) => {
22
+ if (currentDepth >= maxDepth) {
23
+ // Stop going deeper once maximum depth is reached, remove children
24
+ return options.map(({ children: _children, ...option }) => option);
25
+ }
26
+ return options.map(option => {
27
+ if (!option.children)
28
+ return option;
29
+ return {
30
+ ...option,
31
+ children: truncate(option.children, currentDepth + 1),
32
+ };
33
+ });
34
+ };
35
+ // Calculate maximum depth by checking all elements (not just the first one)
36
+ const getDepth = (options, depth = 1) => {
37
+ if (!options || options.length === 0)
38
+ return depth - 1;
39
+ // Find the maximum depth among all options
40
+ return Math.max(...options.map((option) => {
41
+ if (!option.children || option.children.length === 0) {
42
+ return depth - 1;
43
+ }
44
+ return getDepth(option.children, depth + 1);
45
+ }));
46
+ };
47
+ const depth = getDepth(input);
48
+ if (depth <= maxDepth)
49
+ return input;
50
+ // Exceeds maximum depth → warn
51
+ if (warn) {
52
+ console.error(`[truncateArrayDepth] Input DropdownOption array exceeds ${maxDepth} levels. Extra levels were truncated.`);
53
+ }
54
+ // Truncate to specified depth
55
+ return truncate(input);
56
+ }
57
+ function DropdownItem(props) {
58
+ const { activeIndex, disabled = false, listboxId, listboxLabel, mode = 'single', options, value, type, maxHeight, actionConfig, onHover, onSelect, followText, headerContent, } = props;
59
+ const optionsContent = truncateArrayDepth(options, 3);
60
+ const listRef = useRef(null);
61
+ const [expandedNodes, setExpandedNodes] = useState(new Set());
62
+ const hasActions = Boolean(actionConfig === null || actionConfig === void 0 ? void 0 : actionConfig.showActions);
63
+ const hasHeader = Boolean(headerContent);
64
+ // Use custom hook to measure element heights
65
+ const [actionRef, actionHeight] = useElementHeight(hasActions && !!maxHeight);
66
+ const [headerRef, headerHeight] = useElementHeight(hasHeader && !!maxHeight);
67
+ const toggleExpand = useCallback((optionId) => {
68
+ setExpandedNodes((prev) => {
69
+ const next = new Set(prev);
70
+ if (next.has(optionId)) {
71
+ next.delete(optionId);
72
+ }
73
+ else {
74
+ next.add(optionId);
75
+ }
76
+ return next;
77
+ });
78
+ }, []);
79
+ const visibleShortcutOptions = useMemo(() => {
80
+ const result = [];
81
+ const collectDefault = (optionList) => {
82
+ optionList === null || optionList === void 0 ? void 0 : optionList.forEach((option) => {
83
+ result.push(option);
84
+ });
85
+ };
86
+ const collectGrouped = (optionList) => {
87
+ optionList === null || optionList === void 0 ? void 0 : optionList.forEach((groupOption) => {
88
+ var _a;
89
+ (_a = groupOption.children) === null || _a === void 0 ? void 0 : _a.forEach((option) => {
90
+ result.push(option);
91
+ });
92
+ });
93
+ };
94
+ const collectTree = (optionList) => {
95
+ optionList === null || optionList === void 0 ? void 0 : optionList.forEach((option) => {
96
+ result.push(option);
97
+ if (option.children && expandedNodes.has(option.id)) {
98
+ collectTree(option.children);
99
+ }
100
+ });
101
+ };
102
+ if (type === 'grouped') {
103
+ collectGrouped(optionsContent);
104
+ }
105
+ else if (type === 'tree') {
106
+ collectTree(optionsContent);
107
+ }
108
+ else {
109
+ collectDefault(optionsContent);
110
+ }
111
+ return result;
112
+ }, [expandedNodes, optionsContent, type]);
113
+ const matchShortcut = useCallback((event, shortcut) => {
114
+ var _a, _b;
115
+ const eventCode = (_a = event.which) !== null && _a !== void 0 ? _a : event.keyCode;
116
+ if (typeof shortcut === 'number') {
117
+ return eventCode === shortcut;
118
+ }
119
+ const tokens = shortcut
120
+ .split('+')
121
+ .map((token) => token.trim().toLowerCase())
122
+ .filter(Boolean);
123
+ let requireMeta = false;
124
+ let requireCtrl = false;
125
+ let requireAlt = false;
126
+ let requireShift = false;
127
+ let mainToken = null;
128
+ tokens.forEach((token) => {
129
+ switch (token) {
130
+ case 'cmd':
131
+ case 'meta':
132
+ case 'command':
133
+ requireMeta = true;
134
+ break;
135
+ case 'ctrl':
136
+ case 'control':
137
+ requireCtrl = true;
138
+ break;
139
+ case 'alt':
140
+ case 'option':
141
+ requireAlt = true;
142
+ break;
143
+ case 'shift':
144
+ requireShift = true;
145
+ break;
146
+ default:
147
+ mainToken = token;
148
+ break;
149
+ }
150
+ });
151
+ if (!mainToken)
152
+ return false;
153
+ if (requireMeta !== event.metaKey
154
+ || requireCtrl !== event.ctrlKey
155
+ || requireAlt !== event.altKey
156
+ || requireShift !== event.shiftKey) {
157
+ return false;
158
+ }
159
+ const mainCode = keycode(mainToken);
160
+ if (typeof mainCode === 'number' && eventCode === mainCode) {
161
+ return true;
162
+ }
163
+ const eventKey = (_b = event.key) === null || _b === void 0 ? void 0 : _b.toLowerCase();
164
+ if (eventKey && eventKey === mainToken) {
165
+ return true;
166
+ }
167
+ const eventKeyName = keycode(eventCode);
168
+ return typeof eventKeyName === 'string' && eventKeyName.toLowerCase() === mainToken;
169
+ }, []);
170
+ const renderGroupedOptions = (optionList, startIndex) => {
171
+ let currentIndex = startIndex;
172
+ const elements = (optionList !== null && optionList !== void 0 ? optionList : []).flatMap((groupOption) => {
173
+ var _a;
174
+ const hasChildren = Boolean(groupOption.children && groupOption.children.length > 0);
175
+ const groupElements = [];
176
+ if (hasChildren) {
177
+ groupElements.push(jsx(Typography, { variant: "body", className: dropdownClasses.groupLabel, children: groupOption.name }, groupOption.id));
178
+ (_a = groupOption.children) === null || _a === void 0 ? void 0 : _a.forEach((option) => {
179
+ var _a, _b;
180
+ currentIndex += 1;
181
+ const optionIndex = currentIndex;
182
+ const isActive = optionIndex === activeIndex;
183
+ const isSelected = Array.isArray(value)
184
+ ? value.includes(option.id)
185
+ : value === option.id;
186
+ groupElements.push(jsx(DropdownItemCard, { followText: followText, active: isActive, checked: isSelected, disabled: disabled, id: `${listboxId}-option-${optionIndex}`, label: option.name, mode: mode, name: option.name, onClick: () => {
187
+ if (disabled)
188
+ return;
189
+ onSelect === null || onSelect === void 0 ? void 0 : onSelect(option);
190
+ }, checkSite: "none", validate: (_a = option.validate) !== null && _a !== void 0 ? _a : 'default', onMouseEnter: () => onHover === null || onHover === void 0 ? void 0 : onHover(optionIndex), showUnderline: (_b = option.showUnderline) !== null && _b !== void 0 ? _b : false, appendContent: option.shortcutText }, option.id));
191
+ });
192
+ }
193
+ return groupElements;
194
+ });
195
+ return { elements, nextIndex: currentIndex };
196
+ };
197
+ const renderTreeOptions = (optionList, depth, startIndex) => {
198
+ let currentIndex = startIndex;
199
+ const elements = (optionList !== null && optionList !== void 0 ? optionList : []).flatMap((option) => {
200
+ var _a, _b;
201
+ currentIndex += 1;
202
+ const optionIndex = currentIndex;
203
+ const level = Math.min(depth, 2);
204
+ const isActive = optionIndex === activeIndex;
205
+ const isSelected = Array.isArray(value)
206
+ ? value.includes(option.id)
207
+ : value === option.id;
208
+ const hasChildren = Boolean(option.children && option.children.length > 0);
209
+ const isExpanded = hasChildren && expandedNodes.has(option.id);
210
+ let prependIcon = undefined;
211
+ if (hasChildren && level !== 2) {
212
+ prependIcon = isExpanded ? CaretDownIcon : CaretRightIcon;
213
+ }
214
+ const checkSite = option.showCheckbox ? 'prepend' : 'none';
215
+ 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: () => {
216
+ if (disabled)
217
+ return;
218
+ if (hasChildren && type === 'tree') {
219
+ toggleExpand(option.id);
220
+ }
221
+ else {
222
+ onSelect === null || onSelect === void 0 ? void 0 : onSelect(option);
223
+ }
224
+ }, followText: followText, checkSite: checkSite, onMouseEnter: () => onHover === null || onHover === void 0 ? void 0 : onHover(optionIndex), prependIcon: prependIcon, showUnderline: (_a = option.showUnderline) !== null && _a !== void 0 ? _a : false, validate: (_b = option.validate) !== null && _b !== void 0 ? _b : 'default', appendContent: option.shortcutText }, option.id));
225
+ if (hasChildren && isExpanded && type === 'tree') {
226
+ const childResult = renderTreeOptions(option.children, depth + 1, currentIndex);
227
+ currentIndex = childResult.nextIndex;
228
+ return [card, ...childResult.elements];
229
+ }
230
+ return [card];
231
+ });
232
+ return { elements, nextIndex: currentIndex };
233
+ };
234
+ const renderDefaultOptions = (optionList, startIndex) => {
235
+ let currentIndex = startIndex;
236
+ const elements = (optionList !== null && optionList !== void 0 ? optionList : []).flatMap((option) => {
237
+ var _a, _b;
238
+ currentIndex += 1;
239
+ const optionIndex = currentIndex;
240
+ const isSelected = Array.isArray(value)
241
+ ? value.includes(option.id)
242
+ : value === option.id;
243
+ const isActive = optionIndex === activeIndex;
244
+ let checkSite = 'none';
245
+ if (option === null || option === void 0 ? void 0 : option.checkSite) {
246
+ checkSite = option.checkSite;
247
+ }
248
+ return (jsx(DropdownItemCard, { followText: followText, active: isActive, checked: isSelected, disabled: disabled, id: `${listboxId}-option-${optionIndex}`, label: option.name, mode: mode, name: option.name, onClick: () => {
249
+ if (disabled)
250
+ return;
251
+ onSelect === null || onSelect === void 0 ? void 0 : onSelect(option);
252
+ }, onMouseEnter: () => onHover === null || onHover === void 0 ? void 0 : onHover(optionIndex), prependIcon: option.icon, validate: (_a = option.validate) !== null && _a !== void 0 ? _a : 'default', showUnderline: (_b = option.showUnderline) !== null && _b !== void 0 ? _b : false, checkSite: checkSite, appendContent: option.shortcutText }, option.id));
253
+ });
254
+ return { elements, nextIndex: currentIndex };
255
+ };
256
+ const renderOptions = (optionList, depth, startIndex) => {
257
+ if (type === 'grouped') {
258
+ return renderGroupedOptions(optionList, startIndex);
259
+ }
260
+ if (type === 'tree') {
261
+ return renderTreeOptions(optionList, depth, startIndex);
262
+ }
263
+ return renderDefaultOptions(optionList, startIndex);
264
+ };
265
+ const { elements: renderedOptions } = renderOptions(optionsContent, 0, -1);
266
+ const listStyle = useMemo(() => {
267
+ if (!maxHeight) {
268
+ return undefined;
269
+ }
270
+ return {
271
+ maxHeight: typeof maxHeight === 'number' ? `${maxHeight}px` : maxHeight,
272
+ };
273
+ }, [maxHeight]);
274
+ const listWrapperStyle = useMemo(() => {
275
+ if (!maxHeight) {
276
+ return undefined;
277
+ }
278
+ const maxHeightValue = typeof maxHeight === 'number' ? maxHeight : parseFloat(maxHeight);
279
+ const availableHeight = Math.max(0, maxHeightValue - actionHeight - headerHeight);
280
+ return {
281
+ maxHeight: `${availableHeight}px`,
282
+ };
283
+ }, [maxHeight, actionHeight, headerHeight]);
284
+ useEffect(() => {
285
+ const listElement = listRef.current;
286
+ if (!listElement || disabled) {
287
+ return;
288
+ }
289
+ const handleKeyDown = (event) => {
290
+ if (event.repeat)
291
+ return;
292
+ const targetOption = visibleShortcutOptions.find((option) => {
293
+ if (!Array.isArray(option.shortcutKeys) || option.shortcutKeys.length === 0) {
294
+ return false;
295
+ }
296
+ return option.shortcutKeys.some((shortcut) => matchShortcut(event, shortcut));
297
+ });
298
+ if (!targetOption)
299
+ return;
300
+ event.preventDefault();
301
+ event.stopPropagation();
302
+ if (type === 'tree' && targetOption.children && targetOption.children.length > 0) {
303
+ toggleExpand(targetOption.id);
304
+ return;
305
+ }
306
+ onSelect === null || onSelect === void 0 ? void 0 : onSelect(targetOption);
307
+ };
308
+ listElement.addEventListener('keydown', handleKeyDown);
309
+ return () => {
310
+ listElement.removeEventListener('keydown', handleKeyDown);
311
+ };
312
+ }, [disabled, matchShortcut, onSelect, type, toggleExpand, visibleShortcutOptions]);
313
+ 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
314
+ ? (jsx("div", { className: dropdownClasses.listWrapper, style: listWrapperStyle, children: renderedOptions }))
315
+ : renderedOptions, hasActions && (jsx("div", { ref: actionRef, children: jsx(DropdownAction, { ...actionConfig }) }))] }));
316
+ }
317
+
318
+ export { DropdownItem as default };
@@ -0,0 +1,96 @@
1
+ import { dropdownCheckPosition, dropdownItemLevel, dropdownItemValidate, dropdownMode } from "@mezzanine-ui/core/dropdown/dropdown";
2
+ import { type IconDefinition } from "@mezzanine-ui/icons";
3
+ export interface DropdownItemCardProps {
4
+ /**
5
+ * Whether the option is currently active (highlighted by keyboard navigation).
6
+ * This controls the aria-selected attribute according to W3C ARIA spec.
7
+ * When an option is referenced by aria-activedescendant, it should have aria-selected="true".
8
+ */
9
+ active?: boolean;
10
+ /**
11
+ * The icon to append.
12
+ */
13
+ appendIcon?: IconDefinition;
14
+ /**
15
+ * The content to append.
16
+ */
17
+ appendContent?: string;
18
+ /**
19
+ * The position of the checkbox.
20
+ */
21
+ checkSite?: dropdownCheckPosition;
22
+ /**
23
+ * Controlled: Whether the option is selected/checked.
24
+ * Controls checkbox state in multiple mode.
25
+ * When provided, the state is controlled externally.
26
+ */
27
+ checked?: boolean;
28
+ /**
29
+ * Additional className for the list item.
30
+ */
31
+ className?: string;
32
+ /**
33
+ * Uncontrolled: Default checked/selected state.
34
+ * Only used when `checked` is not provided.
35
+ */
36
+ defaultChecked?: boolean;
37
+ /**
38
+ * Whether the dropdown item card is disabled.
39
+ */
40
+ disabled?: boolean;
41
+ /**
42
+ * The text to follow.
43
+ */
44
+ followText?: string;
45
+ /**
46
+ * DOM id for the option, useful for aria-activedescendant.
47
+ */
48
+ id?: string;
49
+ /**
50
+ * The label of the dropdown item card.
51
+ */
52
+ label?: string;
53
+ /**
54
+ * The level of the dropdown item card.
55
+ */
56
+ level?: dropdownItemLevel;
57
+ /**
58
+ * The mode of the dropdown item card.
59
+ */
60
+ mode: dropdownMode;
61
+ /**
62
+ * The accessible name / label for the option.
63
+ * Falls back to label if not provided.
64
+ */
65
+ name?: string;
66
+ /**
67
+ * Callback fired when the checked/selected state changes.
68
+ */
69
+ onCheckedChange?: (checked: boolean) => void;
70
+ /**
71
+ * Click handler when the list item is activated.
72
+ */
73
+ onClick?: () => void;
74
+ /**
75
+ * Mouse enter handler.
76
+ */
77
+ onMouseEnter?: () => void;
78
+ /**
79
+ * The icon to prepend.
80
+ */
81
+ prependIcon?: IconDefinition;
82
+ /**
83
+ * Whether to show the underline.
84
+ * @default false
85
+ */
86
+ showUnderline?: boolean;
87
+ /**
88
+ * The subtitle of the dropdown item card.
89
+ */
90
+ subTitle?: string;
91
+ /**
92
+ * The validation of the dropdown item card.
93
+ */
94
+ validate?: dropdownItemValidate;
95
+ }
96
+ export default function DropdownItemCard(props: DropdownItemCardProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,115 @@
1
+ 'use client';
2
+ import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
3
+ import cx from 'clsx';
4
+ import { useMemo, useState } from 'react';
5
+ import { dropdownClasses } from '@mezzanine-ui/core/dropdown/dropdown';
6
+ import { CheckedIcon } from '@mezzanine-ui/icons';
7
+ import Checkbox from '../Checkbox/Checkbox.js';
8
+ import Typography from '../Typography/Typography.js';
9
+ import { highlightText } from './highlightText.js';
10
+ import Icon from '../Icon/Icon.js';
11
+
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;
14
+ const cardLabel = label || '';
15
+ const cardName = _name || cardLabel;
16
+ const level = levelProp || 0;
17
+ // Generate ID for the label element to use with aria-labelledby
18
+ // If no id is provided, we'll rely on the visible text content for accessibility
19
+ const labelId = useMemo(() => {
20
+ if (!id)
21
+ return undefined;
22
+ return `${id}-label`;
23
+ }, [id]);
24
+ // If name is different from label, we need to use aria-label as fallback
25
+ // Note: aria-label on role="option" has limited support, but it's better than nothing
26
+ const ariaLabel = useMemo(() => {
27
+ if (cardName !== cardLabel) {
28
+ return cardName;
29
+ }
30
+ return undefined;
31
+ }, [cardName, cardLabel]);
32
+ // Controlled/uncontrolled mode for checked/selected state
33
+ const isControlled = checked !== undefined;
34
+ const [internalChecked, setInternalChecked] = useState(defaultChecked !== null && defaultChecked !== void 0 ? defaultChecked : false);
35
+ const isChecked = isControlled ? checked : internalChecked;
36
+ const labelColor = useMemo(() => {
37
+ return validate === 'danger' ? 'text-error' : 'text-neutral-solid';
38
+ }, [validate]);
39
+ const appendIconColor = useMemo(() => {
40
+ return disabled || validate === 'danger' ? 'neutral-light' : 'neutral';
41
+ }, [disabled, validate]);
42
+ const iconColor = useMemo(() => {
43
+ if (disabled)
44
+ return 'neutral-light';
45
+ return validate === 'danger' ? 'error' : 'neutral';
46
+ }, [disabled, validate]);
47
+ const labelParts = useMemo(() => {
48
+ return followText
49
+ ? highlightText(cardLabel, followText)
50
+ : [
51
+ {
52
+ text: cardLabel,
53
+ highlight: false
54
+ }
55
+ ];
56
+ }, [cardLabel, followText]);
57
+ const showPrependContent = useMemo(() => {
58
+ return prependIcon || (checkSite === 'prepend' && mode === 'multiple');
59
+ }, [prependIcon, checkSite, mode]);
60
+ const showAppendContent = useMemo(() => {
61
+ return appendContent || appendIcon || (checkSite === 'append' && isChecked);
62
+ }, [appendContent, appendIcon, checkSite, isChecked]);
63
+ const subTitleParts = useMemo(() => {
64
+ return followText && subTitle
65
+ ? highlightText(subTitle, followText)
66
+ : subTitle
67
+ ? [
68
+ {
69
+ text: subTitle,
70
+ highlight: false
71
+ }
72
+ ]
73
+ : [];
74
+ }, [subTitle, followText]);
75
+ const renderHighlightedText = (parts, defaultColor, className, id) => {
76
+ return (jsx(Typography, { color: defaultColor, className: className, id: id, children: parts.map((part, index) => (jsx("span", { className: part.highlight && validate !== 'danger' ? dropdownClasses.cardHighlightedText : '', children: part.text }, index))) }));
77
+ };
78
+ const toggleChecked = () => {
79
+ if (disabled)
80
+ return;
81
+ const newChecked = !isChecked;
82
+ if (!isControlled) {
83
+ setInternalChecked(newChecked);
84
+ }
85
+ onCheckedChange === null || onCheckedChange === void 0 ? void 0 : onCheckedChange(newChecked);
86
+ };
87
+ const handleClick = () => {
88
+ if (disabled)
89
+ return;
90
+ if (mode === 'multiple') {
91
+ toggleChecked();
92
+ }
93
+ onClick === null || onClick === void 0 ? void 0 : onClick();
94
+ };
95
+ const handleCheckboxChange = (event) => {
96
+ event.stopPropagation();
97
+ toggleChecked();
98
+ };
99
+ const handleKeyDown = (event) => {
100
+ if (disabled)
101
+ return;
102
+ if (event.key === 'Enter' || event.key === ' ') {
103
+ event.preventDefault();
104
+ onClick === null || onClick === void 0 ? void 0 : onClick();
105
+ }
106
+ };
107
+ return (jsxs(Fragment, { children: [jsx("li", { ...(labelId ? { 'aria-labelledby': labelId } : {}), ...(ariaLabel ? { 'aria-label': ariaLabel } : {}), "aria-selected": active, className: cx(dropdownClasses.card, dropdownClasses.cardLevel(level), {
108
+ // Highlight: keyboard/mouse focused (active) or selected (isChecked)
109
+ [dropdownClasses.cardActive]: active || isChecked,
110
+ [dropdownClasses.cardDisabled]: disabled,
111
+ }, 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
112
+ && jsx(Icon, { icon: prependIcon, color: iconColor }), checkSite === 'prepend' && mode === 'multiple' && (jsx(Checkbox, { checked: isChecked, disabled: disabled, onChange: handleCheckboxChange }))] })), jsxs("div", { className: dropdownClasses.cardBody, children: [cardLabel && renderHighlightedText(labelParts, labelColor, dropdownClasses.cardTitle, labelId), subTitleParts.length > 0 && renderHighlightedText(subTitleParts, 'text-neutral', 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 === 'append' && isChecked && jsx(Icon, { icon: CheckedIcon, color: appendIconColor, size: 16 })] }))] }) }), showUnderline && jsx("div", { className: dropdownClasses.cardUnderline })] }));
113
+ }
114
+
115
+ export { DropdownItemCard as default };