@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,57 @@
1
+ import type { TableDataSource } from '@mezzanine-ui/core/table';
2
+ export interface TableTransitionState {
3
+ /** Keys of rows that are currently in "adding" state (highlighted) */
4
+ addingKeys: Set<string>;
5
+ /** Keys of rows that are currently in "deleting" state (red highlight before fade) */
6
+ deletingKeys: Set<string>;
7
+ /** Keys of rows that are currently fading out */
8
+ fadingOutKeys: Set<string>;
9
+ }
10
+ export interface UseTableDataSourceOptions<T extends TableDataSource> {
11
+ /** Initial data source */
12
+ initialData?: T[];
13
+ /** Duration of highlight animation in ms
14
+ * @default 1000
15
+ */
16
+ highlightDuration?: number;
17
+ /** Duration of fade out animation in ms
18
+ * @default 200
19
+ */
20
+ fadeOutDuration?: number;
21
+ }
22
+ export interface UpdateDataSourceOptions {
23
+ /**
24
+ * Keys of newly added items that should be animated.
25
+ * If provided, these items will show the adding animation.
26
+ * If not provided, new items will appear without animation.
27
+ */
28
+ addedKeys?: (string | number)[];
29
+ /**
30
+ * Keys of items being removed that should be animated.
31
+ * These items should NOT be in the new data array.
32
+ * The hook will temporarily keep them for animation, then remove after animation completes.
33
+ */
34
+ removedKeys?: (string | number)[];
35
+ }
36
+ export interface UseTableDataSourceReturn<T extends TableDataSource> {
37
+ /** Current data source to pass to Table component */
38
+ dataSource: T[];
39
+ /**
40
+ * Update the data source with optional animation support.
41
+ * This is the recommended method for GraphQL refetch pattern.
42
+ *
43
+ * @example
44
+ * // After create mutation + refetch
45
+ * const { data } = await refetch();
46
+ * updateDataSource(data.items, { addedKeys: [newItem.id] });
47
+ *
48
+ * @example
49
+ * // After delete mutation + refetch
50
+ * const { data } = await refetch();
51
+ * updateDataSource(data.items, { removedKeys: [deletedId] });
52
+ */
53
+ updateDataSource: (data: T[], options?: UpdateDataSourceOptions) => void;
54
+ /** Transition state for Table component */
55
+ transitionState: TableTransitionState;
56
+ }
57
+ export declare function useTableDataSource<T extends TableDataSource>(options?: UseTableDataSourceOptions<T>): UseTableDataSourceReturn<T>;
@@ -0,0 +1,183 @@
1
+ 'use client';
2
+ import { useState, useRef, useEffect, useCallback, useMemo } from 'react';
3
+ import { getRowKey } from '@mezzanine-ui/core/table';
4
+
5
+ function useTableDataSource(options = {}) {
6
+ const { initialData = [], highlightDuration = 1000, fadeOutDuration = 200, } = options;
7
+ // Internal data source that includes items being deleted (for animation)
8
+ const [internalData, setInternalData] = useState(initialData);
9
+ // Track adding keys
10
+ const [addingKeys, setAddingKeys] = useState(new Set());
11
+ // Track deleting keys (red highlight phase)
12
+ const [deletingKeys, setDeletingKeys] = useState(new Set());
13
+ // Track fading out keys
14
+ const [fadingOutKeys, setFadingOutKeys] = useState(new Set());
15
+ // Keep track of items pending removal (after fade out completes)
16
+ const pendingRemovalRef = useRef(new Set());
17
+ // Store items being animated for removal (needed when refetch returns data without these items)
18
+ const removingItemsRef = useRef(new Map());
19
+ // Timers ref for cleanup
20
+ const timersRef = useRef(new Map());
21
+ // Cleanup timers on unmount
22
+ useEffect(() => {
23
+ return () => {
24
+ const { current: timers } = timersRef;
25
+ timers.forEach((timer) => clearTimeout(timer));
26
+ timers.clear();
27
+ };
28
+ }, []);
29
+ const clearTimerForKey = useCallback((key) => {
30
+ const existingTimer = timersRef.current.get(key);
31
+ if (existingTimer) {
32
+ clearTimeout(existingTimer);
33
+ timersRef.current.delete(key);
34
+ }
35
+ }, []);
36
+ // Helper to start adding animation for a key
37
+ const startAddingAnimation = useCallback((key) => {
38
+ clearTimerForKey(key);
39
+ setAddingKeys((prev) => new Set(prev).add(key));
40
+ const timer = setTimeout(() => {
41
+ setAddingKeys((prev) => {
42
+ const next = new Set(prev);
43
+ next.delete(key);
44
+ return next;
45
+ });
46
+ timersRef.current.delete(key);
47
+ }, highlightDuration);
48
+ timersRef.current.set(key, timer);
49
+ }, [highlightDuration, clearTimerForKey]);
50
+ // Helper to start removing animation for a key, returns cleanup function
51
+ const startRemovingAnimation = useCallback((keyStr, onComplete) => {
52
+ clearTimerForKey(keyStr);
53
+ clearTimerForKey(`${keyStr}-fade`);
54
+ pendingRemovalRef.current.add(keyStr);
55
+ setDeletingKeys((prev) => new Set(prev).add(keyStr));
56
+ const highlightTimer = setTimeout(() => {
57
+ setDeletingKeys((prev) => {
58
+ const next = new Set(prev);
59
+ next.delete(keyStr);
60
+ return next;
61
+ });
62
+ setFadingOutKeys((prev) => new Set(prev).add(keyStr));
63
+ const fadeTimer = setTimeout(() => {
64
+ setFadingOutKeys((prev) => {
65
+ const next = new Set(prev);
66
+ next.delete(keyStr);
67
+ return next;
68
+ });
69
+ pendingRemovalRef.current.delete(keyStr);
70
+ removingItemsRef.current.delete(keyStr);
71
+ timersRef.current.delete(`${keyStr}-fade`);
72
+ onComplete();
73
+ }, fadeOutDuration);
74
+ timersRef.current.set(`${keyStr}-fade`, fadeTimer);
75
+ timersRef.current.delete(keyStr);
76
+ }, highlightDuration);
77
+ timersRef.current.set(keyStr, highlightTimer);
78
+ }, [highlightDuration, fadeOutDuration, clearTimerForKey]);
79
+ const updateDataSource = useCallback((data, updateOptions) => {
80
+ const { addedKeys = [], removedKeys = [] } = updateOptions || {};
81
+ // Convert to string keys for comparison
82
+ const addedKeyStrs = new Set(addedKeys.map(String));
83
+ const removedKeyStrs = new Set(removedKeys.map(String));
84
+ // Get current data keys for comparison
85
+ const currentKeys = new Set(internalData.map((item) => getRowKey(item)));
86
+ const newKeys = new Set(data.map((item) => getRowKey(item)));
87
+ // Items to animate as added (either explicitly specified or auto-detected)
88
+ const keysToAnimateAdd = addedKeyStrs;
89
+ // Items to animate as removed
90
+ const keysToAnimateRemove = removedKeyStrs;
91
+ // For removed items, we need to keep them temporarily for animation
92
+ // Find items that are being removed and store them
93
+ const itemsToKeepForAnimation = [];
94
+ keysToAnimateRemove.forEach((keyStr) => {
95
+ // Check if already animating
96
+ if (pendingRemovalRef.current.has(keyStr)) {
97
+ // Already animating, keep the stored item
98
+ const storedItem = removingItemsRef.current.get(keyStr);
99
+ if (storedItem) {
100
+ itemsToKeepForAnimation.push(storedItem);
101
+ }
102
+ return;
103
+ }
104
+ // Find in current data
105
+ const item = internalData.find((i) => getRowKey(i) === keyStr);
106
+ if (item && !newKeys.has(keyStr)) {
107
+ removingItemsRef.current.set(keyStr, item);
108
+ itemsToKeepForAnimation.push(item);
109
+ // Start remove animation
110
+ startRemovingAnimation(keyStr, () => {
111
+ // After animation, remove from internal data
112
+ setInternalData((prev) => prev.filter((i) => getRowKey(i) !== keyStr));
113
+ });
114
+ }
115
+ });
116
+ // Clear adding animations for items no longer in data
117
+ setAddingKeys((prev) => {
118
+ const next = new Set(prev);
119
+ prev.forEach((key) => {
120
+ if (!newKeys.has(key) && !keysToAnimateRemove.has(key)) {
121
+ next.delete(key);
122
+ clearTimerForKey(key);
123
+ }
124
+ });
125
+ return next;
126
+ });
127
+ // Build final data: new data + items being animated for removal
128
+ const finalData = [...data];
129
+ // Add items being removed (for animation) that aren't already in new data
130
+ itemsToKeepForAnimation.forEach((item) => {
131
+ const key = getRowKey(item);
132
+ if (!newKeys.has(key)) {
133
+ // Find original position or add at the end
134
+ const originalIndex = internalData.findIndex((i) => getRowKey(i) === key);
135
+ if (originalIndex !== -1) {
136
+ // Try to maintain relative position
137
+ finalData.splice(Math.min(originalIndex, finalData.length), 0, item);
138
+ }
139
+ else {
140
+ finalData.push(item);
141
+ }
142
+ }
143
+ });
144
+ // Also keep any currently animating items that aren't in the removedKeys
145
+ pendingRemovalRef.current.forEach((keyStr) => {
146
+ if (!keysToAnimateRemove.has(keyStr) &&
147
+ !finalData.some((i) => getRowKey(i) === keyStr)) {
148
+ const storedItem = removingItemsRef.current.get(keyStr);
149
+ if (storedItem) {
150
+ finalData.push(storedItem);
151
+ }
152
+ }
153
+ });
154
+ setInternalData(finalData);
155
+ // Start adding animations for new items
156
+ keysToAnimateAdd.forEach((keyStr) => {
157
+ if (newKeys.has(keyStr) && !currentKeys.has(keyStr)) {
158
+ startAddingAnimation(keyStr);
159
+ }
160
+ else if (newKeys.has(keyStr)) {
161
+ // Item exists, still animate it
162
+ startAddingAnimation(keyStr);
163
+ }
164
+ });
165
+ }, [
166
+ internalData,
167
+ clearTimerForKey,
168
+ startAddingAnimation,
169
+ startRemovingAnimation,
170
+ ]);
171
+ const transitionState = useMemo(() => ({
172
+ addingKeys,
173
+ deletingKeys,
174
+ fadingOutKeys,
175
+ }), [addingKeys, deletingKeys, fadingOutKeys]);
176
+ return {
177
+ dataSource: internalData,
178
+ transitionState,
179
+ updateDataSource,
180
+ };
181
+ }
182
+
183
+ export { useTableDataSource };
@@ -0,0 +1,7 @@
1
+ import { type TableDataSource, type TableExpandable } from '@mezzanine-ui/core/table';
2
+ import type { TableExpansionState } from '../TableContext';
3
+ export interface UseTableExpansionOptions<T extends TableDataSource> {
4
+ expandable?: TableExpandable<T>;
5
+ hasDragHandle: boolean;
6
+ }
7
+ export declare function useTableExpansion<T extends TableDataSource>({ expandable, hasDragHandle, }: UseTableExpansionOptions<T>): TableExpansionState<T> | undefined;
@@ -0,0 +1,52 @@
1
+ 'use client';
2
+ import { useState, useCallback, useMemo } from 'react';
3
+ import { DRAG_HANDLE_COLUMN_WIDTH, EXPANSION_COLUMN_WIDTH } from '@mezzanine-ui/core/table';
4
+
5
+ function useTableExpansion({ expandable, hasDragHandle, }) {
6
+ const [internalExpandedKeys, setInternalExpandedKeys] = useState([]);
7
+ const { expandedRowKeys: expandedRowKeysProp, onExpand, onExpandedRowsChange, } = expandable || {};
8
+ const isControlled = expandedRowKeysProp !== undefined;
9
+ const expandedRowKeys = isControlled && expandedRowKeysProp
10
+ ? expandedRowKeysProp
11
+ : internalExpandedKeys;
12
+ const isRowExpanded = useCallback((key) => {
13
+ return expandedRowKeys.includes(key);
14
+ }, [expandedRowKeys]);
15
+ const toggleExpand = useCallback((key, record) => {
16
+ const isExpanded = expandedRowKeys.includes(key);
17
+ const newKeys = isExpanded
18
+ ? expandedRowKeys.filter((k) => k !== key)
19
+ : [...expandedRowKeys, key];
20
+ if (!isControlled) {
21
+ setInternalExpandedKeys(newKeys);
22
+ }
23
+ onExpand === null || onExpand === void 0 ? void 0 : onExpand(!isExpanded, record);
24
+ onExpandedRowsChange === null || onExpandedRowsChange === void 0 ? void 0 : onExpandedRowsChange(newKeys);
25
+ }, [expandedRowKeys, isControlled, onExpand, onExpandedRowsChange]);
26
+ const expansionLeftPadding = useMemo(() => {
27
+ let padding = 0;
28
+ // Number(
29
+ // getCSSVariableValue('--mzn-spacing-padding-horizontal-comfort').replace(
30
+ // 'rem',
31
+ // '',
32
+ // ),
33
+ // ) * 16;
34
+ if (hasDragHandle)
35
+ padding += DRAG_HANDLE_COLUMN_WIDTH;
36
+ if (expandable)
37
+ padding += EXPANSION_COLUMN_WIDTH;
38
+ return padding;
39
+ }, [expandable, hasDragHandle]);
40
+ if (!expandable) {
41
+ return undefined;
42
+ }
43
+ return {
44
+ config: expandable,
45
+ expansionLeftPadding,
46
+ expandedRowKeys,
47
+ isRowExpanded,
48
+ toggleExpand,
49
+ };
50
+ }
51
+
52
+ export { useTableExpansion };
@@ -0,0 +1,29 @@
1
+ import { type TableColumn } from '@mezzanine-ui/core/table';
2
+ import type { ActionColumnConfig } from './typings';
3
+ export interface FixedOffsetInfo {
4
+ /** CSS offset value for the column */
5
+ offset: number;
6
+ /** Fixed side: 'start' or 'end' */
7
+ side: 'start' | 'end';
8
+ }
9
+ export interface UseTableFixedOffsetsReturn {
10
+ /** Get offset info for a specific column by key */
11
+ getColumnOffset: (key: string) => FixedOffsetInfo | null;
12
+ /** Get offset info for drag handle column */
13
+ getDragHandleOffset: () => FixedOffsetInfo | null;
14
+ /** Get offset info for selection column */
15
+ getSelectionOffset: () => FixedOffsetInfo | null;
16
+ /** Get offset info for expansion column */
17
+ getExpansionOffset: () => FixedOffsetInfo | null;
18
+ /** Check if a column should show shadow based on scroll position */
19
+ shouldShowShadow: (key: string, scrollLeft: number, containerWidth: number) => boolean;
20
+ }
21
+ export interface UseTableFixedOffsetsOptions {
22
+ /** Column definitions */
23
+ columns: TableColumn[];
24
+ /** Action column configuration */
25
+ actionConfig: ActionColumnConfig;
26
+ /** Get computed width for a column (from columnState) */
27
+ getResizedColumnWidth?: (key: string) => number | undefined;
28
+ }
29
+ export declare function useTableFixedOffsets(options: UseTableFixedOffsetsOptions): UseTableFixedOffsetsReturn;
@@ -0,0 +1,241 @@
1
+ 'use client';
2
+ import { useState, useEffect, useCallback, useMemo } from 'react';
3
+ import { DRAG_HANDLE_KEY, DRAG_HANDLE_COLUMN_WIDTH, EXPANSION_KEY, EXPANSION_COLUMN_WIDTH, SELECTION_KEY, SELECTION_COLUMN_WIDTH } from '@mezzanine-ui/core/table';
4
+ import { useTableSuperContext } from '../TableContext.js';
5
+
6
+ const parseFixed = (fixed) => {
7
+ if (fixed === true || fixed === 'start')
8
+ return 'start';
9
+ if (fixed === 'end')
10
+ return 'end';
11
+ return null;
12
+ };
13
+ function useTableFixedOffsets(options) {
14
+ const { expansionLeftPadding = 0 } = useTableSuperContext();
15
+ const { actionConfig, columns, getResizedColumnWidth } = options;
16
+ // Store measured widths
17
+ const [measuredWidths, setMeasuredWidths] = useState(new Map());
18
+ const { hasDragHandle, hasExpansion, hasSelection } = actionConfig;
19
+ useEffect(() => {
20
+ const innerMap = new Map();
21
+ if (hasDragHandle) {
22
+ innerMap.set(DRAG_HANDLE_KEY, DRAG_HANDLE_COLUMN_WIDTH);
23
+ }
24
+ if (hasExpansion) {
25
+ innerMap.set(EXPANSION_KEY, EXPANSION_COLUMN_WIDTH);
26
+ }
27
+ if (hasSelection) {
28
+ innerMap.set(SELECTION_KEY, SELECTION_COLUMN_WIDTH);
29
+ }
30
+ setMeasuredWidths(innerMap);
31
+ }, [hasDragHandle, hasExpansion, hasSelection]);
32
+ // Get width for a column (prioritize measured, then computed, then defined, then fallback)
33
+ const getWidth = useCallback((key, fallback = 0) => {
34
+ // Get static widths first
35
+ const measured = measuredWidths.get(key);
36
+ if (measured !== undefined && measured > 0) {
37
+ return measured;
38
+ }
39
+ // get resized column width
40
+ const computed = getResizedColumnWidth === null || getResizedColumnWidth === void 0 ? void 0 : getResizedColumnWidth(key);
41
+ if (computed !== undefined) {
42
+ return computed;
43
+ }
44
+ // Then try to find column definition width
45
+ const column = columns.find((col) => col.key === key);
46
+ if ((column === null || column === void 0 ? void 0 : column.width) !== undefined) {
47
+ return column.width;
48
+ }
49
+ return fallback;
50
+ }, [columns, getResizedColumnWidth, measuredWidths]);
51
+ // Build ordered list of all fixed columns
52
+ const { fixedEndKeys, fixedStartKeys } = useMemo(() => {
53
+ const startKeys = [];
54
+ const endKeys = [];
55
+ // Action columns first (in order: drag handle, expansion, selection)
56
+ if (actionConfig.hasDragHandle && actionConfig.dragHandleFixed) {
57
+ startKeys.push(DRAG_HANDLE_KEY);
58
+ }
59
+ if (actionConfig.hasExpansion && actionConfig.expansionFixed) {
60
+ startKeys.push(EXPANSION_KEY);
61
+ }
62
+ if (actionConfig.hasSelection && actionConfig.selectionFixed) {
63
+ startKeys.push(SELECTION_KEY);
64
+ }
65
+ // Then data columns
66
+ columns.forEach((column) => {
67
+ const side = parseFixed(column.fixed);
68
+ if (side === 'start') {
69
+ startKeys.push(column.key);
70
+ }
71
+ else if (side === 'end') {
72
+ endKeys.push(column.key);
73
+ }
74
+ });
75
+ return { fixedEndKeys: endKeys, fixedStartKeys: startKeys };
76
+ }, [actionConfig, columns]);
77
+ // Calculate all fixed offsets
78
+ const fixedOffsets = useMemo(() => {
79
+ const startOffsets = new Map();
80
+ const endOffsets = new Map();
81
+ // Calculate start offsets
82
+ let currentStartOffset = 0;
83
+ fixedStartKeys.forEach((key) => {
84
+ startOffsets.set(key, {
85
+ offset: currentStartOffset,
86
+ side: 'start',
87
+ });
88
+ currentStartOffset += getWidth(key);
89
+ });
90
+ // Calculate end offsets (from right to left)
91
+ let currentEndOffset = 0;
92
+ for (let i = fixedEndKeys.length - 1; i >= 0; i--) {
93
+ const key = fixedEndKeys[i];
94
+ endOffsets.set(key, {
95
+ offset: currentEndOffset,
96
+ side: 'end',
97
+ });
98
+ currentEndOffset += getWidth(key);
99
+ }
100
+ return { endOffsets, startOffsets };
101
+ }, [fixedEndKeys, fixedStartKeys, getWidth]);
102
+ // Build ordered list of all columns (for position calculation)
103
+ const allColumnKeys = useMemo(() => {
104
+ const keys = [];
105
+ // Action columns first
106
+ if (hasDragHandle) {
107
+ keys.push(DRAG_HANDLE_KEY);
108
+ }
109
+ if (hasExpansion) {
110
+ keys.push(EXPANSION_KEY);
111
+ }
112
+ if (hasSelection) {
113
+ keys.push(SELECTION_KEY);
114
+ }
115
+ // Then data columns
116
+ columns.forEach((column) => {
117
+ keys.push(column.key);
118
+ });
119
+ return keys;
120
+ }, [hasDragHandle, hasSelection, hasExpansion, columns]);
121
+ // Calculate original positions (left edge) for all columns
122
+ const originalPositions = useMemo(() => {
123
+ const positions = new Map();
124
+ let currentPosition = 0;
125
+ allColumnKeys.forEach((key) => {
126
+ positions.set(key, currentPosition);
127
+ currentPosition += getWidth(key);
128
+ });
129
+ return positions;
130
+ }, [allColumnKeys, getWidth]);
131
+ /**
132
+ * Determine if a column should show shadow.
133
+ */
134
+ const shouldShowShadow = useCallback((key, scrollLeft, containerWidth) => {
135
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
136
+ const offsetInfo = (_a = fixedOffsets.startOffsets.get(key)) !== null && _a !== void 0 ? _a : fixedOffsets.endOffsets.get(key);
137
+ if (!offsetInfo)
138
+ return false;
139
+ if (offsetInfo.side === 'start') {
140
+ // For start-fixed columns
141
+ const keyIndex = fixedStartKeys.indexOf(key);
142
+ if (keyIndex === -1)
143
+ return false;
144
+ const originalPos = (_b = originalPositions.get(key)) !== null && _b !== void 0 ? _b : 0;
145
+ const isSticky = scrollLeft - expansionLeftPadding > originalPos - offsetInfo.offset;
146
+ if (!isSticky)
147
+ return false;
148
+ // Now check if there's a gap to the right (shadow should show)
149
+ // Find the next fixed-start column
150
+ const nextIndex = keyIndex + 1;
151
+ if (nextIndex < fixedStartKeys.length) {
152
+ const nextKey = fixedStartKeys[nextIndex];
153
+ const nextOriginalPos = (_c = originalPositions.get(nextKey)) !== null && _c !== void 0 ? _c : 0;
154
+ const nextOffset = (_e = (_d = fixedOffsets.startOffsets.get(nextKey)) === null || _d === void 0 ? void 0 : _d.offset) !== null && _e !== void 0 ? _e : 0;
155
+ // Gap closes when scroll reaches the point where next column becomes sticky
156
+ const isNextSticky = scrollLeft - expansionLeftPadding >= nextOriginalPos - nextOffset;
157
+ return !isNextSticky;
158
+ }
159
+ return true;
160
+ }
161
+ else {
162
+ // For end-fixed columns
163
+ const keyIndex = fixedEndKeys.indexOf(key);
164
+ if (keyIndex === -1)
165
+ return false;
166
+ // Get this column's original position from left
167
+ const originalPos = (_f = originalPositions.get(key)) !== null && _f !== void 0 ? _f : 0;
168
+ const columnWidth = getWidth(key);
169
+ const isSticky = scrollLeft + containerWidth <
170
+ originalPos + columnWidth + offsetInfo.offset + expansionLeftPadding;
171
+ if (!isSticky)
172
+ return false;
173
+ // Check if there's a gap to the left (shadow should show)
174
+ const prevIndex = keyIndex - 1;
175
+ if (~prevIndex) {
176
+ const prevKey = fixedEndKeys[prevIndex];
177
+ const prevOriginalPos = (_g = originalPositions.get(prevKey)) !== null && _g !== void 0 ? _g : 0;
178
+ const prevOffset = (_j = (_h = fixedOffsets.endOffsets.get(prevKey)) === null || _h === void 0 ? void 0 : _h.offset) !== null && _j !== void 0 ? _j : 0;
179
+ const prevColumnWidth = getWidth(prevKey);
180
+ const isPrevSticky = scrollLeft + containerWidth <
181
+ prevOriginalPos +
182
+ prevColumnWidth +
183
+ prevOffset +
184
+ expansionLeftPadding;
185
+ // If both are sticky, the gap is closed if this column's
186
+ // left visual edge touches the prev column's right visual edge
187
+ return !isPrevSticky;
188
+ }
189
+ // This is the leftmost fixed-end column
190
+ return true;
191
+ }
192
+ }, [
193
+ fixedEndKeys,
194
+ fixedOffsets,
195
+ fixedStartKeys,
196
+ getWidth,
197
+ originalPositions,
198
+ expansionLeftPadding,
199
+ ]);
200
+ const getColumnOffset = useCallback((key) => {
201
+ var _a, _b;
202
+ return ((_b = (_a = fixedOffsets.startOffsets.get(key)) !== null && _a !== void 0 ? _a : fixedOffsets.endOffsets.get(key)) !== null && _b !== void 0 ? _b : null);
203
+ }, [fixedOffsets]);
204
+ const getDragHandleOffset = useCallback(() => {
205
+ var _a;
206
+ if (!actionConfig.hasDragHandle || !actionConfig.dragHandleFixed) {
207
+ return null;
208
+ }
209
+ return (_a = fixedOffsets.startOffsets.get(DRAG_HANDLE_KEY)) !== null && _a !== void 0 ? _a : null;
210
+ }, [actionConfig, fixedOffsets]);
211
+ const getSelectionOffset = useCallback(() => {
212
+ var _a;
213
+ if (!actionConfig.hasSelection || !actionConfig.selectionFixed) {
214
+ return null;
215
+ }
216
+ return (_a = fixedOffsets.startOffsets.get(SELECTION_KEY)) !== null && _a !== void 0 ? _a : null;
217
+ }, [actionConfig, fixedOffsets]);
218
+ const getExpansionOffset = useCallback(() => {
219
+ var _a;
220
+ if (!actionConfig.hasExpansion || !actionConfig.expansionFixed) {
221
+ return null;
222
+ }
223
+ return (_a = fixedOffsets.startOffsets.get(EXPANSION_KEY)) !== null && _a !== void 0 ? _a : null;
224
+ }, [actionConfig, fixedOffsets]);
225
+ // Memoize return object to prevent unnecessary re-renders
226
+ return useMemo(() => ({
227
+ getColumnOffset,
228
+ getDragHandleOffset,
229
+ getExpansionOffset,
230
+ getSelectionOffset,
231
+ shouldShowShadow,
232
+ }), [
233
+ getColumnOffset,
234
+ getDragHandleOffset,
235
+ getExpansionOffset,
236
+ getSelectionOffset,
237
+ shouldShowShadow,
238
+ ]);
239
+ }
240
+
241
+ export { useTableFixedOffsets };
@@ -0,0 +1,12 @@
1
+ export interface UseTableScrollReturn {
2
+ /** Container width (viewport width) */
3
+ containerWidth: number;
4
+ handleScroll: (event: React.UIEvent<HTMLDivElement>) => void;
5
+ isScrollingHorizontally: boolean;
6
+ scrollLeft: number;
7
+ /** Set refs for measuring container width */
8
+ setContainerRef: (element: HTMLDivElement | null) => void;
9
+ }
10
+ export declare function useTableScroll({ enabled, }: {
11
+ enabled: boolean;
12
+ }): UseTableScrollReturn;
@@ -0,0 +1,58 @@
1
+ 'use client';
2
+ import { useState, useRef, useCallback, useEffect, useMemo } from 'react';
3
+
4
+ function useTableScroll({ enabled, }) {
5
+ const [isScrollingHorizontally, setIsScrollingHorizontally] = useState(false);
6
+ const [scrollLeft, setScrollLeft] = useState(0);
7
+ const [containerWidth, setContainerWidth] = useState(0);
8
+ const containerRef = useRef(null);
9
+ const setContainerRef = useCallback((element) => {
10
+ containerRef.current = element;
11
+ }, []);
12
+ const measureDimensions = useCallback(() => {
13
+ if (containerRef.current) {
14
+ const newContainerWidth = containerRef.current.clientWidth;
15
+ setContainerWidth((prev) => prev !== newContainerWidth ? newContainerWidth : prev);
16
+ }
17
+ }, []);
18
+ const handleScroll = useCallback((event) => {
19
+ const target = event.currentTarget;
20
+ const newScrollLeft = target.scrollLeft;
21
+ setScrollLeft(newScrollLeft);
22
+ setIsScrollingHorizontally(newScrollLeft > 0);
23
+ }, []);
24
+ // Set up ResizeObserver to track dimension changes
25
+ useEffect(() => {
26
+ const { current: container } = containerRef;
27
+ if (!container || !enabled)
28
+ return;
29
+ const resizeObserver = new ResizeObserver(() => {
30
+ measureDimensions();
31
+ });
32
+ resizeObserver.observe(container);
33
+ return () => {
34
+ resizeObserver.disconnect();
35
+ };
36
+ }, [measureDimensions, enabled]);
37
+ // Initial measurement after mount
38
+ useEffect(() => {
39
+ if (enabled) {
40
+ measureDimensions();
41
+ }
42
+ }, [measureDimensions, enabled]);
43
+ return useMemo(() => ({
44
+ containerWidth,
45
+ handleScroll,
46
+ isScrollingHorizontally,
47
+ scrollLeft,
48
+ setContainerRef,
49
+ }), [
50
+ containerWidth,
51
+ handleScroll,
52
+ isScrollingHorizontally,
53
+ scrollLeft,
54
+ setContainerRef,
55
+ ]);
56
+ }
57
+
58
+ export { useTableScroll };
@@ -0,0 +1,7 @@
1
+ import { type TableDataSource, type TableRowSelection } from '@mezzanine-ui/core/table';
2
+ import type { TableSelectionState } from '../TableContext';
3
+ export interface UseTableSelectionOptions<T extends TableDataSource> {
4
+ dataSource: T[];
5
+ rowSelection?: TableRowSelection<T>;
6
+ }
7
+ export declare function useTableSelection<T extends TableDataSource>({ dataSource, rowSelection, }: UseTableSelectionOptions<T>): TableSelectionState<T> | undefined;