@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
@@ -0,0 +1,132 @@
1
+ 'use client';
2
+ import { objectWithoutProperties as _objectWithoutProperties, objectSpread2 as _objectSpread2 } from '../../_virtual/_rollupPluginBabelHelpers.js';
3
+ import { jsx } from 'react/jsx-runtime';
4
+ import React, { useRef, useCallback, useSyncExternalStore, useEffect } from 'react';
5
+ import { cn } from '../../utils/cn.js';
6
+ import { setRefs } from '../../utils/position.js';
7
+
8
+ var _excluded = ["children", "position", "align", "fullWidth", "stackOnMobile", "keyboardAware", "className", "style"],
9
+ _excluded2 = ["className"];
10
+ // Sticky bar at the bottom of the screen for the primary action on forms and
11
+ // checkout pages (DTUI-57). Pads the home indicator (pb-safe), sits on the
12
+ // z-sticky layer and rides above the on-screen keyboard via visualViewport.
13
+ /** CSS variable the bar publishes with its height, read by BottomActionBar.Spacer. */
14
+ var BAR_HEIGHT_VAR = '--dt-bottom-action-bar-height';
15
+ // Part of the layout viewport the on-screen keyboard covers (0 without visualViewport).
16
+ function readKeyboardOffset() {
17
+ if (typeof window === 'undefined') return 0;
18
+ var vv = window.visualViewport;
19
+ if (!vv) return 0;
20
+ var inner = window.innerHeight || 0;
21
+ return Math.max(0, Math.round(inner - (vv.height || inner) - (vv.offsetTop || 0)));
22
+ }
23
+ var getServerOffset = function getServerOffset() {
24
+ return 0;
25
+ };
26
+ /** Keyboard offset in px while `active`; 0 on the server and without visualViewport. */
27
+ function useKeyboardOffset(active) {
28
+ var subscribe = useCallback(function (onChange) {
29
+ var _vv$addEventListener, _vv$addEventListener2;
30
+ if (!active || typeof window === 'undefined') return function () {};
31
+ var vv = window.visualViewport;
32
+ vv === null || vv === void 0 || (_vv$addEventListener = vv.addEventListener) === null || _vv$addEventListener === void 0 || _vv$addEventListener.call(vv, 'resize', onChange);
33
+ vv === null || vv === void 0 || (_vv$addEventListener2 = vv.addEventListener) === null || _vv$addEventListener2 === void 0 || _vv$addEventListener2.call(vv, 'scroll', onChange);
34
+ window.addEventListener('resize', onChange);
35
+ return function () {
36
+ var _vv$removeEventListen, _vv$removeEventListen2;
37
+ vv === null || vv === void 0 || (_vv$removeEventListen = vv.removeEventListener) === null || _vv$removeEventListen === void 0 || _vv$removeEventListen.call(vv, 'resize', onChange);
38
+ vv === null || vv === void 0 || (_vv$removeEventListen2 = vv.removeEventListener) === null || _vv$removeEventListen2 === void 0 || _vv$removeEventListen2.call(vv, 'scroll', onChange);
39
+ window.removeEventListener('resize', onChange);
40
+ };
41
+ }, [active]);
42
+ var getSnapshot = function getSnapshot() {
43
+ return active ? readKeyboardOffset() : 0;
44
+ };
45
+ return useSyncExternalStore(subscribe, getSnapshot, getServerOffset);
46
+ }
47
+ /** Publishes the fixed bar's height as a CSS variable on <html> so the Spacer can match it. */
48
+ function usePublishedHeight(nodeRef, enabled) {
49
+ useEffect(function () {
50
+ var node = nodeRef.current;
51
+ if (!enabled || !node || typeof document === 'undefined') return undefined;
52
+ var root = document.documentElement;
53
+ var publish = function publish() {
54
+ var _node$getBoundingClie;
55
+ var h = ((_node$getBoundingClie = node.getBoundingClientRect) === null || _node$getBoundingClie === void 0 ? void 0 : _node$getBoundingClie.call(node).height) || 0;
56
+ if (h > 0) root.style.setProperty(BAR_HEIGHT_VAR, "".concat(Math.round(h), "px"));
57
+ };
58
+ publish();
59
+ var observer;
60
+ if (typeof window.ResizeObserver === 'function') {
61
+ observer = new window.ResizeObserver(publish);
62
+ observer.observe(node);
63
+ }
64
+ return function () {
65
+ var _observer;
66
+ (_observer = observer) === null || _observer === void 0 || _observer.disconnect();
67
+ root.style.removeProperty(BAR_HEIGHT_VAR);
68
+ };
69
+ }, [nodeRef, enabled]);
70
+ }
71
+ var ALIGN = {
72
+ start: 'justify-start',
73
+ center: 'justify-center',
74
+ end: 'justify-end',
75
+ between: 'justify-between'
76
+ };
77
+ var BottomActionBar = /*#__PURE__*/React.forwardRef(function (_ref, ref) {
78
+ var children = _ref.children,
79
+ _ref$position = _ref.position,
80
+ position = _ref$position === void 0 ? 'fixed' : _ref$position,
81
+ _ref$align = _ref.align,
82
+ align = _ref$align === void 0 ? 'end' : _ref$align,
83
+ _ref$fullWidth = _ref.fullWidth,
84
+ fullWidth = _ref$fullWidth === void 0 ? false : _ref$fullWidth,
85
+ _ref$stackOnMobile = _ref.stackOnMobile,
86
+ stackOnMobile = _ref$stackOnMobile === void 0 ? false : _ref$stackOnMobile,
87
+ _ref$keyboardAware = _ref.keyboardAware,
88
+ keyboardAware = _ref$keyboardAware === void 0 ? true : _ref$keyboardAware,
89
+ className = _ref.className,
90
+ style = _ref.style,
91
+ props = _objectWithoutProperties(_ref, _excluded);
92
+ var localRef = useRef(null);
93
+ var setRef = useCallback(function (node) {
94
+ return setRefs(node, localRef, ref);
95
+ }, [ref]);
96
+ var fixed = position !== 'sticky';
97
+ var offset = useKeyboardOffset(keyboardAware);
98
+ usePublishedHeight(localRef, fixed);
99
+ // Above the keyboard the home indicator is covered, so the safe-area pad goes.
100
+ var lifted = offset > 0;
101
+ var barStyle = lifted ? _objectSpread2(_objectSpread2({}, style), {}, {
102
+ bottom: "".concat(offset, "px")
103
+ }) : style;
104
+ return jsx("div", _objectSpread2(_objectSpread2({
105
+ ref: setRef,
106
+ "data-position": fixed ? 'fixed' : 'sticky',
107
+ "data-keyboard": lifted ? 'open' : undefined,
108
+ className: cn(fixed ? 'fixed inset-x-0 bottom-0' : 'sticky bottom-0', 'z-sticky border-t border-line bg-surface text-foreground px-safe', 'shadow-[0_-4px_12px_-4px_rgb(0_0_0/calc(0.12*var(--dt-shadow-strength,1)))]', 'transition-[bottom] duration-fast ease-standard motion-reduce:transition-none', !lifted && 'pb-safe', className),
109
+ style: barStyle
110
+ }, props), {}, {
111
+ children: jsx("div", {
112
+ className: cn('mx-auto flex w-full max-w-7xl items-center gap-3 px-4 py-3 md:px-8', ALIGN[align] || ALIGN.end, fullWidth && 'max-sm:[&>*]:flex-1', stackOnMobile && 'max-sm:flex-col-reverse max-sm:items-stretch max-sm:[&>*]:w-full'),
113
+ children: children
114
+ })
115
+ }));
116
+ });
117
+ BottomActionBar.displayName = 'BottomActionBar';
118
+ // Reserves the fixed bar's height (published by the bar, 4.5rem + safe area
119
+ // before it measures) so the last rows of the page aren't hidden behind it.
120
+ var BottomActionBarSpacer = /*#__PURE__*/React.forwardRef(function (_ref2, ref) {
121
+ var className = _ref2.className,
122
+ props = _objectWithoutProperties(_ref2, _excluded2);
123
+ return jsx("div", _objectSpread2({
124
+ ref: ref,
125
+ "aria-hidden": "true",
126
+ className: cn('h-[var(--dt-bottom-action-bar-height,calc(4.5rem+env(safe-area-inset-bottom,0px)))] shrink-0', className)
127
+ }, props));
128
+ });
129
+ BottomActionBarSpacer.displayName = 'BottomActionBar.Spacer';
130
+ BottomActionBar.Spacer = BottomActionBarSpacer;
131
+
132
+ export { BAR_HEIGHT_VAR, BottomActionBar as default };
@@ -1,13 +1,20 @@
1
1
  'use client';
2
2
  import { objectWithoutProperties as _objectWithoutProperties, objectSpread2 as _objectSpread2 } from '../../_virtual/_rollupPluginBabelHelpers.js';
3
3
  import { jsx, jsxs } from 'react/jsx-runtime';
4
- import React from 'react';
4
+ import React, { useEffect } from 'react';
5
5
  import Dialog from '../feedback/Dialog.js';
6
6
  import Button from '../core/Button.js';
7
+ import { usePresentation } from '../../hooks/usePresentation.js';
8
+ import { useHaptics } from '../../hooks/useHaptics.js';
9
+ import { useLatestRef } from '../../hooks/useLatestRef.js';
7
10
 
8
- var _excluded = ["isOpen", "title", "description", "confirmLabel", "cancelLabel", "destructive", "loading", "onConfirm", "onCancel", "className"];
11
+ var _excluded = ["isOpen", "title", "description", "confirmLabel", "cancelLabel", "destructive", "loading", "onConfirm", "onCancel", "className", "presentation", "haptics"];
9
12
  // Extracted from firepro/components/ui/confirm-dialog.tsx. Replaces native confirm():
10
13
  // the title asks the question, one line says what happens, the button is named with the verb.
14
+ //
15
+ // Actions keep the DOM (and Tab) order [cancel, confirm]. `flex-col-reverse`
16
+ // shows the confirm button on top when they stack: below `sm` in the centred
17
+ // modal, and always in sheet mode, where both buttons are full width.
11
18
  var ConfirmDialog = /*#__PURE__*/React.forwardRef(function (_ref, ref) {
12
19
  var isOpen = _ref.isOpen,
13
20
  title = _ref.title,
@@ -22,7 +29,33 @@ var ConfirmDialog = /*#__PURE__*/React.forwardRef(function (_ref, ref) {
22
29
  onConfirm = _ref.onConfirm,
23
30
  onCancel = _ref.onCancel,
24
31
  className = _ref.className,
32
+ _ref$presentation = _ref.presentation,
33
+ presentation = _ref$presentation === void 0 ? 'auto' : _ref$presentation,
34
+ _ref$haptics = _ref.haptics,
35
+ haptics = _ref$haptics === void 0 ? false : _ref$haptics,
25
36
  props = _objectWithoutProperties(_ref, _excluded);
37
+ // Resolve once here so the footer layout and the Dialog agree on the mode.
38
+ var resolved = usePresentation(presentation);
39
+ var sheet = resolved === 'sheet';
40
+ // Opt-in haptics: a warning buzz when a destructive confirm opens, a success
41
+ // tick on confirm. Only the open transition fires the warning.
42
+ var _useHaptics = useHaptics({
43
+ enabled: haptics
44
+ }),
45
+ haptic = _useHaptics.trigger;
46
+ var openCue = useLatestRef(function () {
47
+ if (destructive) haptic('warning');
48
+ });
49
+ useEffect(function () {
50
+ if (isOpen) openCue.current();
51
+ }, [isOpen, openCue]);
52
+ var handleConfirm = function handleConfirm() {
53
+ haptic('success');
54
+ for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
55
+ args[_key] = arguments[_key];
56
+ }
57
+ return onConfirm === null || onConfirm === void 0 ? void 0 : onConfirm.apply(void 0, args);
58
+ };
26
59
  return jsx(Dialog, _objectSpread2(_objectSpread2({
27
60
  ref: ref,
28
61
  isOpen: isOpen,
@@ -31,17 +64,20 @@ var ConfirmDialog = /*#__PURE__*/React.forwardRef(function (_ref, ref) {
31
64
  size: "sm",
32
65
  dismissible: !loading,
33
66
  className: className,
67
+ presentation: resolved,
34
68
  footer: jsxs("div", {
35
- className: "flex flex-col-reverse gap-3 sm:flex-row sm:justify-end",
69
+ className: sheet ? 'flex flex-col-reverse gap-3' : 'flex flex-col-reverse gap-3 sm:flex-row sm:justify-end',
36
70
  children: [jsx(Button, {
37
71
  variant: "outline",
38
72
  onClick: onCancel,
39
73
  disabled: loading,
74
+ fullWidth: sheet,
40
75
  children: cancelLabel
41
76
  }), jsx(Button, {
42
77
  variant: destructive ? 'destructive' : 'primary',
43
- onClick: onConfirm,
78
+ onClick: handleConfirm,
44
79
  loading: loading,
80
+ fullWidth: sheet,
45
81
  children: confirmLabel
46
82
  })]
47
83
  })
@@ -4,26 +4,35 @@ import { jsxs, jsx } from 'react/jsx-runtime';
4
4
  import React from 'react';
5
5
  import { ArrowLeft } from 'lucide-react';
6
6
  import { cn } from '../../utils/cn.js';
7
+ import { TOUCH_TARGET } from '../../utils/touchTarget.js';
8
+ import { usePresentation } from '../../hooks/usePresentation.js';
9
+ import BottomActionBar from './BottomActionBar.js';
7
10
 
8
- var _excluded = ["title", "subtitle", "actions", "back", "linkAs", "className"];
11
+ var _excluded = ["title", "subtitle", "actions", "actionsPlacement", "back", "linkAs", "className"];
9
12
  // Extracted from taskpro/client/src/features/shared/PageHeader.jsx (31 imports) and the
10
13
  // CampaignPro UX guide's page anatomy: one h1, a muted subtitle, actions top right.
14
+ // `actionsPlacement="bottom"` pins the actions in a BottomActionBar on phones (DTUI-52).
11
15
  var PageHeader = /*#__PURE__*/React.forwardRef(function (_ref, ref) {
12
16
  var title = _ref.title,
13
17
  subtitle = _ref.subtitle,
14
18
  actions = _ref.actions,
19
+ _ref$actionsPlacement = _ref.actionsPlacement,
20
+ actionsPlacement = _ref$actionsPlacement === void 0 ? 'inline' : _ref$actionsPlacement,
15
21
  back = _ref.back,
16
22
  _ref$linkAs = _ref.linkAs,
17
23
  Link = _ref$linkAs === void 0 ? 'a' : _ref$linkAs,
18
24
  className = _ref.className,
19
25
  props = _objectWithoutProperties(_ref, _excluded);
26
+ // 'sheet' = coarse pointer at phone width; 'popover' on the server and on desktop.
27
+ var isPhone = usePresentation() === 'sheet';
28
+ var pinned = Boolean(actions) && actionsPlacement === 'bottom' && isPhone;
20
29
  return jsxs("div", _objectSpread2(_objectSpread2({
21
30
  ref: ref,
22
31
  className: cn('flex flex-col gap-3', className)
23
32
  }, props), {}, {
24
33
  children: [back && jsxs(Link, {
25
34
  href: back.href,
26
- className: "inline-flex w-fit items-center gap-1.5 rounded-control text-sm text-muted hover:text-foreground focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2",
35
+ className: cn('inline-flex w-fit items-center gap-1.5 rounded-control text-sm text-muted hover:text-foreground', 'focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-2', TOUCH_TARGET),
27
36
  children: [jsx(ArrowLeft, {
28
37
  className: "h-4 w-4",
29
38
  "aria-hidden": "true"
@@ -39,10 +48,14 @@ var PageHeader = /*#__PURE__*/React.forwardRef(function (_ref, ref) {
39
48
  className: "mt-1 max-w-2xl text-sm text-muted",
40
49
  children: subtitle
41
50
  })]
42
- }), actions && jsx("div", {
51
+ }), actions && !pinned && jsx("div", {
43
52
  className: "flex flex-wrap items-center gap-3",
44
53
  children: actions
45
54
  })]
55
+ }), pinned && jsx(BottomActionBar, {
56
+ fullWidth: true,
57
+ "data-page-header-actions": "",
58
+ children: actions
46
59
  })]
47
60
  }));
48
61
  });
@@ -4,8 +4,9 @@ import { jsxs, jsx } from 'react/jsx-runtime';
4
4
  import { forwardRef, useId, useRef, useState, useEffect, useCallback } from 'react';
5
5
  import { Check, Minus } from 'lucide-react';
6
6
  import { cn } from '../../utils/cn.js';
7
+ import { useHaptics } from '../../hooks/useHaptics.js';
7
8
 
8
- var _excluded = ["id", "name", "label", "checked", "defaultChecked", "onChange", "disabled", "required", "className", "size", "labelPosition", "value", "ariaLabel", "variant", "indeterminate", "borderColor", "color", "iconClass", "checkedClass", "wrapperProps"];
9
+ var _excluded = ["id", "name", "label", "checked", "defaultChecked", "onChange", "disabled", "required", "className", "size", "labelPosition", "value", "ariaLabel", "variant", "indeterminate", "color", "iconClass", "checkedClass", "wrapperProps", "haptics"];
9
10
  var SIZE_CLASSES = {
10
11
  sm: {
11
12
  box: "h-4 w-4",
@@ -25,8 +26,8 @@ var SIZE_CLASSES = {
25
26
  };
26
27
  // `checked` paints the box from the native :checked state; `fill` is the same
27
28
  // paint applied from React state for the indeterminate (mixed) state.
28
- // blue / indigo / purple are raw-palette presets kept for compatibility
29
- // (deprecated; removed in 2.0 — use the token names).
29
+ // Token presets only: the raw-palette presets (blue / indigo / purple) were
30
+ // removed in 2.0.0 (DTUI-65).
30
31
  var COLOR_PRESETS = {
31
32
  primary: {
32
33
  checked: "peer-checked:bg-primary-600 peer-checked:border-primary-600 peer-checked:text-white dark:peer-checked:bg-primary-500 dark:peer-checked:border-primary-500",
@@ -67,21 +68,6 @@ var COLOR_PRESETS = {
67
68
  checked: "peer-checked:bg-warning-600 peer-checked:border-warning-600 peer-checked:text-white dark:peer-checked:bg-warning-500 dark:peer-checked:border-warning-500",
68
69
  fill: "bg-warning-600 border-warning-600 text-white dark:bg-warning-500 dark:border-warning-500",
69
70
  ring: "peer-focus-visible:ring-warning-500"
70
- },
71
- blue: {
72
- checked: "peer-checked:bg-blue-600 peer-checked:border-blue-600 peer-checked:text-white dark:peer-checked:bg-blue-500 dark:peer-checked:border-blue-500",
73
- fill: "bg-blue-600 border-blue-600 text-white dark:bg-blue-500 dark:border-blue-500",
74
- ring: "peer-focus-visible:ring-blue-500"
75
- },
76
- indigo: {
77
- checked: "peer-checked:bg-indigo-600 peer-checked:border-indigo-600 peer-checked:text-white dark:peer-checked:bg-indigo-500 dark:peer-checked:border-indigo-500",
78
- fill: "bg-indigo-600 border-indigo-600 text-white dark:bg-indigo-500 dark:border-indigo-500",
79
- ring: "peer-focus-visible:ring-indigo-500"
80
- },
81
- purple: {
82
- checked: "peer-checked:bg-purple-600 peer-checked:border-purple-600 peer-checked:text-white dark:peer-checked:bg-purple-500 dark:peer-checked:border-purple-500",
83
- fill: "bg-purple-600 border-purple-600 text-white dark:bg-purple-500 dark:border-purple-500",
84
- ring: "peer-focus-visible:ring-purple-500"
85
71
  }
86
72
  };
87
73
  // The invisible native input is the hit area: the box size with a mouse,
@@ -110,12 +96,13 @@ var Checkbox = /*#__PURE__*/forwardRef(function (_ref, ref) {
110
96
  _ref.variant;
111
97
  var _ref$indeterminate = _ref.indeterminate,
112
98
  indeterminate = _ref$indeterminate === void 0 ? false : _ref$indeterminate,
113
- borderColor = _ref.borderColor,
114
99
  _ref$color = _ref.color,
115
100
  color = _ref$color === void 0 ? "primary" : _ref$color,
116
101
  iconClass = _ref.iconClass,
117
102
  checkedClass = _ref.checkedClass,
118
103
  wrapperProps = _ref.wrapperProps,
104
+ _ref$haptics = _ref.haptics,
105
+ haptics = _ref$haptics === void 0 ? false : _ref$haptics,
119
106
  props = _objectWithoutProperties(_ref, _excluded);
120
107
  var autoId = useId();
121
108
  var inputId = id || "".concat(autoId, "-checkbox");
@@ -136,10 +123,15 @@ var Checkbox = /*#__PURE__*/forwardRef(function (_ref, ref) {
136
123
  if (!inputRef.current) return;
137
124
  inputRef.current.indeterminate = Boolean(indeterminate);
138
125
  }, [indeterminate]);
126
+ var _useHaptics = useHaptics({
127
+ enabled: haptics
128
+ }),
129
+ haptic = _useHaptics.trigger;
139
130
  var handleChange = useCallback(function (e) {
140
131
  if (!isControlled) setInternalChecked(e.target.checked);
132
+ haptic("selection");
141
133
  if (typeof onChange === "function") onChange(e);
142
- }, [isControlled, onChange]);
134
+ }, [isControlled, onChange, haptic]);
143
135
  var sizeClass = SIZE_CLASSES[size] || SIZE_CLASSES.md;
144
136
  var isPreset = COLOR_PRESETS[color];
145
137
  var isMixed = Boolean(indeterminate);
@@ -162,10 +154,7 @@ var Checkbox = /*#__PURE__*/forwardRef(function (_ref, ref) {
162
154
  className: cn(sizeClass.label, "select-none mr-2 text-foreground"),
163
155
  children: label
164
156
  }), jsxs("div", {
165
- className: cn("relative inline-flex shrink-0 items-center justify-center rounded-sm border transition-shadow duration-fast motion-reduce:transition-none", sizeClass.box, disabled ? "opacity-60 cursor-not-allowed" : "cursor-pointer hover:shadow-sm", !disabled && showBoxBorder && !borderColor && "border-gray-300 dark:border-gray-600"),
166
- style: disabled || !showBoxBorder || !borderColor ? {} : {
167
- borderColor: borderColor
168
- },
157
+ className: cn("relative inline-flex shrink-0 items-center justify-center rounded-sm border transition-shadow duration-fast motion-reduce:transition-none", sizeClass.box, disabled ? "opacity-60 cursor-not-allowed" : "cursor-pointer hover:shadow-sm", !disabled && showBoxBorder && "border-gray-300 dark:border-gray-600"),
169
158
  "aria-disabled": disabled,
170
159
  children: [jsx("input", _objectSpread2({
171
160
  id: inputId,