@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,58 +1,151 @@
1
1
  'use client';
2
- import { objectWithoutProperties as _objectWithoutProperties, objectSpread2 as _objectSpread2 } from '../../_virtual/_rollupPluginBabelHelpers.js';
3
- import { jsx, jsxs } from 'react/jsx-runtime';
4
- import React, { useRef, useId, useEffect } from 'react';
2
+ import { objectWithoutProperties as _objectWithoutProperties, slicedToArray as _slicedToArray, objectSpread2 as _objectSpread2 } from '../../_virtual/_rollupPluginBabelHelpers.js';
3
+ import { jsxs, jsx } from 'react/jsx-runtime';
4
+ import React, { useRef, useId, useState, useCallback, useEffect } from 'react';
5
5
  import { createPortal } from 'react-dom';
6
6
  import { cn } from '../../utils/cn.js';
7
7
  import { X } from 'lucide-react';
8
8
  import { useLatestRef } from '../../hooks/useLatestRef.js';
9
9
  import { useFocusTrap } from '../../hooks/useFocusTrap.js';
10
10
  import { useScrollLock } from '../../hooks/useScrollLock.js';
11
- import { setRefs } from '../../utils/position.js';
11
+ import { usePresence } from '../../hooks/usePresence.js';
12
+ import { usePresentation } from '../../hooks/usePresentation.js';
12
13
  import { TOUCH_TARGET } from '../../utils/touchTarget.js';
14
+ import Sheet from './Sheet.js';
13
15
 
14
- var _excluded = ["isOpen", "onClose", "title", "children", "size", "dismissible", "showCloseButton", "footer", "className", "onOpen"],
16
+ var _excluded = ["isOpen", "onClose", "title", "children", "size", "dismissible", "showCloseButton", "footer", "className", "onOpen", "presentation"],
15
17
  _excluded2 = ["children", "className"],
16
18
  _excluded3 = ["children", "className"],
17
19
  _excluded4 = ["children", "className"];
18
- var Dialog = /*#__PURE__*/React.forwardRef(function (_ref, _ref2) {
19
- var isOpen = _ref.isOpen,
20
- onClose = _ref.onClose,
21
- title = _ref.title,
22
- children = _ref.children,
23
- _ref$size = _ref.size,
24
- size = _ref$size === void 0 ? 'md' : _ref$size,
25
- _ref$dismissible = _ref.dismissible,
26
- dismissible = _ref$dismissible === void 0 ? true : _ref$dismissible,
27
- _ref$showCloseButton = _ref.showCloseButton,
28
- showCloseButton = _ref$showCloseButton === void 0 ? true : _ref$showCloseButton,
29
- footer = _ref.footer,
30
- className = _ref.className,
31
- onOpen = _ref.onOpen,
32
- props = _objectWithoutProperties(_ref, _excluded);
20
+ // Exit length of the centred modal. `animate-scale-out` / `animate-fade-out`
21
+ // run for duration-fast (120ms) with `forwards`, so the panel stays invisible
22
+ // for the few ms before it unmounts.
23
+ var EXIT_MS = 150;
24
+ // Backdrop gutter: 1rem plus the device safe area on every side, so the panel
25
+ // never sits under a notch, the home indicator or a rounded corner.
26
+ var SAFE_GUTTER = 'pt-[calc(1rem+env(safe-area-inset-top,0px))] pb-[calc(1rem+env(safe-area-inset-bottom,0px))] ' + 'pl-[calc(1rem+env(safe-area-inset-left,0px))] pr-[calc(1rem+env(safe-area-inset-right,0px))]';
27
+ // Tallest the centred panel may grow: the viewport minus the gutter above.
28
+ // Taller content scrolls inside the body; header and footer stay put.
29
+ var PANEL_MAX_HEIGHT = 'max-h-[calc(100dvh-2rem-env(safe-area-inset-top,0px)-env(safe-area-inset-bottom,0px))]';
30
+ // Sheet-mode footer: actions stack full-width. `flex-col-reverse` puts the last
31
+ // DOM child (by convention the primary action) on top, iOS-style, while the
32
+ // DOM and Tab order stay [secondary, primary] as on desktop.
33
+ var SHEET_FOOTER = 'flex-col-reverse items-stretch [&>*]:w-full';
34
+ // Assign one node to several refs (callback or object). Lets the dialog keep
35
+ // its internal `dialogRef` for the focus trap while still exposing the panel
36
+ // node to a consumer-supplied `ref` (issue #56).
37
+ var setRefs = function setRefs(node) {
38
+ for (var _len = arguments.length, refs = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
39
+ refs[_key - 1] = arguments[_key];
40
+ }
41
+ refs.forEach(function (ref) {
42
+ if (!ref) return;
43
+ if (typeof ref === 'function') ref(node);else ref.current = node;
44
+ });
45
+ };
46
+ var CloseButton = function CloseButton(_ref) {
47
+ var onClick = _ref.onClick;
48
+ return jsx("button", {
49
+ type: "button",
50
+ className: cn('inline-flex items-center justify-center rounded-md p-1 text-muted hover:bg-gray-100 hover:text-foreground dark:hover:bg-gray-800', 'focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500', TOUCH_TARGET),
51
+ onClick: onClick,
52
+ "aria-label": "Close dialog",
53
+ children: jsx(X, {
54
+ className: "h-5 w-5",
55
+ "aria-hidden": "true"
56
+ })
57
+ });
58
+ };
59
+ var Dialog = /*#__PURE__*/React.forwardRef(function (_ref2, ref) {
60
+ var isOpen = _ref2.isOpen,
61
+ onClose = _ref2.onClose,
62
+ title = _ref2.title,
63
+ children = _ref2.children,
64
+ _ref2$size = _ref2.size,
65
+ size = _ref2$size === void 0 ? 'md' : _ref2$size,
66
+ _ref2$dismissible = _ref2.dismissible,
67
+ dismissible = _ref2$dismissible === void 0 ? true : _ref2$dismissible,
68
+ _ref2$showCloseButton = _ref2.showCloseButton,
69
+ showCloseButton = _ref2$showCloseButton === void 0 ? true : _ref2$showCloseButton,
70
+ footer = _ref2.footer,
71
+ className = _ref2.className,
72
+ onOpen = _ref2.onOpen,
73
+ _ref2$presentation = _ref2.presentation,
74
+ presentation = _ref2$presentation === void 0 ? 'auto' : _ref2$presentation,
75
+ props = _objectWithoutProperties(_ref2, _excluded);
33
76
  var dialogRef = useRef(null);
34
77
  // Unique per instance so two open dialogs never share an aria-labelledby target.
35
78
  var titleId = "dialog-title-".concat(useId());
36
79
  // Latest onOpen without re-running the open effect when the prop identity changes.
37
80
  var onOpenRef = useLatestRef(onOpen);
38
- // Ref-counted body lock: closing a Dialog stacked over a Drawer (or another
39
- // overlay) leaves the page locked until the last overlay closes.
40
- useScrollLock(isOpen);
41
- // Shared focus management: initial focus, Tab trap, Escape (top layer only),
42
- // and focus restore to the opener on close.
43
- useFocusTrap(dialogRef, {
44
- active: isOpen,
45
- onEscape: function onEscape() {
46
- if (dismissible) onClose === null || onClose === void 0 || onClose();
47
- }
81
+ var onCloseRef = useLatestRef(onClose);
82
+ // The mounted panel node (either mode). onOpen waits for it, because the
83
+ // sheet's Portal renders the panel one commit after `isOpen` flips.
84
+ var _useState = useState(null),
85
+ _useState2 = _slicedToArray(_useState, 2),
86
+ panelEl = _useState2[0],
87
+ setPanelEl = _useState2[1];
88
+ var panelRef = useCallback(function (node) {
89
+ return setRefs(node, dialogRef, ref, setPanelEl);
90
+ }, [ref]);
91
+ var isSheet = usePresentation(presentation) === 'sheet';
92
+ // Centred mode only: the sheet runs its own focus trap, scroll lock and exit.
93
+ var centredOpen = Boolean(isOpen) && !isSheet;
94
+ var presence = usePresence(centredOpen, {
95
+ duration: EXIT_MS
48
96
  });
49
97
  useEffect(function () {
50
98
  var _onOpenRef$current;
51
- if (isOpen) (_onOpenRef$current = onOpenRef.current) === null || _onOpenRef$current === void 0 || _onOpenRef$current.call(onOpenRef, {
52
- dialogRef: dialogRef.current
99
+ if (isOpen && panelEl) (_onOpenRef$current = onOpenRef.current) === null || _onOpenRef$current === void 0 || _onOpenRef$current.call(onOpenRef, {
100
+ dialogRef: panelEl
53
101
  });
54
- }, [isOpen, onOpenRef]);
55
- if (!isOpen) return null;
102
+ }, [isOpen, panelEl, onOpenRef]);
103
+ // Centred mode only (the sheet runs its own). Ref-counted body lock, so
104
+ // closing a Dialog stacked over another overlay keeps the page locked; the
105
+ // shared focus trap handles initial focus, Tab cycling, Escape (top layer
106
+ // only) and focus restore to the opener the moment `isOpen` turns false.
107
+ useScrollLock(centredOpen);
108
+ useFocusTrap(dialogRef, {
109
+ active: centredOpen,
110
+ onEscape: function onEscape() {
111
+ var _onCloseRef$current;
112
+ if (dismissible) (_onCloseRef$current = onCloseRef.current) === null || _onCloseRef$current === void 0 || _onCloseRef$current.call(onCloseRef);
113
+ }
114
+ });
115
+ // Sheet mode: the public bottom Sheet (content height, no snap points; drag
116
+ // to dismiss only when dismissible). Escape / backdrop follow `dismissible`
117
+ // exactly as in the centred modal; onClose is called without arguments.
118
+ if (isSheet) {
119
+ var close = function close() {
120
+ var _onCloseRef$current2;
121
+ return (_onCloseRef$current2 = onCloseRef.current) === null || _onCloseRef$current2 === void 0 ? void 0 : _onCloseRef$current2.call(onCloseRef);
122
+ };
123
+ return jsxs(Sheet, _objectSpread2(_objectSpread2({
124
+ ref: panelRef,
125
+ isOpen: Boolean(isOpen),
126
+ onClose: close,
127
+ placement: "bottom",
128
+ dismissible: dismissible,
129
+ title: title || undefined,
130
+ showCloseButton: showCloseButton,
131
+ className: className,
132
+ "data-presentation": "sheet"
133
+ }, props), {}, {
134
+ children: [!title && showCloseButton && jsx(Sheet.Header, {
135
+ className: "flex justify-end py-1",
136
+ children: jsx(CloseButton, {
137
+ onClick: close
138
+ })
139
+ }), jsx(Sheet.Body, {
140
+ children: children
141
+ }), footer && jsx(Sheet.Footer, {
142
+ className: SHEET_FOOTER,
143
+ children: footer
144
+ })]
145
+ }));
146
+ }
147
+ if (!presence.mounted) return null;
148
+ var exiting = presence.state === 'exiting';
56
149
  var sizeClasses = {
57
150
  sm: 'max-w-md',
58
151
  md: 'max-w-lg',
@@ -61,44 +154,39 @@ var Dialog = /*#__PURE__*/React.forwardRef(function (_ref, _ref2) {
61
154
  full: 'max-w-full mx-4'
62
155
  };
63
156
  var handleBackdropClick = function handleBackdropClick(e) {
64
- if (dismissible && e.target === e.currentTarget) {
157
+ if (!exiting && dismissible && e.target === e.currentTarget) {
65
158
  onClose === null || onClose === void 0 || onClose();
66
159
  }
67
160
  };
68
161
  return /*#__PURE__*/createPortal(jsx("div", {
69
- className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4",
162
+ className: cn('fixed inset-0 z-modal flex items-center justify-center bg-black/50 motion-reduce:animate-none', SAFE_GUTTER, exiting ? 'pointer-events-none animate-fade-out' : 'animate-fade-in'),
163
+ "data-state": presence.state,
70
164
  onClick: handleBackdropClick,
71
165
  children: jsxs("div", _objectSpread2(_objectSpread2({
72
- ref: function ref(node) {
73
- return setRefs(node, dialogRef, _ref2);
74
- },
75
- className: cn('w-full rounded-lg border border-line bg-surface text-foreground shadow-xl focus:outline-none animate-scale-in motion-reduce:animate-none', sizeClasses[size], className),
166
+ ref: panelRef,
167
+ className: cn('flex w-full flex-col overflow-hidden rounded-lg border border-line bg-surface text-foreground shadow-xl focus:outline-none motion-reduce:animate-none', PANEL_MAX_HEIGHT, exiting ? 'animate-scale-out' : 'animate-scale-in', sizeClasses[size], className),
76
168
  tabIndex: -1,
77
169
  role: "dialog",
78
170
  "aria-modal": "true",
79
- "aria-labelledby": title ? titleId : undefined
171
+ "aria-labelledby": title ? titleId : undefined,
172
+ "data-state": presence.state,
173
+ "data-presentation": "popover"
80
174
  }, props), {}, {
81
175
  children: [(title || showCloseButton) && jsxs("div", {
82
- className: "flex items-center justify-between border-b border-line px-6 py-4",
176
+ className: "flex shrink-0 items-center justify-between border-b border-line px-6 py-4",
83
177
  children: [title && jsx("h2", {
84
178
  id: titleId,
85
179
  className: "text-lg font-semibold text-foreground",
86
180
  children: title
87
- }), showCloseButton && jsx("button", {
88
- type: "button",
89
- className: cn('inline-flex items-center justify-center rounded-md p-1 text-muted hover:bg-gray-100 hover:text-foreground dark:hover:bg-gray-800', 'focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500', TOUCH_TARGET),
90
- onClick: onClose,
91
- "aria-label": "Close dialog",
92
- children: jsx(X, {
93
- className: "h-5 w-5",
94
- "aria-hidden": "true"
95
- })
181
+ }), showCloseButton && jsx(CloseButton, {
182
+ onClick: onClose
96
183
  })]
97
184
  }), jsx("div", {
98
- className: "px-6 py-4",
185
+ className: "min-h-0 flex-1 overflow-y-auto overscroll-contain px-6 py-4",
186
+ "data-dialog-body": "",
99
187
  children: children
100
188
  }), footer && jsx("div", {
101
- className: "flex items-center justify-end gap-3 border-t border-line px-6 py-4",
189
+ className: "flex shrink-0 items-center justify-end gap-3 border-t border-line px-6 py-4",
102
190
  children: footer
103
191
  })]
104
192
  }))
@@ -1,20 +1,19 @@
1
1
  'use client';
2
2
  import { objectWithoutProperties as _objectWithoutProperties, objectSpread2 as _objectSpread2 } from '../../_virtual/_rollupPluginBabelHelpers.js';
3
- import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
4
- import React, { useRef, useId } from 'react';
5
- import { createPortal } from 'react-dom';
3
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
+ import React, { useContext } from 'react';
6
5
  import { X } from 'lucide-react';
7
6
  import { cn } from '../../utils/cn.js';
8
- import { setRefs } from '../../utils/position.js';
9
- import { useFocusTrap } from '../../hooks/useFocusTrap.js';
10
- import { useScrollLock } from '../../hooks/useScrollLock.js';
11
- import { useSwipeToClose } from '../../hooks/useSwipeToClose.js';
12
7
  import { TOUCH_TARGET } from '../../utils/touchTarget.js';
8
+ import { SheetCore, SheetContext, mergeHandlers } from './sheetCore.js';
13
9
 
14
- var _excluded = ["isOpen", "onClose", "title", "description", "children", "placement", "size", "dismissible", "showCloseButton", "footer", "initialFocusRef", "handle", "swipeToClose", "className", "style"],
10
+ var _excluded = ["isOpen", "onClose", "title", "description", "children", "placement", "size", "dismissible", "showCloseButton", "footer", "initialFocusRef", "handle", "swipeToClose"],
15
11
  _excluded2 = ["className"],
16
12
  _excluded3 = ["className"],
17
13
  _excluded4 = ["className"];
14
+ // Record-peek / editor panel. Built on the internal sheet core (ADR 0001,
15
+ // DTUI-33): it slides in and out from its edge, stays mounted through the
16
+ // exit, and `swipeToClose` drags it toward its own edge on every placement.
18
17
  var PLACEMENT_CLASSES = {
19
18
  right: "inset-y-0 right-0 h-full w-full border-l",
20
19
  left: "inset-y-0 left-0 h-full w-full border-r",
@@ -29,12 +28,13 @@ var SIDE_SIZES = {
29
28
  xl: "max-w-4xl",
30
29
  full: "max-w-full"
31
30
  };
31
+ // `dvh`, not `vh`: mobile browser chrome and the on-screen keyboard don't push the panel off-screen.
32
32
  var EDGE_SIZES = {
33
- sm: "max-h-[30vh]",
34
- md: "max-h-[50vh]",
35
- lg: "max-h-[75vh]",
36
- xl: "max-h-[90vh]",
37
- full: "h-full"
33
+ sm: "max-h-[30dvh]",
34
+ md: "max-h-[50dvh]",
35
+ lg: "max-h-[75dvh]",
36
+ xl: "max-h-[90dvh]",
37
+ full: "h-dvh"
38
38
  };
39
39
  var CloseButton = function CloseButton(_ref) {
40
40
  var onClose = _ref.onClose,
@@ -50,141 +50,139 @@ var CloseButton = function CloseButton(_ref) {
50
50
  })
51
51
  });
52
52
  };
53
- var Drawer = /*#__PURE__*/React.forwardRef(function (_ref2, _ref3) {
54
- var isOpen = _ref2.isOpen,
55
- onClose = _ref2.onClose,
56
- title = _ref2.title,
57
- description = _ref2.description,
58
- children = _ref2.children,
59
- _ref2$placement = _ref2.placement,
60
- placement = _ref2$placement === void 0 ? "right" : _ref2$placement,
61
- _ref2$size = _ref2.size,
62
- size = _ref2$size === void 0 ? "md" : _ref2$size,
63
- _ref2$dismissible = _ref2.dismissible,
64
- dismissible = _ref2$dismissible === void 0 ? true : _ref2$dismissible,
65
- _ref2$showCloseButton = _ref2.showCloseButton,
66
- showCloseButton = _ref2$showCloseButton === void 0 ? true : _ref2$showCloseButton,
67
- footer = _ref2.footer,
68
- initialFocusRef = _ref2.initialFocusRef,
69
- handle = _ref2.handle,
70
- _ref2$swipeToClose = _ref2.swipeToClose,
71
- swipeToClose = _ref2$swipeToClose === void 0 ? false : _ref2$swipeToClose,
72
- className = _ref2.className,
73
- style = _ref2.style,
74
- props = _objectWithoutProperties(_ref2, _excluded);
75
- var panelRef = useRef(null);
76
- var titleId = "drawer-title-".concat(useId());
77
- var descriptionId = "drawer-description-".concat(useId());
53
+ // Inside the core a header is the grab zone: it gets the drag handlers (when
54
+ // `swipeToClose` is on) and the grab cursor. The scrolling body never drags.
55
+ function useGrabZone(props) {
56
+ var ctx = useContext(SheetContext);
57
+ if (!ctx) return {
58
+ dragProps: {},
59
+ grabClass: ""
60
+ };
61
+ return {
62
+ dragProps: mergeHandlers(ctx.headerDragProps, props),
63
+ grabClass: ctx.grabClass
64
+ };
65
+ }
66
+ var DrawerTitleBar = function DrawerTitleBar(_ref2) {
67
+ var className = _ref2.className,
68
+ children = _ref2.children;
69
+ var _useGrabZone = useGrabZone({}),
70
+ dragProps = _useGrabZone.dragProps,
71
+ grabClass = _useGrabZone.grabClass;
72
+ return jsx("div", _objectSpread2(_objectSpread2({}, dragProps), {}, {
73
+ className: cn("flex shrink-0 items-start justify-between gap-4 border-b border-gray-200 px-6 py-4 dark:border-gray-700", grabClass, className),
74
+ children: children
75
+ }));
76
+ };
77
+ var Drawer = /*#__PURE__*/React.forwardRef(function (_ref3, ref) {
78
+ var isOpen = _ref3.isOpen,
79
+ onClose = _ref3.onClose,
80
+ title = _ref3.title,
81
+ description = _ref3.description,
82
+ children = _ref3.children,
83
+ _ref3$placement = _ref3.placement,
84
+ placement = _ref3$placement === void 0 ? "right" : _ref3$placement,
85
+ _ref3$size = _ref3.size,
86
+ size = _ref3$size === void 0 ? "md" : _ref3$size,
87
+ _ref3$dismissible = _ref3.dismissible,
88
+ dismissible = _ref3$dismissible === void 0 ? true : _ref3$dismissible,
89
+ _ref3$showCloseButton = _ref3.showCloseButton,
90
+ showCloseButton = _ref3$showCloseButton === void 0 ? true : _ref3$showCloseButton,
91
+ footer = _ref3.footer,
92
+ initialFocusRef = _ref3.initialFocusRef,
93
+ handle = _ref3.handle,
94
+ _ref3$swipeToClose = _ref3.swipeToClose,
95
+ swipeToClose = _ref3$swipeToClose === void 0 ? false : _ref3$swipeToClose,
96
+ props = _objectWithoutProperties(_ref3, _excluded);
78
97
  var side = PLACEMENT_CLASSES[placement] ? placement : "right";
79
- var isEdge = side === "top" || side === "bottom";
98
+ var sizes = side === "top" || side === "bottom" ? EDGE_SIZES : SIDE_SIZES;
80
99
  // Without title/description/footer the children are the panel content, so
81
100
  // Drawer.Header / Drawer.Body / Drawer.Footer compose without double padding.
82
101
  var composed = !title && !description && !footer;
83
- var showHandle = handle !== null && handle !== void 0 ? handle : side === "bottom";
84
- var swipe = useSwipeToClose({
85
- enabled: swipeToClose && side === "bottom" && dismissible,
86
- onClose: onClose
87
- });
88
- // The grab zone (handle + header) starts a swipe; the scrolling body never does.
89
- var grabProps = swipeToClose && side === "bottom" && dismissible ? _objectSpread2(_objectSpread2({}, swipe.dragProps), {}, {
90
- className: "touch-none cursor-grab"
91
- }) : {
92
- className: ""
102
+ // Drawer has always called onClose() without arguments; keep it that way.
103
+ var close = function close() {
104
+ return onClose === null || onClose === void 0 ? void 0 : onClose();
93
105
  };
94
- useScrollLock(isOpen);
95
- useFocusTrap(panelRef, {
96
- active: isOpen,
97
- initialFocusRef: initialFocusRef,
98
- onEscape: function onEscape() {
99
- if (dismissible) onClose === null || onClose === void 0 || onClose();
106
+ var renderHeader = function renderHeader(_ref4) {
107
+ var titleId = _ref4.titleId,
108
+ descriptionId = _ref4.descriptionId,
109
+ showHandle = _ref4.showHandle;
110
+ if (composed) {
111
+ return showCloseButton ? jsx(CloseButton, {
112
+ onClose: close,
113
+ className: "absolute right-3 top-3 z-10"
114
+ }) : null;
100
115
  }
101
- });
102
- if (!isOpen || typeof document === "undefined") return null;
103
- return /*#__PURE__*/createPortal(jsxs("div", {
104
- className: "fixed inset-0 z-50",
105
- children: [jsx("div", {
106
- "aria-hidden": "true",
107
- "data-testid": "drawer-backdrop",
108
- className: "absolute inset-0 bg-black/50 animate-fade-in motion-reduce:animate-none",
109
- onClick: function onClick() {
110
- return dismissible && (onClose === null || onClose === void 0 ? void 0 : onClose());
111
- }
112
- }), jsxs("div", _objectSpread2(_objectSpread2({
113
- ref: function ref(node) {
114
- return setRefs(node, panelRef, _ref3);
115
- },
116
- role: "dialog",
117
- "aria-modal": "true",
118
- "aria-labelledby": title ? titleId : undefined,
119
- "aria-describedby": description ? descriptionId : undefined,
120
- tabIndex: -1,
121
- "data-placement": side,
122
- className: cn("absolute flex flex-col border-gray-200 bg-white text-gray-900 shadow-xl focus:outline-none", "dark:border-gray-700 dark:bg-gray-900 dark:text-gray-100", "animate-fade-in motion-reduce:animate-none", PLACEMENT_CLASSES[side], (isEdge ? EDGE_SIZES : SIDE_SIZES)[size] || (isEdge ? EDGE_SIZES.md : SIDE_SIZES.md), !swipe.dragging && "transition-transform duration-200 ease-out motion-reduce:transition-none", className),
123
- style: swipe.offset ? _objectSpread2(_objectSpread2({}, style), {}, {
124
- transform: "translateY(".concat(swipe.offset, "px)")
125
- }) : style
126
- }, props), {}, {
127
- children: [showHandle && jsx("div", _objectSpread2(_objectSpread2({
128
- "aria-hidden": "true",
129
- "data-testid": "drawer-handle"
130
- }, grabProps), {}, {
131
- className: cn("flex shrink-0 justify-center pb-1 pt-3", grabProps.className),
132
- children: jsx("span", {
133
- className: "h-1.5 w-10 rounded-full bg-gray-300 dark:bg-gray-600"
134
- })
135
- })), composed ? jsxs(Fragment, {
136
- children: [showCloseButton && jsx(CloseButton, {
137
- onClose: onClose,
138
- className: "absolute right-3 top-3"
139
- }), children]
140
- }) : jsxs(Fragment, {
141
- children: [jsxs("div", _objectSpread2(_objectSpread2({}, grabProps), {}, {
142
- className: cn("flex items-start justify-between gap-4 border-b border-gray-200 px-6 py-4 dark:border-gray-700", showHandle && "pt-2", grabProps.className),
143
- children: [jsxs("div", {
144
- className: "min-w-0",
145
- children: [title && jsx("h2", {
146
- id: titleId,
147
- className: "text-lg font-semibold",
148
- children: title
149
- }), description && jsx("p", {
150
- id: descriptionId,
151
- className: "mt-1 text-sm text-gray-500 dark:text-gray-400",
152
- children: description
153
- })]
154
- }), showCloseButton && jsx(CloseButton, {
155
- onClose: onClose
156
- })]
157
- })), jsx("div", {
158
- className: "min-h-0 flex-1 overflow-y-auto px-6 py-4",
159
- children: children
160
- }), footer && jsx("div", {
161
- className: "flex items-center justify-end gap-3 border-t border-gray-200 px-6 py-4 dark:border-gray-700",
162
- children: footer
116
+ return jsxs(DrawerTitleBar, {
117
+ className: cn(showHandle && side !== "top" && "pt-2"),
118
+ children: [jsxs("div", {
119
+ className: "min-w-0",
120
+ children: [title && jsx("h2", {
121
+ id: titleId,
122
+ className: "text-lg font-semibold",
123
+ children: title
124
+ }), description && jsx("p", {
125
+ id: descriptionId,
126
+ className: "mt-1 text-sm text-gray-500 dark:text-gray-400",
127
+ children: description
163
128
  })]
129
+ }), showCloseButton && jsx(CloseButton, {
130
+ onClose: close
164
131
  })]
165
- }))]
166
- }), document.body);
167
- });
168
- Drawer.displayName = "Drawer";
169
- var DrawerHeader = /*#__PURE__*/React.forwardRef(function (_ref4, ref) {
170
- var className = _ref4.className,
171
- props = _objectWithoutProperties(_ref4, _excluded2);
172
- return jsx("div", _objectSpread2({
132
+ });
133
+ };
134
+ return jsx(SheetCore, _objectSpread2(_objectSpread2({
173
135
  ref: ref,
174
- className: cn("border-b border-gray-200 px-6 py-4 dark:border-gray-700", className)
175
- }, props));
136
+ open: Boolean(isOpen),
137
+ onClose: close,
138
+ placement: side,
139
+ dismissible: dismissible,
140
+ dragEnabled: swipeToClose && dismissible,
141
+ handle: handle,
142
+ initialFocus: initialFocusRef,
143
+ hasTitle: Boolean(title),
144
+ hasDescription: Boolean(description),
145
+ idPrefix: "drawer",
146
+ testIdPrefix: "drawer",
147
+ handleBarClassName: "bg-gray-300 dark:bg-gray-600",
148
+ panelClassName: cn("border-gray-200 bg-white text-gray-900 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-100", PLACEMENT_CLASSES[side], sizes[size] || sizes.md),
149
+ renderHeader: renderHeader
150
+ }, props), {}, {
151
+ children: composed ? children : jsxs(Fragment, {
152
+ children: [jsx("div", {
153
+ className: "min-h-0 flex-1 overflow-y-auto overscroll-contain px-6 py-4",
154
+ children: children
155
+ }), footer && jsx("div", {
156
+ className: "flex shrink-0 items-center justify-end gap-3 border-t border-gray-200 px-6 py-4 dark:border-gray-700",
157
+ children: footer
158
+ })]
159
+ })
160
+ }));
176
161
  });
177
- var DrawerBody = /*#__PURE__*/React.forwardRef(function (_ref5, ref) {
162
+ Drawer.displayName = "Drawer";
163
+ var DrawerHeader = /*#__PURE__*/React.forwardRef(function (_ref5, ref) {
178
164
  var className = _ref5.className,
179
- props = _objectWithoutProperties(_ref5, _excluded3);
165
+ props = _objectWithoutProperties(_ref5, _excluded2);
166
+ var _useGrabZone2 = useGrabZone(props),
167
+ dragProps = _useGrabZone2.dragProps,
168
+ grabClass = _useGrabZone2.grabClass;
169
+ return jsx("div", _objectSpread2(_objectSpread2(_objectSpread2({
170
+ ref: ref
171
+ }, props), dragProps), {}, {
172
+ className: cn("border-b border-gray-200 px-6 py-4 dark:border-gray-700", grabClass, className)
173
+ }));
174
+ });
175
+ var DrawerBody = /*#__PURE__*/React.forwardRef(function (_ref6, ref) {
176
+ var className = _ref6.className,
177
+ props = _objectWithoutProperties(_ref6, _excluded3);
180
178
  return jsx("div", _objectSpread2({
181
179
  ref: ref,
182
180
  className: cn("min-h-0 flex-1 overflow-y-auto px-6 py-4", className)
183
181
  }, props));
184
182
  });
185
- var DrawerFooter = /*#__PURE__*/React.forwardRef(function (_ref6, ref) {
186
- var className = _ref6.className,
187
- props = _objectWithoutProperties(_ref6, _excluded4);
183
+ var DrawerFooter = /*#__PURE__*/React.forwardRef(function (_ref7, ref) {
184
+ var className = _ref7.className,
185
+ props = _objectWithoutProperties(_ref7, _excluded4);
188
186
  return jsx("div", _objectSpread2({
189
187
  ref: ref,
190
188
  className: cn("flex items-center justify-end gap-3 border-t border-gray-200 px-6 py-4 dark:border-gray-700", className)