@dreamtree-org/twreact-ui 1.3.11 → 2.0.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 (164) hide show
  1. package/README.md +40 -6
  2. package/ai-skills/dreamtree-ui.md +47 -9
  3. package/codemods/v2.cjs +198 -0
  4. package/dist/ai-schema.json +609 -33
  5. package/dist/components/blocks/AppShell.d.ts.map +1 -1
  6. package/dist/components/blocks/BottomActionBar.d.ts +6 -0
  7. package/dist/components/blocks/BottomActionBar.d.ts.map +1 -0
  8. package/dist/components/blocks/ConfirmDialog.d.ts.map +1 -1
  9. package/dist/components/blocks/PageHeader.d.ts.map +1 -1
  10. package/dist/components/core/Checkbox.d.ts.map +1 -1
  11. package/dist/components/core/DatePicker.d.ts.map +1 -1
  12. package/dist/components/core/DateRangePicker.d.ts.map +1 -1
  13. package/dist/components/core/Form.d.ts.map +1 -1
  14. package/dist/components/core/Input.d.ts.map +1 -1
  15. package/dist/components/core/Popover.d.ts.map +1 -1
  16. package/dist/components/core/ProgressBar.d.ts.map +1 -1
  17. package/dist/components/core/Rate.d.ts.map +1 -1
  18. package/dist/components/core/SearchField.d.ts +9 -0
  19. package/dist/components/core/SearchField.d.ts.map +1 -0
  20. package/dist/components/core/SegmentedControl.d.ts +8 -0
  21. package/dist/components/core/SegmentedControl.d.ts.map +1 -0
  22. package/dist/components/core/Select.d.ts.map +1 -1
  23. package/dist/components/core/SwipeableListItem.d.ts +4 -0
  24. package/dist/components/core/SwipeableListItem.d.ts.map +1 -0
  25. package/dist/components/core/SwipeableListItemGesture.d.ts +58 -0
  26. package/dist/components/core/SwipeableListItemGesture.d.ts.map +1 -0
  27. package/dist/components/core/Switch.d.ts.map +1 -1
  28. package/dist/components/core/Table/ActionMenu.d.ts +10 -3
  29. package/dist/components/core/Table/ActionMenu.d.ts.map +1 -1
  30. package/dist/components/core/Table/MobileCard.d.ts +10 -1
  31. package/dist/components/core/Table/MobileCard.d.ts.map +1 -1
  32. package/dist/components/core/Table/MobileFilters.d.ts +8 -2
  33. package/dist/components/core/Table/MobileFilters.d.ts.map +1 -1
  34. package/dist/components/core/Table/Table.d.ts.map +1 -1
  35. package/dist/components/core/Table/TableRow.d.ts +2 -1
  36. package/dist/components/core/Table/TableRow.d.ts.map +1 -1
  37. package/dist/components/core/Tabs.d.ts +3 -3
  38. package/dist/components/core/Tabs.d.ts.map +1 -1
  39. package/dist/components/core/ThreeDotPopover.d.ts +10 -22
  40. package/dist/components/core/ThreeDotPopover.d.ts.map +1 -1
  41. package/dist/components/core/Tooltip.d.ts +2 -1
  42. package/dist/components/core/Tooltip.d.ts.map +1 -1
  43. package/dist/components/core/internal/Calendar.d.ts +77 -0
  44. package/dist/components/core/internal/Calendar.d.ts.map +1 -0
  45. package/dist/components/core/menu/ActionMenuSurface.d.ts +42 -0
  46. package/dist/components/core/menu/ActionMenuSurface.d.ts.map +1 -0
  47. package/dist/components/core/menu/ActionSheetMenu.d.ts +17 -0
  48. package/dist/components/core/menu/ActionSheetMenu.d.ts.map +1 -0
  49. package/dist/components/core/menu/MenuPopover.d.ts +21 -0
  50. package/dist/components/core/menu/MenuPopover.d.ts.map +1 -0
  51. package/dist/components/core/menu/menuItems.d.ts +21 -0
  52. package/dist/components/core/menu/menuItems.d.ts.map +1 -0
  53. package/dist/components/core/menu/menuKeyboard.d.ts +16 -0
  54. package/dist/components/core/menu/menuKeyboard.d.ts.map +1 -0
  55. package/dist/components/feedback/ActionSheet.d.ts +4 -0
  56. package/dist/components/feedback/ActionSheet.d.ts.map +1 -0
  57. package/dist/components/feedback/Alert.d.ts.map +1 -1
  58. package/dist/components/feedback/Dialog.d.ts.map +1 -1
  59. package/dist/components/feedback/Drawer.d.ts.map +1 -1
  60. package/dist/components/feedback/Sheet.d.ts +4 -0
  61. package/dist/components/feedback/Sheet.d.ts.map +1 -0
  62. package/dist/components/feedback/Toast.d.ts +4 -3
  63. package/dist/components/feedback/Toast.d.ts.map +1 -1
  64. package/dist/components/feedback/ToastItem.d.ts +47 -0
  65. package/dist/components/feedback/ToastItem.d.ts.map +1 -0
  66. package/dist/components/feedback/ToastStack.d.ts +20 -0
  67. package/dist/components/feedback/ToastStack.d.ts.map +1 -0
  68. package/dist/components/feedback/ToastSwipe.d.ts +22 -0
  69. package/dist/components/feedback/ToastSwipe.d.ts.map +1 -0
  70. package/dist/components/feedback/Tour.d.ts.map +1 -1
  71. package/dist/components/feedback/sheetCore.d.ts +30 -0
  72. package/dist/components/feedback/sheetCore.d.ts.map +1 -0
  73. package/dist/components/navigation/Navbar.d.ts.map +1 -1
  74. package/dist/components/navigation/Sidebar.d.ts +6 -0
  75. package/dist/components/navigation/Sidebar.d.ts.map +1 -1
  76. package/dist/components/rich-text/RichTextEditor.d.ts.map +1 -1
  77. package/dist/components/rich-text/RichTextToolbar.d.ts +6 -2
  78. package/dist/components/rich-text/RichTextToolbar.d.ts.map +1 -1
  79. package/dist/components/utility/PullToRefresh.d.ts +5 -0
  80. package/dist/components/utility/PullToRefresh.d.ts.map +1 -0
  81. package/dist/esm/components/blocks/AppShell.js +97 -90
  82. package/dist/esm/components/blocks/BottomActionBar.js +132 -0
  83. package/dist/esm/components/blocks/ConfirmDialog.js +40 -4
  84. package/dist/esm/components/blocks/PageHeader.js +16 -3
  85. package/dist/esm/components/core/Checkbox.js +13 -24
  86. package/dist/esm/components/core/DatePicker.js +117 -304
  87. package/dist/esm/components/core/DateRangePicker.js +260 -308
  88. package/dist/esm/components/core/Form.js +11 -2
  89. package/dist/esm/components/core/Input.js +13 -18
  90. package/dist/esm/components/core/Popover.js +59 -48
  91. package/dist/esm/components/core/ProgressBar.js +36 -64
  92. package/dist/esm/components/core/Rate.js +5 -10
  93. package/dist/esm/components/core/SearchField.js +311 -0
  94. package/dist/esm/components/core/SegmentedControl.js +189 -0
  95. package/dist/esm/components/core/Select.js +185 -190
  96. package/dist/esm/components/core/SwipeableListItem.js +286 -0
  97. package/dist/esm/components/core/SwipeableListItemGesture.js +219 -0
  98. package/dist/esm/components/core/Switch.js +11 -13
  99. package/dist/esm/components/core/Table/ActionMenu.js +45 -231
  100. package/dist/esm/components/core/Table/ColumnMenu.js +1 -1
  101. package/dist/esm/components/core/Table/MobileCard.js +66 -16
  102. package/dist/esm/components/core/Table/MobileFilters.js +181 -10
  103. package/dist/esm/components/core/Table/Table.js +149 -102
  104. package/dist/esm/components/core/Table/TableRow.js +10 -2
  105. package/dist/esm/components/core/Tabs.js +7 -7
  106. package/dist/esm/components/core/ThreeDotPopover.js +93 -99
  107. package/dist/esm/components/core/Tooltip.js +138 -145
  108. package/dist/esm/components/core/internal/Calendar.js +381 -0
  109. package/dist/esm/components/core/menu/ActionMenuSurface.js +110 -0
  110. package/dist/esm/components/core/menu/ActionSheetMenu.js +140 -0
  111. package/dist/esm/components/core/menu/MenuPopover.js +177 -0
  112. package/dist/esm/components/core/menu/menuItems.js +61 -0
  113. package/dist/esm/components/core/menu/menuKeyboard.js +54 -0
  114. package/dist/esm/components/feedback/ActionSheet.js +55 -0
  115. package/dist/esm/components/feedback/Alert.js +63 -53
  116. package/dist/esm/components/feedback/Dialog.js +141 -53
  117. package/dist/esm/components/feedback/Drawer.js +130 -132
  118. package/dist/esm/components/feedback/Sheet.js +125 -0
  119. package/dist/esm/components/feedback/Toast.js +21 -291
  120. package/dist/esm/components/feedback/ToastItem.js +348 -0
  121. package/dist/esm/components/feedback/ToastStack.js +130 -0
  122. package/dist/esm/components/feedback/ToastSwipe.js +52 -0
  123. package/dist/esm/components/feedback/Tour.js +107 -30
  124. package/dist/esm/components/feedback/sheetCore.js +494 -0
  125. package/dist/esm/components/navigation/Dropdown.js +1 -1
  126. package/dist/esm/components/navigation/Navbar.js +172 -37
  127. package/dist/esm/components/navigation/Sidebar.js +263 -111
  128. package/dist/esm/components/rich-text/RichTextEditor.js +9 -3
  129. package/dist/esm/components/rich-text/RichTextToolbar.js +29 -9
  130. package/dist/esm/components/utility/Portal.js +31 -0
  131. package/dist/esm/components/utility/PullToRefresh.js +229 -0
  132. package/dist/esm/hooks/useFloatingPosition.js +82 -0
  133. package/dist/esm/hooks/useHaptics.js +65 -0
  134. package/dist/esm/hooks/usePresence.js +58 -0
  135. package/dist/esm/hooks/usePresentation.js +21 -0
  136. package/dist/esm/hooks/useSheetDrag.js +220 -0
  137. package/dist/esm/index.js +13 -2
  138. package/dist/esm/utils/position.js +10 -63
  139. package/dist/hooks/useFloatingPosition.d.ts +8 -4
  140. package/dist/hooks/useFloatingPosition.d.ts.map +1 -1
  141. package/dist/hooks/useHaptics.d.ts +19 -0
  142. package/dist/hooks/useHaptics.d.ts.map +1 -0
  143. package/dist/hooks/useSheetDrag.d.ts +31 -0
  144. package/dist/hooks/useSheetDrag.d.ts.map +1 -0
  145. package/dist/index.css +1 -1
  146. package/dist/index.d.ts +9 -1
  147. package/dist/index.esm.css +1 -1
  148. package/dist/index.esm.js +8851 -3198
  149. package/dist/index.js +8856 -3195
  150. package/dist/rich-text/index.js +41 -11
  151. package/dist/types/blocks.d.ts +32 -2
  152. package/dist/types/blocks.d.ts.map +1 -1
  153. package/dist/types/index.d.ts +353 -19
  154. package/dist/types/index.d.ts.map +1 -1
  155. package/dist/types/rich-text.d.ts +5 -0
  156. package/dist/types/rich-text.d.ts.map +1 -1
  157. package/dist/utils/position.d.ts +0 -15
  158. package/dist/utils/position.d.ts.map +1 -1
  159. package/doc/README.md +15 -0
  160. package/mcp/catalog.snapshot.json +1398 -464
  161. package/package.json +7 -2
  162. package/dist/esm/hooks/useSwipeToClose.js +0 -66
  163. package/dist/hooks/useSwipeToClose.d.ts +0 -24
  164. package/dist/hooks/useSwipeToClose.d.ts.map +0 -1
@@ -1,251 +1,65 @@
1
1
  'use client';
2
- import { slicedToArray as _slicedToArray, toConsumableArray as _toConsumableArray } from '../../../_virtual/_rollupPluginBabelHelpers.js';
3
- import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
- import { useRef, useState, useLayoutEffect, useEffect } from 'react';
5
- import { createPortal } from 'react-dom';
2
+ import { jsx } from 'react/jsx-runtime';
3
+ import 'react';
6
4
  import { cn } from '../../../utils/cn.js';
7
- import { useLatestRef } from '../../../hooks/useLatestRef.js';
5
+ import ActionMenuSurface from '../menu/ActionMenuSurface.js';
8
6
 
9
7
  var DEFAULT_MENU_BG = "bg-surface border border-line shadow-xl";
10
8
  var DEFAULT_MENU_ITEM_HOVER = "opacity-75 hover:opacity-100 hover:bg-gray-100/80 active:bg-gray-200/80 dark:hover:bg-gray-800 dark:active:bg-gray-700";
9
+ /**
10
+ * Table's row action menu. Rendered by TableRow / MobileCard while a row's
11
+ * kebab is open (the Table owns the open state), on the shared action-menu
12
+ * primitive: a portaled floating menu on desktop, an action sheet on phones.
13
+ *
14
+ * Actions: { name, label, icon?, render?(row), disabled?, tone?: 'destructive', destructive? }.
15
+ */
11
16
  function ActionMenu(_ref) {
12
17
  var _theme$menuBg, _theme$menuItemHover;
13
18
  var _ref$actions = _ref.actions,
14
19
  actions = _ref$actions === void 0 ? [] : _ref$actions,
15
20
  row = _ref.row,
16
21
  onAction = _ref.onAction,
17
- onClose = _ref.onClose,
18
- externalMenuRef = _ref.menuRef,
22
+ _onClose = _ref.onClose,
23
+ menuRef = _ref.menuRef,
19
24
  anchorEl = _ref.anchorEl,
20
- theme = _ref.theme;
21
- var internalRef = useRef(null);
22
- var menuRef = externalMenuRef || internalRef;
23
- var _useState = useState(-1),
24
- _useState2 = _slicedToArray(_useState, 2),
25
- focusedIndex = _useState2[0],
26
- setFocusedIndex = _useState2[1];
25
+ theme = _ref.theme,
26
+ _ref$presentation = _ref.presentation,
27
+ presentation = _ref$presentation === void 0 ? "auto" : _ref$presentation;
27
28
  var menuBg = (_theme$menuBg = theme === null || theme === void 0 ? void 0 : theme.menuBg) !== null && _theme$menuBg !== void 0 ? _theme$menuBg : DEFAULT_MENU_BG;
28
29
  var menuItemHover = (_theme$menuItemHover = theme === null || theme === void 0 ? void 0 : theme.menuItemHover) !== null && _theme$menuItemHover !== void 0 ? _theme$menuItemHover : DEFAULT_MENU_ITEM_HOVER;
29
- var _useState3 = useState({
30
- left: 0,
31
- top: 0,
32
- transformOrigin: "top right",
33
- maxHeight: 300,
34
- width: 190,
35
- opacity: 0
36
- }),
37
- _useState4 = _slicedToArray(_useState3, 2),
38
- style = _useState4[0],
39
- setStyle = _useState4[1];
40
- var menuWidth = 190;
41
- var maxMenuHeight = 320;
42
- var minMenuHeight = 80;
43
- var margin = 8;
44
- var computePosition = function computePosition() {
45
- if (!anchorEl || typeof window === "undefined") return;
46
- var rect = anchorEl.getBoundingClientRect();
47
- var scrollY = window.scrollY || window.pageYOffset || 0;
48
- var scrollX = window.scrollX || window.pageXOffset || 0;
49
- var spaceBelow = window.innerHeight - rect.bottom;
50
- var spaceAbove = rect.top;
51
- // Estimate or measure menu height
52
- var menuEl = menuRef === null || menuRef === void 0 ? void 0 : menuRef.current;
53
- var actionCount = Array.isArray(actions) && actions.length > 0 ? actions.length : 1;
54
- var estimatedHeight = Math.min(maxMenuHeight, Math.max(minMenuHeight, actionCount * 42 + 16));
55
- var actualContentHeight = estimatedHeight;
56
- if (menuEl) {
57
- actualContentHeight = Math.min(maxMenuHeight, menuEl.scrollHeight || menuEl.offsetHeight || estimatedHeight);
58
- }
59
- // Determine placement (top vs bottom) based on available viewport space
60
- var placement = "bottom";
61
- if (spaceBelow < actualContentHeight + margin && spaceAbove > spaceBelow) {
62
- placement = "top";
63
- }
64
- var availableSpace = placement === "top" ? spaceAbove - margin : spaceBelow - margin;
65
- var allowedMaxHeight = Math.min(maxMenuHeight, Math.max(minMenuHeight, availableSpace));
66
- var finalMenuHeight = Math.min(actualContentHeight, allowedMaxHeight);
67
- // Compute top coordinate based on placement
68
- var top;
69
- if (placement === "top") {
70
- top = rect.top + scrollY - finalMenuHeight - margin;
71
- } else {
72
- top = rect.bottom + scrollY + margin;
73
- }
74
- // Clamp top within viewport scroll bounds
75
- var minTop = margin + scrollY;
76
- var maxTop = window.innerHeight + scrollY - finalMenuHeight - margin;
77
- if (top < minTop) top = minTop;
78
- if (top > maxTop) top = maxTop;
79
- // Compute left coordinate (align right edge to anchor right, fallback to left align if overflow)
80
- var left = rect.right + scrollX - menuWidth;
81
- if (left < margin) {
82
- left = rect.left + scrollX;
83
- }
84
- if (left + menuWidth > window.innerWidth - margin) {
85
- left = Math.max(margin, window.innerWidth - menuWidth - margin);
86
- }
87
- var transformOrigin = placement === "bottom" ? "top right" : "bottom right";
88
- setStyle({
89
- left: left,
90
- top: top,
91
- transformOrigin: transformOrigin,
92
- maxHeight: allowedMaxHeight,
93
- width: menuWidth,
94
- opacity: 1
95
- });
96
- };
97
- // computePosition is rebuilt every render; read it through a ref so the
98
- // listeners below are only re-bound when the anchor/actions change.
99
- var computePositionRef = useLatestRef(computePosition);
100
- useLayoutEffect(function () {
101
- // Measuring the anchor and positioning the menu before paint is the
102
- // intended use of a layout effect.
103
- computePositionRef.current();
104
- var rafId = window.requestAnimationFrame(function () {
105
- return computePositionRef.current();
106
- });
107
- var handleResize = function handleResize() {
108
- window.requestAnimationFrame(function () {
109
- return computePositionRef.current();
110
- });
111
- };
112
- var handleScroll = function handleScroll(e) {
113
- // If scroll event originates inside the menu container itself,
114
- // do NOT close the menu! Let the user scroll through options.
115
- if (menuRef !== null && menuRef !== void 0 && menuRef.current && e.target && e.target instanceof Node && (menuRef.current === e.target || menuRef.current.contains(e.target))) {
116
- return;
30
+ var items = (Array.isArray(actions) ? actions : []).map(function (action, idx) {
31
+ var _action$name;
32
+ return {
33
+ key: (_action$name = action.name) !== null && _action$name !== void 0 ? _action$name : "action-".concat(idx),
34
+ label: action.label,
35
+ icon: action.icon ? jsx("span", {
36
+ className: "inline-flex text-muted",
37
+ children: action.icon
38
+ }) : null,
39
+ content: action.render ? action.render(row) : undefined,
40
+ disabled: Boolean(action.disabled),
41
+ tone: action.tone,
42
+ destructive: action.destructive,
43
+ className: cn("gap-2.5 rounded-lg font-medium", menuItemHover),
44
+ onSelect: function onSelect() {
45
+ return onAction === null || onAction === void 0 ? void 0 : onAction(action, row);
117
46
  }
118
- onClose === null || onClose === void 0 || onClose();
119
- };
120
- window.addEventListener("resize", handleResize);
121
- window.addEventListener("scroll", handleScroll, true);
122
- return function () {
123
- window.cancelAnimationFrame(rafId);
124
- window.removeEventListener("resize", handleResize);
125
- window.removeEventListener("scroll", handleScroll, true);
126
47
  };
127
- }, [anchorEl, actions, menuRef, onClose, computePositionRef]);
128
- // Focus management & Keyboard navigation
129
- useEffect(function () {
130
- var handleKeyDown = function handleKeyDown(e) {
131
- if (!Array.isArray(actions) || actions.length === 0) return;
132
- if (e.key === "Escape") {
133
- var _anchorEl$focus;
134
- e.preventDefault();
135
- onClose === null || onClose === void 0 || onClose();
136
- anchorEl === null || anchorEl === void 0 || (_anchorEl$focus = anchorEl.focus) === null || _anchorEl$focus === void 0 || _anchorEl$focus.call(anchorEl);
137
- return;
138
- }
139
- if (e.key === "ArrowDown") {
140
- e.preventDefault();
141
- setFocusedIndex(function (prev) {
142
- var next = prev + 1;
143
- while (next < actions.length && (_actions$next = actions[next]) !== null && _actions$next !== void 0 && _actions$next.disabled) {
144
- var _actions$next;
145
- next++;
146
- }
147
- return next < actions.length ? next : prev;
148
- });
149
- return;
150
- }
151
- if (e.key === "ArrowUp") {
152
- e.preventDefault();
153
- setFocusedIndex(function (prev) {
154
- var next = prev - 1;
155
- while (next >= 0 && (_actions$next2 = actions[next]) !== null && _actions$next2 !== void 0 && _actions$next2.disabled) {
156
- var _actions$next2;
157
- next--;
158
- }
159
- return next >= 0 ? next : prev;
160
- });
161
- return;
162
- }
163
- if (e.key === "Home") {
164
- e.preventDefault();
165
- var first = actions.findIndex(function (a) {
166
- return !a.disabled;
167
- });
168
- if (first !== -1) setFocusedIndex(first);
169
- return;
170
- }
171
- if (e.key === "End") {
172
- e.preventDefault();
173
- var last = _toConsumableArray(actions).reverse().findIndex(function (a) {
174
- return !a.disabled;
175
- });
176
- if (last !== -1) setFocusedIndex(actions.length - 1 - last);
177
- return;
178
- }
179
- };
180
- window.addEventListener("keydown", handleKeyDown);
181
- return function () {
182
- return window.removeEventListener("keydown", handleKeyDown);
183
- };
184
- }, [actions, onClose, anchorEl]);
185
- // Automatically focus focused item
186
- useEffect(function () {
187
- if (focusedIndex >= 0 && menuRef !== null && menuRef !== void 0 && menuRef.current) {
188
- var buttons = menuRef.current.querySelectorAll("button:not([disabled])");
189
- if (buttons[focusedIndex]) {
190
- buttons[focusedIndex].focus();
191
- }
192
- }
193
- }, [focusedIndex, menuRef]);
194
- return /*#__PURE__*/createPortal(jsx("div", {
195
- ref: menuRef,
196
- role: "menu",
197
- "aria-orientation": "vertical",
198
- style: {
199
- position: "absolute",
200
- top: style.top,
201
- left: style.left,
202
- width: style.width,
203
- maxHeight: style.maxHeight,
204
- transformOrigin: style.transformOrigin,
205
- opacity: style.opacity
206
- },
207
- className: cn("absolute z-50 rounded-xl shadow-xl ring-1 ring-black/10 overflow-y-auto transition-opacity duration-150 ease-out focus:outline-none", menuBg),
208
- onClick: function onClick(e) {
209
- return e.stopPropagation();
48
+ });
49
+ return jsx(ActionMenuSurface, {
50
+ open: true,
51
+ onClose: function onClose() {
52
+ return _onClose === null || _onClose === void 0 ? void 0 : _onClose();
210
53
  },
211
- children: jsx("div", {
212
- className: "p-1.5 space-y-0.5",
213
- children: !Array.isArray(actions) || actions.length === 0 ? jsx("div", {
214
- className: "px-3 py-2 text-sm text-muted italic",
215
- children: "No actions"
216
- }) : actions.map(function (action, idx) {
217
- var _action$name;
218
- var isDisabled = !!action.disabled;
219
- var isFocused = focusedIndex === idx;
220
- return jsx("div", {
221
- children: jsx("button", {
222
- type: "button",
223
- disabled: isDisabled,
224
- "aria-disabled": isDisabled || undefined,
225
- className: cn("w-full text-left px-3 py-2 text-sm flex items-center gap-2.5 rounded-lg transition-colors font-medium", isDisabled ? "opacity-50 cursor-not-allowed text-muted" : ["text-foreground", menuItemHover, isFocused ? "bg-gray-100 text-gray-900 ring-1 ring-gray-300 dark:bg-gray-800 dark:text-foreground dark:ring-gray-600" : ""]),
226
- onMouseEnter: function onMouseEnter() {
227
- return !isDisabled && setFocusedIndex(idx);
228
- },
229
- onClick: function onClick(e) {
230
- e.stopPropagation();
231
- if (isDisabled) return;
232
- onAction === null || onAction === void 0 || onAction(action, row);
233
- onClose === null || onClose === void 0 || onClose();
234
- },
235
- children: action.render ? action.render(row) : jsxs(Fragment, {
236
- children: [action.icon && jsx("span", {
237
- className: "inline-flex text-muted flex-shrink-0",
238
- children: action.icon
239
- }), jsx("span", {
240
- className: "truncate",
241
- children: action.label
242
- })]
243
- })
244
- })
245
- }, (_action$name = action.name) !== null && _action$name !== void 0 ? _action$name : "action-".concat(idx));
246
- })
247
- })
248
- }), document.body);
54
+ anchorEl: anchorEl,
55
+ items: items,
56
+ presentation: presentation,
57
+ placement: "bottom-end",
58
+ closeOnScroll: true,
59
+ menuRef: menuRef,
60
+ label: "Row actions",
61
+ menuClassName: cn("w-[190px] rounded-xl p-1.5 shadow-xl ring-1 ring-black/10", menuBg)
62
+ });
249
63
  }
250
64
 
251
65
  export { ActionMenu as default };
@@ -21,7 +21,7 @@ function ColumnMenu(_ref) {
21
21
  role: "group",
22
22
  "aria-label": "Show / hide columns",
23
23
  ref: menuRef,
24
- className: "absolute right-4 mt-2 w-56 z-50 rounded-lg shadow-lg ring-1 ring-black ring-opacity-5 p-3 lg:block sm:block hidden",
24
+ className: "absolute right-4 mt-2 w-56 z-dropdown rounded-lg shadow-lg ring-1 ring-black ring-opacity-5 p-3 lg:block sm:block hidden",
25
25
  onClick: function onClick(e) {
26
26
  return e.stopPropagation();
27
27
  },
@@ -4,8 +4,22 @@ import React from 'react';
4
4
  import { cn } from '../../../utils/cn.js';
5
5
  import { MoreVertical } from 'lucide-react';
6
6
  import ActionMenu from './ActionMenu.js';
7
+ import SwipeableListItem from '../SwipeableListItem.js';
7
8
  import { getRowKey, resolveActions, rowHasDetails, getActionLabelText, getStripeProps } from './helpers.js';
8
9
 
10
+ // Card controls are phone controls: 44×44 px hit areas (WCAG 2.5.5).
11
+ var CARD_ICON_BUTTON = "inline-flex min-h-11 min-w-11 items-center justify-center rounded-lg outline-none focus-visible:ring-2 focus-visible:ring-primary-500";
12
+ /** Resolves `swipeActions(row, index)` to `{ leading, trailing }` arrays. */
13
+ function resolveSwipeActions(swipeActions, row, index) {
14
+ if (typeof swipeActions !== "function") return null;
15
+ var result = swipeActions(row, index) || {};
16
+ var leading = Array.isArray(result.leading) ? result.leading : [];
17
+ var trailing = Array.isArray(result.trailing) ? result.trailing : [];
18
+ return leading.length || trailing.length ? {
19
+ leading: leading,
20
+ trailing: trailing
21
+ } : null;
22
+ }
9
23
  /**
10
24
  * Mobile card view for a single table row
11
25
  */
@@ -32,12 +46,17 @@ function MobileCard(_ref) {
32
46
  activeAnchorEl = _ref.activeAnchorEl,
33
47
  registerActionButton = _ref.registerActionButton,
34
48
  actionMenuRef = _ref.actionMenuRef,
49
+ _ref$actionMenuPresen = _ref.actionMenuPresentation,
50
+ actionMenuPresentation = _ref$actionMenuPresen === void 0 ? "auto" : _ref$actionMenuPresen,
35
51
  toggleExpandRow = _ref.toggleExpandRow,
36
52
  handleSelectRow = _ref.handleSelectRow,
37
53
  toggleActions = _ref.toggleActions,
38
54
  handleOnAction = _ref.handleOnAction,
39
55
  closeActionMenu = _ref.closeActionMenu,
40
- renderCell = _ref.renderCell;
56
+ renderCell = _ref.renderCell,
57
+ swipeActions = _ref.swipeActions,
58
+ openSwipe = _ref.openSwipe,
59
+ setOpenSwipe = _ref.setOpenSwipe;
41
60
  var globalIndex = startIndex + rowIndexInPage;
42
61
  var key = getRowKey(row, globalIndex);
43
62
  var actionCellKey = "actions-".concat(key);
@@ -62,9 +81,12 @@ function MobileCard(_ref) {
62
81
  var showHeader = showDetailsToggle || selectable || showSerial || hasActions;
63
82
  var stripeBg = stripedRows && !isSelected && Array.isArray(stripedColors) && stripedColors.length > 0 ? stripedColors[globalIndex % stripedColors.length] : undefined;
64
83
  var stripeProps = getStripeProps(stripeBg);
65
- return jsxs("div", {
84
+ var swipe = React.useMemo(function () {
85
+ return resolveSwipeActions(swipeActions, row, globalIndex);
86
+ }, [swipeActions, row, globalIndex]);
87
+ var card = jsxs("div", {
66
88
  style: stripeProps.style,
67
- className: cn(stripeProps.className, "border border-line rounded-lg mb-3 shadow-sm overflow-hidden transition-all duration-200", {
89
+ className: cn(stripeProps.className, "border border-line rounded-lg shadow-sm overflow-hidden transition-all duration-200 motion-reduce:transition-none", !swipe && "mb-3", {
68
90
  "cursor-pointer": !!onRowClick,
69
91
  "bg-primary-50 border-primary-200": isSelected,
70
92
  "hover:shadow-md": !isSelected
@@ -84,19 +106,28 @@ function MobileCard(_ref) {
84
106
  },
85
107
  "aria-expanded": isExpanded,
86
108
  "aria-label": isExpanded ? "Collapse row details" : "Expand row details",
87
- className: "p-1 rounded hover:bg-gray-200 dark:hover:bg-gray-700",
109
+ className: cn(CARD_ICON_BUTTON, "hover:bg-line/60"),
88
110
  children: isExpanded ? "▾" : "▸"
89
- }), selectable && jsx("input", {
90
- type: "checkbox",
91
- className: "rounded border-gray-300 h-4 w-4",
92
- checked: isSelected,
93
- onChange: function onChange(e) {
94
- e.stopPropagation();
95
- handleSelectRow(row, rowIndexInPage);
96
- },
111
+ }), selectable &&
112
+ // The label is the 44px hit area around the 20px box.
113
+ jsx("label", {
114
+ className: "inline-flex min-h-11 min-w-11 cursor-pointer items-center justify-center rounded-lg",
97
115
  onClick: function onClick(e) {
98
116
  return e.stopPropagation();
99
- }
117
+ },
118
+ children: jsx("input", {
119
+ type: "checkbox",
120
+ "aria-label": "Select row ".concat(globalIndex + 1),
121
+ className: "h-5 w-5 rounded border-line focus-visible:ring-2 focus-visible:ring-primary-500",
122
+ checked: isSelected,
123
+ onChange: function onChange(e) {
124
+ e.stopPropagation();
125
+ handleSelectRow(row, rowIndexInPage);
126
+ },
127
+ onClick: function onClick(e) {
128
+ return e.stopPropagation();
129
+ }
130
+ })
100
131
  }), showSerial && jsxs("span", {
101
132
  className: "text-sm font-medium",
102
133
  children: ["#", globalIndex + 1]
@@ -118,7 +149,7 @@ function MobileCard(_ref) {
118
149
  if (action.disabled) return;
119
150
  handleOnAction(action, row);
120
151
  },
121
- className: cn("p-1.5 rounded", action.disabled ? "opacity-50 cursor-not-allowed" : "hover:bg-gray-200 dark:hover:bg-gray-700"),
152
+ className: cn(CARD_ICON_BUTTON, action.disabled ? "opacity-50 cursor-not-allowed" : "hover:bg-line/60"),
122
153
  children: action.render ? action.render(row) : (_action$icon = action.icon) !== null && _action$icon !== void 0 ? _action$icon : jsx("span", {
123
154
  className: "text-xs",
124
155
  children: actionLabel
@@ -134,8 +165,9 @@ function MobileCard(_ref) {
134
165
  onClick: function onClick(e) {
135
166
  return toggleActions(e, actionCellKey, row);
136
167
  },
168
+ "aria-haspopup": "menu",
137
169
  "aria-expanded": activeActionRowKey === actionCellKey,
138
- className: "p-1.5 rounded hover:bg-gray-200 dark:hover:bg-gray-700",
170
+ className: cn(CARD_ICON_BUTTON, "hover:bg-line/60"),
139
171
  title: "Actions",
140
172
  "aria-label": "Actions",
141
173
  children: jsx(MoreVertical, {
@@ -151,6 +183,7 @@ function MobileCard(_ref) {
151
183
  menuRef: actionMenuRef,
152
184
  anchorEl: activeAnchorEl,
153
185
  onClose: closeActionMenu,
186
+ presentation: actionMenuPresentation,
154
187
  theme: theme
155
188
  })]
156
189
  })]
@@ -184,6 +217,23 @@ function MobileCard(_ref) {
184
217
  })
185
218
  })]
186
219
  }, key);
220
+ if (!swipe) return card;
221
+ return jsx(SwipeableListItem, {
222
+ className: "mb-3 rounded-lg",
223
+ contentClassName: "rounded-lg",
224
+ leadingActions: swipe.leading,
225
+ trailingActions: swipe.trailing,
226
+ open: (openSwipe === null || openSwipe === void 0 ? void 0 : openSwipe.key) === key ? openSwipe.side : null,
227
+ onOpenChange: function onOpenChange(side) {
228
+ return setOpenSwipe === null || setOpenSwipe === void 0 ? void 0 : setOpenSwipe(side ? {
229
+ key: key,
230
+ side: side
231
+ } : null);
232
+ },
233
+ moreActionsLabel: "Swipe actions",
234
+ "data-row-key": key,
235
+ children: card
236
+ });
187
237
  }
188
238
 
189
- export { MobileCard as default };
239
+ export { MobileCard as default, resolveSwipeActions };
@@ -1,23 +1,194 @@
1
1
  'use client';
2
- import { jsxs, jsx } from 'react/jsx-runtime';
2
+ import { slicedToArray as _slicedToArray, objectSpread2 as _objectSpread2, defineProperty as _defineProperty } from '../../../_virtual/_rollupPluginBabelHelpers.js';
3
+ import { jsx, jsxs } from 'react/jsx-runtime';
3
4
  import React from 'react';
4
- import { Search } from 'lucide-react';
5
+ import { Search, SlidersHorizontal } from 'lucide-react';
6
+ import { cn } from '../../../utils/cn.js';
7
+ import Sheet from '../../feedback/Sheet.js';
5
8
 
9
+ var FIELD_CLASSES = "w-full rounded-md border border-line bg-surface px-3 py-2 text-base sm:text-sm text-foreground outline-none focus-visible:ring-2 focus-visible:ring-primary-500";
10
+ var FOOTER_BUTTON = "inline-flex min-h-11 flex-1 items-center justify-center rounded-lg px-4 text-sm font-medium outline-none focus-visible:ring-2 focus-visible:ring-primary-500 transition-colors duration-fast motion-reduce:transition-none";
11
+ /** Number of non-empty filters (the global search counts as one). */
12
+ function countActiveFilters(filters) {
13
+ return Object.values(filters || {}).filter(function (v) {
14
+ return v != null && String(v).trim() !== "";
15
+ }).length;
16
+ }
6
17
  /**
7
- * Mobile-specific filters panel
18
+ * Phone presentation: a "Filters" button opening a bottom Sheet. Edits are a
19
+ * draft until Apply; Clear empties the draft.
8
20
  */
9
- function MobileFilters(_ref) {
10
- var showMobileFilters = _ref.showMobileFilters,
11
- setShowMobileFilters = _ref.setShowMobileFilters,
12
- globalSearch = _ref.globalSearch,
21
+ function FilterSheet(_ref) {
22
+ var _draft$global;
23
+ var globalSearch = _ref.globalSearch,
13
24
  searchInput = _ref.searchInput,
14
- setSearchInput = _ref.setSearchInput,
15
- onGlobalKeyDown = _ref.onGlobalKeyDown,
16
25
  filterable = _ref.filterable,
17
26
  visibleColumns = _ref.visibleColumns,
18
27
  filters = _ref.filters,
28
+ applyFilters = _ref.applyFilters,
19
29
  handleFilter = _ref.handleFilter;
20
30
  var idPrefix = React.useId();
31
+ var _React$useState = React.useState(false),
32
+ _React$useState2 = _slicedToArray(_React$useState, 2),
33
+ open = _React$useState2[0],
34
+ setOpen = _React$useState2[1];
35
+ var _React$useState3 = React.useState({}),
36
+ _React$useState4 = _slicedToArray(_React$useState3, 2),
37
+ draft = _React$useState4[0],
38
+ setDraft = _React$useState4[1];
39
+ var active = countActiveFilters(filters);
40
+ var openSheet = function openSheet() {
41
+ var _ref2;
42
+ var next = {};
43
+ if (filterable) visibleColumns.forEach(function (c) {
44
+ return next[c.key] = filters[c.key] || "";
45
+ });
46
+ if (globalSearch) next.global = (_ref2 = searchInput !== null && searchInput !== void 0 ? searchInput : filters.global) !== null && _ref2 !== void 0 ? _ref2 : "";
47
+ setDraft(next);
48
+ setOpen(true);
49
+ };
50
+ var apply = function apply(e) {
51
+ var _e$preventDefault;
52
+ e === null || e === void 0 || (_e$preventDefault = e.preventDefault) === null || _e$preventDefault === void 0 || _e$preventDefault.call(e);
53
+ if (applyFilters) applyFilters(draft);else Object.entries(draft).forEach(function (_ref3) {
54
+ var _ref4 = _slicedToArray(_ref3, 2),
55
+ k = _ref4[0],
56
+ v = _ref4[1];
57
+ return handleFilter(k, v);
58
+ });
59
+ setOpen(false);
60
+ };
61
+ var clear = function clear() {
62
+ return setDraft(function (prev) {
63
+ return Object.fromEntries(Object.keys(prev).map(function (k) {
64
+ return [k, ""];
65
+ }));
66
+ });
67
+ };
68
+ var setField = function setField(key, value) {
69
+ return setDraft(function (prev) {
70
+ return _objectSpread2(_objectSpread2({}, prev), {}, _defineProperty({}, key, value));
71
+ });
72
+ };
73
+ var formId = "".concat(idPrefix, "-filters");
74
+ return jsxs("div", {
75
+ className: "mb-4 flex items-center justify-between gap-3 p-3",
76
+ children: [jsxs("button", {
77
+ type: "button",
78
+ onClick: openSheet,
79
+ "aria-haspopup": "dialog",
80
+ "aria-expanded": open,
81
+ className: cn(FOOTER_BUTTON, "flex-none gap-2 border border-line bg-surface text-foreground hover:bg-line/50"),
82
+ children: [jsx(SlidersHorizontal, {
83
+ className: "h-4 w-4",
84
+ "aria-hidden": "true"
85
+ }), jsx("span", {
86
+ children: "Filters"
87
+ }), active > 0 && jsxs("span", {
88
+ className: "rounded-full bg-primary-600 px-2 py-0.5 text-xs font-semibold text-primary-50",
89
+ children: [jsx("span", {
90
+ className: "sr-only",
91
+ children: ", active: "
92
+ }), active]
93
+ })]
94
+ }), jsxs(Sheet, {
95
+ open: open,
96
+ onClose: function onClose() {
97
+ return setOpen(false);
98
+ },
99
+ title: "Filters",
100
+ placement: "bottom",
101
+ children: [jsx(Sheet.Body, {
102
+ children: jsxs("form", {
103
+ id: formId,
104
+ className: "space-y-4",
105
+ onSubmit: apply,
106
+ children: [globalSearch && jsxs("div", {
107
+ className: "relative",
108
+ children: [jsx("label", {
109
+ htmlFor: "".concat(idPrefix, "-global"),
110
+ className: "mb-1 block text-xs font-medium text-muted",
111
+ children: "Search"
112
+ }), jsx("input", {
113
+ id: "".concat(idPrefix, "-global"),
114
+ type: "search",
115
+ placeholder: "Search...",
116
+ className: cn(FIELD_CLASSES, "pr-10"),
117
+ value: (_draft$global = draft.global) !== null && _draft$global !== void 0 ? _draft$global : "",
118
+ onChange: function onChange(e) {
119
+ return setField("global", e.target.value);
120
+ }
121
+ }), jsx(Search, {
122
+ className: "pointer-events-none absolute bottom-3 right-3 h-4 w-4 text-muted",
123
+ "aria-hidden": "true"
124
+ })]
125
+ }), filterable && visibleColumns.map(function (column) {
126
+ var _draft$column$key;
127
+ return jsxs("div", {
128
+ children: [jsx("label", {
129
+ htmlFor: "".concat(idPrefix, "-filter-").concat(column.key),
130
+ className: "mb-1 block text-xs font-medium text-muted",
131
+ children: column.label
132
+ }), jsx("input", {
133
+ id: "".concat(idPrefix, "-filter-").concat(column.key),
134
+ type: "text",
135
+ placeholder: "Filter ".concat(column.label),
136
+ className: FIELD_CLASSES,
137
+ value: (_draft$column$key = draft[column.key]) !== null && _draft$column$key !== void 0 ? _draft$column$key : "",
138
+ onChange: function onChange(e) {
139
+ return setField(column.key, e.target.value);
140
+ }
141
+ })]
142
+ }, column.key);
143
+ })]
144
+ })
145
+ }), jsxs(Sheet.Footer, {
146
+ children: [jsx("button", {
147
+ type: "button",
148
+ onClick: clear,
149
+ className: cn(FOOTER_BUTTON, "border border-line bg-surface text-foreground hover:bg-line/50"),
150
+ children: "Clear"
151
+ }), jsx("button", {
152
+ type: "submit",
153
+ form: formId,
154
+ className: cn(FOOTER_BUTTON, "bg-primary-600 text-primary-50 hover:bg-primary-700"),
155
+ children: "Apply"
156
+ })]
157
+ })]
158
+ })]
159
+ });
160
+ }
161
+ /**
162
+ * Mobile filters. `presentation="inline"` (default) is the collapsible panel;
163
+ * `presentation="sheet"` is a Filters button opening a bottom Sheet with an
164
+ * Apply / Clear footer.
165
+ */
166
+ function MobileFilters(_ref5) {
167
+ var showMobileFilters = _ref5.showMobileFilters,
168
+ setShowMobileFilters = _ref5.setShowMobileFilters,
169
+ globalSearch = _ref5.globalSearch,
170
+ searchInput = _ref5.searchInput,
171
+ setSearchInput = _ref5.setSearchInput,
172
+ onGlobalKeyDown = _ref5.onGlobalKeyDown,
173
+ filterable = _ref5.filterable,
174
+ visibleColumns = _ref5.visibleColumns,
175
+ filters = _ref5.filters,
176
+ handleFilter = _ref5.handleFilter,
177
+ applyFilters = _ref5.applyFilters,
178
+ _ref5$presentation = _ref5.presentation,
179
+ presentation = _ref5$presentation === void 0 ? "inline" : _ref5$presentation;
180
+ var idPrefix = React.useId();
181
+ if (presentation === "sheet") {
182
+ return jsx(FilterSheet, {
183
+ globalSearch: globalSearch,
184
+ searchInput: searchInput,
185
+ filterable: filterable,
186
+ visibleColumns: visibleColumns,
187
+ filters: filters,
188
+ applyFilters: applyFilters,
189
+ handleFilter: handleFilter
190
+ });
191
+ }
21
192
  return jsxs("div", {
22
193
  className: "mb-4 p-3 bg-background rounded-lg",
23
194
  children: [jsxs("div", {
@@ -76,4 +247,4 @@ function MobileFilters(_ref) {
76
247
  });
77
248
  }
78
249
 
79
- export { MobileFilters as default };
250
+ export { countActiveFilters, MobileFilters as default };