@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.
- package/README.md +40 -6
- package/ai-skills/dreamtree-ui.md +47 -9
- package/codemods/v2.cjs +198 -0
- package/dist/ai-schema.json +609 -33
- package/dist/components/blocks/AppShell.d.ts.map +1 -1
- package/dist/components/blocks/BottomActionBar.d.ts +6 -0
- package/dist/components/blocks/BottomActionBar.d.ts.map +1 -0
- package/dist/components/blocks/ConfirmDialog.d.ts.map +1 -1
- package/dist/components/blocks/PageHeader.d.ts.map +1 -1
- package/dist/components/core/Checkbox.d.ts.map +1 -1
- package/dist/components/core/DatePicker.d.ts.map +1 -1
- package/dist/components/core/DateRangePicker.d.ts.map +1 -1
- package/dist/components/core/Form.d.ts.map +1 -1
- package/dist/components/core/Input.d.ts.map +1 -1
- package/dist/components/core/Popover.d.ts.map +1 -1
- package/dist/components/core/ProgressBar.d.ts.map +1 -1
- package/dist/components/core/Rate.d.ts.map +1 -1
- package/dist/components/core/SearchField.d.ts +9 -0
- package/dist/components/core/SearchField.d.ts.map +1 -0
- package/dist/components/core/SegmentedControl.d.ts +8 -0
- package/dist/components/core/SegmentedControl.d.ts.map +1 -0
- package/dist/components/core/Select.d.ts.map +1 -1
- package/dist/components/core/SwipeableListItem.d.ts +4 -0
- package/dist/components/core/SwipeableListItem.d.ts.map +1 -0
- package/dist/components/core/SwipeableListItemGesture.d.ts +58 -0
- package/dist/components/core/SwipeableListItemGesture.d.ts.map +1 -0
- package/dist/components/core/Switch.d.ts.map +1 -1
- package/dist/components/core/Table/ActionMenu.d.ts +10 -3
- package/dist/components/core/Table/ActionMenu.d.ts.map +1 -1
- package/dist/components/core/Table/MobileCard.d.ts +10 -1
- package/dist/components/core/Table/MobileCard.d.ts.map +1 -1
- package/dist/components/core/Table/MobileFilters.d.ts +8 -2
- package/dist/components/core/Table/MobileFilters.d.ts.map +1 -1
- package/dist/components/core/Table/Table.d.ts.map +1 -1
- package/dist/components/core/Table/TableRow.d.ts +2 -1
- package/dist/components/core/Table/TableRow.d.ts.map +1 -1
- package/dist/components/core/Tabs.d.ts +3 -3
- package/dist/components/core/Tabs.d.ts.map +1 -1
- package/dist/components/core/ThreeDotPopover.d.ts +10 -22
- package/dist/components/core/ThreeDotPopover.d.ts.map +1 -1
- package/dist/components/core/Tooltip.d.ts +2 -1
- package/dist/components/core/Tooltip.d.ts.map +1 -1
- package/dist/components/core/internal/Calendar.d.ts +77 -0
- package/dist/components/core/internal/Calendar.d.ts.map +1 -0
- package/dist/components/core/menu/ActionMenuSurface.d.ts +42 -0
- package/dist/components/core/menu/ActionMenuSurface.d.ts.map +1 -0
- package/dist/components/core/menu/ActionSheetMenu.d.ts +17 -0
- package/dist/components/core/menu/ActionSheetMenu.d.ts.map +1 -0
- package/dist/components/core/menu/MenuPopover.d.ts +21 -0
- package/dist/components/core/menu/MenuPopover.d.ts.map +1 -0
- package/dist/components/core/menu/menuItems.d.ts +21 -0
- package/dist/components/core/menu/menuItems.d.ts.map +1 -0
- package/dist/components/core/menu/menuKeyboard.d.ts +16 -0
- package/dist/components/core/menu/menuKeyboard.d.ts.map +1 -0
- package/dist/components/feedback/ActionSheet.d.ts +4 -0
- package/dist/components/feedback/ActionSheet.d.ts.map +1 -0
- package/dist/components/feedback/Alert.d.ts.map +1 -1
- package/dist/components/feedback/Dialog.d.ts.map +1 -1
- package/dist/components/feedback/Drawer.d.ts.map +1 -1
- package/dist/components/feedback/Sheet.d.ts +4 -0
- package/dist/components/feedback/Sheet.d.ts.map +1 -0
- package/dist/components/feedback/Toast.d.ts +4 -3
- package/dist/components/feedback/Toast.d.ts.map +1 -1
- package/dist/components/feedback/ToastItem.d.ts +47 -0
- package/dist/components/feedback/ToastItem.d.ts.map +1 -0
- package/dist/components/feedback/ToastStack.d.ts +20 -0
- package/dist/components/feedback/ToastStack.d.ts.map +1 -0
- package/dist/components/feedback/ToastSwipe.d.ts +22 -0
- package/dist/components/feedback/ToastSwipe.d.ts.map +1 -0
- package/dist/components/feedback/Tour.d.ts.map +1 -1
- package/dist/components/feedback/sheetCore.d.ts +30 -0
- package/dist/components/feedback/sheetCore.d.ts.map +1 -0
- package/dist/components/navigation/Navbar.d.ts.map +1 -1
- package/dist/components/navigation/Sidebar.d.ts +6 -0
- package/dist/components/navigation/Sidebar.d.ts.map +1 -1
- package/dist/components/rich-text/RichTextEditor.d.ts.map +1 -1
- package/dist/components/rich-text/RichTextToolbar.d.ts +6 -2
- package/dist/components/rich-text/RichTextToolbar.d.ts.map +1 -1
- package/dist/components/utility/PullToRefresh.d.ts +5 -0
- package/dist/components/utility/PullToRefresh.d.ts.map +1 -0
- package/dist/esm/components/blocks/AppShell.js +97 -90
- package/dist/esm/components/blocks/BottomActionBar.js +132 -0
- package/dist/esm/components/blocks/ConfirmDialog.js +40 -4
- package/dist/esm/components/blocks/PageHeader.js +16 -3
- package/dist/esm/components/core/Checkbox.js +13 -24
- package/dist/esm/components/core/DatePicker.js +117 -304
- package/dist/esm/components/core/DateRangePicker.js +260 -308
- package/dist/esm/components/core/Form.js +11 -2
- package/dist/esm/components/core/Input.js +13 -18
- package/dist/esm/components/core/Popover.js +59 -48
- package/dist/esm/components/core/ProgressBar.js +36 -64
- package/dist/esm/components/core/Rate.js +5 -10
- package/dist/esm/components/core/SearchField.js +311 -0
- package/dist/esm/components/core/SegmentedControl.js +189 -0
- package/dist/esm/components/core/Select.js +185 -190
- package/dist/esm/components/core/SwipeableListItem.js +286 -0
- package/dist/esm/components/core/SwipeableListItemGesture.js +219 -0
- package/dist/esm/components/core/Switch.js +11 -13
- package/dist/esm/components/core/Table/ActionMenu.js +45 -231
- package/dist/esm/components/core/Table/ColumnMenu.js +1 -1
- package/dist/esm/components/core/Table/MobileCard.js +66 -16
- package/dist/esm/components/core/Table/MobileFilters.js +181 -10
- package/dist/esm/components/core/Table/Table.js +149 -102
- package/dist/esm/components/core/Table/TableRow.js +10 -2
- package/dist/esm/components/core/Tabs.js +7 -7
- package/dist/esm/components/core/ThreeDotPopover.js +93 -99
- package/dist/esm/components/core/Tooltip.js +138 -145
- package/dist/esm/components/core/internal/Calendar.js +381 -0
- package/dist/esm/components/core/menu/ActionMenuSurface.js +110 -0
- package/dist/esm/components/core/menu/ActionSheetMenu.js +140 -0
- package/dist/esm/components/core/menu/MenuPopover.js +177 -0
- package/dist/esm/components/core/menu/menuItems.js +61 -0
- package/dist/esm/components/core/menu/menuKeyboard.js +54 -0
- package/dist/esm/components/feedback/ActionSheet.js +55 -0
- package/dist/esm/components/feedback/Alert.js +63 -53
- package/dist/esm/components/feedback/Dialog.js +141 -53
- package/dist/esm/components/feedback/Drawer.js +130 -132
- package/dist/esm/components/feedback/Sheet.js +125 -0
- package/dist/esm/components/feedback/Toast.js +21 -291
- package/dist/esm/components/feedback/ToastItem.js +348 -0
- package/dist/esm/components/feedback/ToastStack.js +130 -0
- package/dist/esm/components/feedback/ToastSwipe.js +52 -0
- package/dist/esm/components/feedback/Tour.js +107 -30
- package/dist/esm/components/feedback/sheetCore.js +494 -0
- package/dist/esm/components/navigation/Dropdown.js +1 -1
- package/dist/esm/components/navigation/Navbar.js +172 -37
- package/dist/esm/components/navigation/Sidebar.js +263 -111
- package/dist/esm/components/rich-text/RichTextEditor.js +9 -3
- package/dist/esm/components/rich-text/RichTextToolbar.js +29 -9
- package/dist/esm/components/utility/Portal.js +31 -0
- package/dist/esm/components/utility/PullToRefresh.js +229 -0
- package/dist/esm/hooks/useFloatingPosition.js +82 -0
- package/dist/esm/hooks/useHaptics.js +65 -0
- package/dist/esm/hooks/usePresence.js +58 -0
- package/dist/esm/hooks/usePresentation.js +21 -0
- package/dist/esm/hooks/useSheetDrag.js +220 -0
- package/dist/esm/index.js +13 -2
- package/dist/esm/utils/position.js +10 -63
- package/dist/hooks/useFloatingPosition.d.ts +8 -4
- package/dist/hooks/useFloatingPosition.d.ts.map +1 -1
- package/dist/hooks/useHaptics.d.ts +19 -0
- package/dist/hooks/useHaptics.d.ts.map +1 -0
- package/dist/hooks/useSheetDrag.d.ts +31 -0
- package/dist/hooks/useSheetDrag.d.ts.map +1 -0
- package/dist/index.css +1 -1
- package/dist/index.d.ts +9 -1
- package/dist/index.esm.css +1 -1
- package/dist/index.esm.js +8851 -3198
- package/dist/index.js +8856 -3195
- package/dist/rich-text/index.js +41 -11
- package/dist/types/blocks.d.ts +32 -2
- package/dist/types/blocks.d.ts.map +1 -1
- package/dist/types/index.d.ts +353 -19
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/rich-text.d.ts +5 -0
- package/dist/types/rich-text.d.ts.map +1 -1
- package/dist/utils/position.d.ts +0 -15
- package/dist/utils/position.d.ts.map +1 -1
- package/doc/README.md +15 -0
- package/mcp/catalog.snapshot.json +1398 -464
- package/package.json +7 -2
- package/dist/esm/hooks/useSwipeToClose.js +0 -66
- package/dist/hooks/useSwipeToClose.d.ts +0 -24
- package/dist/hooks/useSwipeToClose.d.ts.map +0 -1
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { objectWithoutProperties as _objectWithoutProperties, slicedToArray as _slicedToArray, objectSpread2 as _objectSpread2, toConsumableArray as _toConsumableArray, typeof as _typeof, defineProperty as _defineProperty } from '../../_virtual/_rollupPluginBabelHelpers.js';
|
|
3
3
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
4
|
-
import React, { useState, useRef, useImperativeHandle, useId, useMemo, useEffect
|
|
4
|
+
import React, { useState, useRef, useCallback, useImperativeHandle, useId, useMemo, useEffect } from 'react';
|
|
5
5
|
import { createPortal } from 'react-dom';
|
|
6
6
|
import { cn } from '../../utils/cn.js';
|
|
7
|
+
import { setRefs } from '../../utils/position.js';
|
|
8
|
+
import { TOUCH_TARGET } from '../../utils/touchTarget.js';
|
|
7
9
|
import { useLatestRef } from '../../hooks/useLatestRef.js';
|
|
10
|
+
import { useMediaQuery } from '../../hooks/useMediaQuery.js';
|
|
11
|
+
import { usePresentation } from '../../hooks/usePresentation.js';
|
|
12
|
+
import { useFloatingPosition } from '../../hooks/useFloatingPosition.js';
|
|
13
|
+
import Sheet from '../feedback/Sheet.js';
|
|
14
|
+
import Button from './Button.js';
|
|
8
15
|
import { ChevronDown, X, Check, Search, Plus } from 'lucide-react';
|
|
9
16
|
|
|
10
|
-
var _excluded = ["options", "value", "onChange", "placeholder", "label", "error", "helperText", "disabled", "required", "multiSelect", "searchable", "grouped", "onMenuItemRender", "className", "allowClear", "creatable", "onCreateOption", "onSearch", "loading", "selectAllOption", "closeOnSelect", "maxTagCount", "renderGroupLabel", "name", "size"];
|
|
17
|
+
var _excluded = ["options", "value", "onChange", "placeholder", "label", "error", "helperText", "disabled", "required", "multiSelect", "searchable", "grouped", "onMenuItemRender", "className", "allowClear", "creatable", "onCreateOption", "onSearch", "loading", "selectAllOption", "closeOnSelect", "maxTagCount", "renderGroupLabel", "name", "size", "presentation"];
|
|
11
18
|
/**
|
|
12
19
|
* Enhanced Select component (debounce removed)
|
|
13
20
|
* - Supports single and multi select
|
|
@@ -19,8 +26,8 @@ var _excluded = ["options", "value", "onChange", "placeholder", "label", "error"
|
|
|
19
26
|
* - ARIA & keyboard accessibility
|
|
20
27
|
* - Custom render per option via onMenuItemRender
|
|
21
28
|
* - Max tag count with overflow indicator
|
|
22
|
-
* -
|
|
23
|
-
* -
|
|
29
|
+
* - Popover: anchored with useFloatingPosition (flip / shift / size)
|
|
30
|
+
* - Sheet: a bottom Sheet on small touch screens (`presentation`, ADR 0001)
|
|
24
31
|
*/
|
|
25
32
|
var isPlainObject = function isPlainObject(v) {
|
|
26
33
|
return v !== null && _typeof(v) === "object";
|
|
@@ -109,6 +116,13 @@ var highlight = function highlight() {
|
|
|
109
116
|
// Trigger sizes. min-h (not h) so multi-select tags can wrap. `md` keeps its
|
|
110
117
|
// desktop look but grows to the 44px touch target on coarse pointers; `lg` is
|
|
111
118
|
// 44px everywhere.
|
|
119
|
+
// Popover menu height cap (px); it also shrinks to the room left on its side.
|
|
120
|
+
var MENU_MAX_HEIGHT = 250;
|
|
121
|
+
// Option rows: compact in the popover, 48px touch rows in the sheet.
|
|
122
|
+
var ROW_CLASSES = {
|
|
123
|
+
popover: "px-3 py-2 text-sm",
|
|
124
|
+
sheet: "min-h-12 px-4 py-3 text-base"
|
|
125
|
+
};
|
|
112
126
|
var SIZE_CLASSES = {
|
|
113
127
|
sm: "min-h-8 px-2.5 py-1 text-xs",
|
|
114
128
|
md: "min-h-[38px] px-3 py-2 text-sm [@media(pointer:coarse)]:min-h-11",
|
|
@@ -152,6 +166,8 @@ var Select = /*#__PURE__*/React.forwardRef(function (_ref, forwardedRef) {
|
|
|
152
166
|
name = _ref.name,
|
|
153
167
|
_ref$size = _ref.size,
|
|
154
168
|
size = _ref$size === void 0 ? "md" : _ref$size,
|
|
169
|
+
_ref$presentation = _ref.presentation,
|
|
170
|
+
presentation = _ref$presentation === void 0 ? "auto" : _ref$presentation,
|
|
155
171
|
props = _objectWithoutProperties(_ref, _excluded);
|
|
156
172
|
var _useState = useState(false),
|
|
157
173
|
_useState2 = _slicedToArray(_useState, 2),
|
|
@@ -174,20 +190,33 @@ var Select = /*#__PURE__*/React.forwardRef(function (_ref, forwardedRef) {
|
|
|
174
190
|
var searchRef = useRef(null);
|
|
175
191
|
var menuRef = useRef(null);
|
|
176
192
|
var listboxRef = useRef(null);
|
|
177
|
-
//
|
|
178
|
-
var
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
var
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
193
|
+
// 'sheet' on small touch screens (or when forced), else 'popover'.
|
|
194
|
+
var isSheet = usePresentation(presentation) === "sheet";
|
|
195
|
+
// Focusing the search box pops the on-screen keyboard over the options,
|
|
196
|
+
// so touch users get the listbox focused instead and tap search to type.
|
|
197
|
+
var isCoarse = useMediaQuery("(pointer: coarse)");
|
|
198
|
+
var autoFocusSearch = searchable && !isSheet && !isCoarse;
|
|
199
|
+
// Popover mode: the portaled menu is anchored to the trigger wrapper.
|
|
200
|
+
var _useFloatingPosition = useFloatingPosition({
|
|
201
|
+
placement: "bottom-start",
|
|
202
|
+
offset: 4,
|
|
203
|
+
matchWidth: "exact",
|
|
204
|
+
maxHeight: MENU_MAX_HEIGHT,
|
|
205
|
+
strategy: "fixed",
|
|
206
|
+
open: isOpen && !isSheet
|
|
207
|
+
}),
|
|
208
|
+
floatingRefs = _useFloatingPosition.refs,
|
|
209
|
+
floatingStyles = _useFloatingPosition.floatingStyles,
|
|
210
|
+
floatingPlacement = _useFloatingPosition.placement,
|
|
211
|
+
isPositioned = _useFloatingPosition.isPositioned;
|
|
212
|
+
var setReference = floatingRefs.setReference,
|
|
213
|
+
setFloating = floatingRefs.setFloating;
|
|
214
|
+
var wrapperRef = useCallback(function (node) {
|
|
215
|
+
return setRefs(node, selectRef, setReference);
|
|
216
|
+
}, [setReference]);
|
|
217
|
+
var popoverRef = useCallback(function (node) {
|
|
218
|
+
return setRefs(node, menuRef, setFloating);
|
|
219
|
+
}, [setFloating]);
|
|
191
220
|
useImperativeHandle(forwardedRef, function () {
|
|
192
221
|
return selectRef.current;
|
|
193
222
|
}, []);
|
|
@@ -278,10 +307,22 @@ var Select = /*#__PURE__*/React.forwardRef(function (_ref, forwardedRef) {
|
|
|
278
307
|
};
|
|
279
308
|
var activeDescendant = isOpen && focusedIndex >= 0 && focusedIndex < navOptions.length ? optionId(focusedIndex) : undefined;
|
|
280
309
|
// A new search term or a collapsed group changes what the rows are, so
|
|
281
|
-
// the active row no longer means the same option: start over.
|
|
282
|
-
|
|
310
|
+
// the active row no longer means the same option: start over. Adjusted
|
|
311
|
+
// during render (previous-value pattern), not in an effect.
|
|
312
|
+
var _useState9 = useState({
|
|
313
|
+
searchTerm: searchTerm,
|
|
314
|
+
collapsedGroups: collapsedGroups
|
|
315
|
+
}),
|
|
316
|
+
_useState0 = _slicedToArray(_useState9, 2),
|
|
317
|
+
rowsKey = _useState0[0],
|
|
318
|
+
setRowsKey = _useState0[1];
|
|
319
|
+
if (rowsKey.searchTerm !== searchTerm || rowsKey.collapsedGroups !== collapsedGroups) {
|
|
320
|
+
setRowsKey({
|
|
321
|
+
searchTerm: searchTerm,
|
|
322
|
+
collapsedGroups: collapsedGroups
|
|
323
|
+
});
|
|
283
324
|
setFocusedIndex(-1);
|
|
284
|
-
}
|
|
325
|
+
}
|
|
285
326
|
// Keep the active option visible while arrowing through a long list.
|
|
286
327
|
useEffect(function () {
|
|
287
328
|
var _el$scrollIntoView;
|
|
@@ -291,101 +332,22 @@ var Select = /*#__PURE__*/React.forwardRef(function (_ref, forwardedRef) {
|
|
|
291
332
|
block: "nearest"
|
|
292
333
|
});
|
|
293
334
|
}, [activeDescendant]);
|
|
294
|
-
//
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
var rect = selectRef.current.getBoundingClientRect();
|
|
300
|
-
var viewportH = window.innerHeight;
|
|
301
|
-
var spaceBelow = viewportH - rect.bottom - MARGIN;
|
|
302
|
-
var spaceAbove = rect.top - MARGIN;
|
|
303
|
-
// desired approximate menu height (we can try to measure menu if already rendered)
|
|
304
|
-
var desiredMenuHeight = DEFAULT_MENU_MAX;
|
|
305
|
-
if (menuRef.current) {
|
|
306
|
-
// when menu exists, use its scrollHeight as desired
|
|
307
|
-
desiredMenuHeight = Math.min(menuRef.current.scrollHeight, DEFAULT_MENU_MAX);
|
|
308
|
-
}
|
|
309
|
-
// decide placement: prefer bottom unless not enough space and top has more space
|
|
310
|
-
var nextPlacement;
|
|
311
|
-
var nextMaxHeight;
|
|
312
|
-
if (spaceBelow >= desiredMenuHeight || spaceBelow >= spaceAbove) {
|
|
313
|
-
nextPlacement = "bottom";
|
|
314
|
-
nextMaxHeight = Math.min(desiredMenuHeight, Math.max(80, spaceBelow));
|
|
315
|
-
} else {
|
|
316
|
-
nextPlacement = "top";
|
|
317
|
-
nextMaxHeight = Math.min(desiredMenuHeight, Math.max(80, spaceAbove));
|
|
318
|
-
}
|
|
319
|
-
setPlacement(nextPlacement);
|
|
320
|
-
setMenuMaxHeight(nextMaxHeight);
|
|
321
|
-
// The menu is portaled to document.body with position:fixed, so it floats
|
|
322
|
-
// above any ancestor with overflow:hidden|auto|scroll. Anchor it to the
|
|
323
|
-
// trigger via the viewport-relative rect (no scroll offset for fixed).
|
|
324
|
-
// maxHeight here mirrors the inline style applied to the menu (+10 fudge).
|
|
325
|
-
var renderedMaxHeight = nextMaxHeight + 10;
|
|
326
|
-
var next = {
|
|
327
|
-
left: rect.left,
|
|
328
|
-
width: rect.width
|
|
329
|
-
};
|
|
330
|
-
if (nextPlacement === "bottom") {
|
|
331
|
-
next.top = rect.bottom + 4; // ~mt-1
|
|
332
|
-
} else {
|
|
333
|
-
// place above the trigger; bottom of menu sits just above the trigger top
|
|
334
|
-
next.top = Math.max(MARGIN, rect.top - 4 - renderedMaxHeight);
|
|
335
|
-
}
|
|
336
|
-
setMenuCoords(next);
|
|
337
|
-
};
|
|
338
|
-
// Anchor the portaled menu BEFORE the browser paints, so it never flashes
|
|
339
|
-
// at (0,0). The menu is already in the DOM (createPortal runs during render),
|
|
340
|
-
// we just need its fixed coords set synchronously.
|
|
341
|
-
useLayoutEffect(function () {
|
|
342
|
-
if (isOpen) calculatePlacement();
|
|
343
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
344
|
-
}, [isOpen, filtered.length]);
|
|
335
|
+
// Popover mode: move focus into the popup once it is in the DOM
|
|
336
|
+
// (WAI-ARIA collapsible listbox) — the search box when searchable on a
|
|
337
|
+
// fine pointer, otherwise the listbox itself. Both carry
|
|
338
|
+
// aria-activedescendant and the same key handling as the trigger. The
|
|
339
|
+
// sheet moves focus itself (to the listbox, via `initialFocus`).
|
|
345
340
|
useEffect(function () {
|
|
346
|
-
if (!isOpen)
|
|
347
|
-
// drop stale coords so the next open re-anchors from scratch
|
|
348
|
-
setMenuCoords(null);
|
|
349
|
-
return;
|
|
350
|
-
}
|
|
351
|
-
// initial calc on open; wait a tick to allow DOM to render
|
|
341
|
+
if (!isOpen || isSheet) return undefined;
|
|
352
342
|
var raf = requestAnimationFrame(function () {
|
|
353
343
|
var _searchRef$current, _listboxRef$current;
|
|
354
|
-
calculatePlacement();
|
|
355
|
-
// Move focus into the popup (WAI-ARIA collapsible listbox): the search
|
|
356
|
-
// box when searchable, otherwise the listbox itself. Both carry
|
|
357
|
-
// aria-activedescendant and the same key handling as the trigger.
|
|
358
344
|
var inMenu = menuRef.current && menuRef.current.contains(document.activeElement);
|
|
359
|
-
if (
|
|
360
|
-
});
|
|
361
|
-
// Reposition on resize/scroll while open (throttled by rAF). Use
|
|
362
|
-
// capture-phase scroll so we also catch any scrolling ancestor — the
|
|
363
|
-
// menu is portaled with position:fixed, so its coords must track the
|
|
364
|
-
// trigger as the page (or a scroll container) moves.
|
|
365
|
-
var rafId = null;
|
|
366
|
-
var onWindowChange = function onWindowChange() {
|
|
367
|
-
if (rafId) cancelAnimationFrame(rafId);
|
|
368
|
-
rafId = requestAnimationFrame(function () {
|
|
369
|
-
return calculatePlacement();
|
|
370
|
-
});
|
|
371
|
-
};
|
|
372
|
-
window.addEventListener("resize", onWindowChange, {
|
|
373
|
-
passive: true
|
|
374
|
-
});
|
|
375
|
-
window.addEventListener("scroll", onWindowChange, {
|
|
376
|
-
passive: true,
|
|
377
|
-
capture: true
|
|
345
|
+
if (autoFocusSearch) (_searchRef$current = searchRef.current) === null || _searchRef$current === void 0 || _searchRef$current.focus();else if (!inMenu) (_listboxRef$current = listboxRef.current) === null || _listboxRef$current === void 0 || _listboxRef$current.focus();
|
|
378
346
|
});
|
|
379
347
|
return function () {
|
|
380
|
-
cancelAnimationFrame(raf);
|
|
381
|
-
if (rafId) cancelAnimationFrame(rafId);
|
|
382
|
-
window.removeEventListener("resize", onWindowChange);
|
|
383
|
-
window.removeEventListener("scroll", onWindowChange, {
|
|
384
|
-
capture: true
|
|
385
|
-
});
|
|
348
|
+
return cancelAnimationFrame(raf);
|
|
386
349
|
};
|
|
387
|
-
|
|
388
|
-
}, [isOpen, searchable, filtered.length]);
|
|
350
|
+
}, [isOpen, isSheet, autoFocusSearch]);
|
|
389
351
|
// ===== handlers =====
|
|
390
352
|
// Close the popup. When focus is inside it (the portaled menu unmounts),
|
|
391
353
|
// hand focus back to the trigger so keyboard users are not dropped on
|
|
@@ -445,7 +407,7 @@ var Select = /*#__PURE__*/React.forwardRef(function (_ref, forwardedRef) {
|
|
|
445
407
|
setIsOpen(true);
|
|
446
408
|
setTimeout(function () {
|
|
447
409
|
var _searchRef$current2;
|
|
448
|
-
return
|
|
410
|
+
return autoFocusSearch ? (_searchRef$current2 = searchRef.current) === null || _searchRef$current2 === void 0 ? void 0 : _searchRef$current2.focus() : null;
|
|
449
411
|
}, 0);
|
|
450
412
|
}
|
|
451
413
|
return;
|
|
@@ -509,8 +471,9 @@ var Select = /*#__PURE__*/React.forwardRef(function (_ref, forwardedRef) {
|
|
|
509
471
|
break;
|
|
510
472
|
case "Tab":
|
|
511
473
|
// Leaving the (portaled) popup: close it and continue the tab order
|
|
512
|
-
// from the trigger rather than from the end of <body>.
|
|
513
|
-
|
|
474
|
+
// from the trigger rather than from the end of <body>. The sheet is
|
|
475
|
+
// modal and traps Tab (search -> options -> Done) instead.
|
|
476
|
+
if (!isSheet) closeMenu();
|
|
514
477
|
break;
|
|
515
478
|
}
|
|
516
479
|
};
|
|
@@ -545,14 +508,18 @@ var Select = /*#__PURE__*/React.forwardRef(function (_ref, forwardedRef) {
|
|
|
545
508
|
children: [jsx("span", {
|
|
546
509
|
className: "truncate max-w-[8rem]",
|
|
547
510
|
children: opt.label
|
|
548
|
-
}), jsx("
|
|
511
|
+
}), jsx("span", {
|
|
549
512
|
"aria-label": "Remove ".concat(opt.label),
|
|
513
|
+
// 44px hit area on coarse pointers; the negative margins
|
|
514
|
+
// keep the pill its compact size.
|
|
515
|
+
className: cn("inline-flex items-center justify-center cursor-pointer", TOUCH_TARGET, "[@media(pointer:coarse)]:-my-4 [@media(pointer:coarse)]:-mr-3"),
|
|
550
516
|
onClick: function onClick(e) {
|
|
551
517
|
e.stopPropagation();
|
|
552
518
|
handleSelect(opt);
|
|
553
519
|
},
|
|
554
520
|
children: jsx(X, {
|
|
555
|
-
className: "h-3 w-3"
|
|
521
|
+
className: "h-3 w-3",
|
|
522
|
+
"aria-hidden": "true"
|
|
556
523
|
})
|
|
557
524
|
})]
|
|
558
525
|
}, opt.value);
|
|
@@ -584,6 +551,7 @@ var Select = /*#__PURE__*/React.forwardRef(function (_ref, forwardedRef) {
|
|
|
584
551
|
var renderGroupedOptions = function renderGroupedOptions() {
|
|
585
552
|
// Running index into `navOptions` (render order, collapsed skipped).
|
|
586
553
|
var navIndex = -1;
|
|
554
|
+
var rowClass = ROW_CLASSES[isSheet ? "sheet" : "popover"];
|
|
587
555
|
return jsx("div", {
|
|
588
556
|
className: "divide-y",
|
|
589
557
|
children: filtered.map(function (group, gi) {
|
|
@@ -630,7 +598,7 @@ var Select = /*#__PURE__*/React.forwardRef(function (_ref, forwardedRef) {
|
|
|
630
598
|
onClick: function onClick() {
|
|
631
599
|
return handleSelect(option);
|
|
632
600
|
},
|
|
633
|
-
className: cn("flex items-center
|
|
601
|
+
className: cn("flex items-center hover:bg-gray-100 cursor-pointer", rowClass, {
|
|
634
602
|
"opacity-50 cursor-not-allowed": option.disabled,
|
|
635
603
|
"bg-primary-50 text-primary-600": focusedIndex === index
|
|
636
604
|
}),
|
|
@@ -668,7 +636,7 @@ var Select = /*#__PURE__*/React.forwardRef(function (_ref, forwardedRef) {
|
|
|
668
636
|
onClick: function onClick() {
|
|
669
637
|
return handleSelect(option);
|
|
670
638
|
},
|
|
671
|
-
className: cn("flex items-center
|
|
639
|
+
className: cn("flex items-center hover:bg-gray-100 cursor-pointer", ROW_CLASSES[isSheet ? "sheet" : "popover"], {
|
|
672
640
|
"opacity-50 cursor-not-allowed": option.disabled,
|
|
673
641
|
"bg-primary-50 text-primary-600": focusedIndex === index,
|
|
674
642
|
"bg-primary-100": multiSelect && selectedValues.includes(option.value)
|
|
@@ -699,6 +667,67 @@ var Select = /*#__PURE__*/React.forwardRef(function (_ref, forwardedRef) {
|
|
|
699
667
|
})]
|
|
700
668
|
});
|
|
701
669
|
};
|
|
670
|
+
var renderSearch = function renderSearch() {
|
|
671
|
+
return jsxs("div", {
|
|
672
|
+
className: "relative",
|
|
673
|
+
children: [selectAllOption && multiSelect && jsx("div", {
|
|
674
|
+
className: " text-sm flex items-center gap-2 border-b absolute left-1 top-1/2 h-4 w-4 -translate-y-1/2",
|
|
675
|
+
children: function () {
|
|
676
|
+
var allSelected = selectedValues.length === flatOptions.filter(function (o) {
|
|
677
|
+
return !o.disabled;
|
|
678
|
+
}).length;
|
|
679
|
+
return jsx("button", {
|
|
680
|
+
type: "button",
|
|
681
|
+
onClick: handleSelectAll,
|
|
682
|
+
"aria-label": allSelected ? "Deselect all" : "Select all",
|
|
683
|
+
"aria-pressed": allSelected,
|
|
684
|
+
className: "text-sm rounded focus:outline-none focus:ring-2 focus:ring-primary-500",
|
|
685
|
+
children: allSelected ? jsx(Check, {
|
|
686
|
+
className: "h-4 w-4 text-primary-600",
|
|
687
|
+
"aria-hidden": "true"
|
|
688
|
+
}) : jsx("div", {
|
|
689
|
+
className: "h-4 w-4 rounded border border-gray-300"
|
|
690
|
+
})
|
|
691
|
+
});
|
|
692
|
+
}()
|
|
693
|
+
}), jsx("input", {
|
|
694
|
+
ref: searchRef,
|
|
695
|
+
type: "text",
|
|
696
|
+
className: cn("w-full rounded-md border-0 bg-transparent pl-8 pr-3 py-1 focus:outline-none focus:ring-0",
|
|
697
|
+
// 16px in the sheet so iOS does not zoom the page on focus.
|
|
698
|
+
isSheet ? "min-h-11 text-base" : "text-sm", {
|
|
699
|
+
"pl-2": !(selectAllOption && multiSelect)
|
|
700
|
+
}),
|
|
701
|
+
placeholder: "Search...",
|
|
702
|
+
value: searchTerm,
|
|
703
|
+
onChange: function onChange(e) {
|
|
704
|
+
return setSearchTerm(e.target.value);
|
|
705
|
+
},
|
|
706
|
+
onKeyDown: handleKeyDown,
|
|
707
|
+
"aria-label": "Search options",
|
|
708
|
+
"aria-controls": listboxId,
|
|
709
|
+
"aria-activedescendant": activeDescendant,
|
|
710
|
+
"aria-autocomplete": "list"
|
|
711
|
+
}), jsx(Search, {
|
|
712
|
+
className: "absolute right-2 top-1/2 h-4 w-4 -translate-y-1/2 text-primary-400"
|
|
713
|
+
})]
|
|
714
|
+
});
|
|
715
|
+
};
|
|
716
|
+
var renderListbox = function renderListbox() {
|
|
717
|
+
return jsx("div", {
|
|
718
|
+
ref: listboxRef,
|
|
719
|
+
className: "overflow-auto focus:outline-none",
|
|
720
|
+
role: "listbox",
|
|
721
|
+
id: listboxId,
|
|
722
|
+
tabIndex: -1,
|
|
723
|
+
"aria-labelledby": label ? labelId : undefined,
|
|
724
|
+
"aria-label": label ? undefined : "Options",
|
|
725
|
+
"aria-multiselectable": multiSelect,
|
|
726
|
+
"aria-activedescendant": activeDescendant,
|
|
727
|
+
onKeyDown: handleKeyDown,
|
|
728
|
+
children: grouped ? renderGroupedOptions() : renderFlatOptions()
|
|
729
|
+
});
|
|
730
|
+
};
|
|
702
731
|
return jsxs("div", {
|
|
703
732
|
className: "w-full",
|
|
704
733
|
children: [label && jsxs("label", {
|
|
@@ -710,13 +739,13 @@ var Select = /*#__PURE__*/React.forwardRef(function (_ref, forwardedRef) {
|
|
|
710
739
|
children: "*"
|
|
711
740
|
})]
|
|
712
741
|
}), jsxs("div", {
|
|
713
|
-
ref:
|
|
742
|
+
ref: wrapperRef,
|
|
714
743
|
className: "relative",
|
|
715
744
|
children: [jsxs("button", _objectSpread2(_objectSpread2({
|
|
716
745
|
ref: triggerRef,
|
|
717
746
|
type: "button",
|
|
718
747
|
id: baseId,
|
|
719
|
-
"aria-haspopup": "listbox",
|
|
748
|
+
"aria-haspopup": isSheet ? "dialog" : "listbox",
|
|
720
749
|
"aria-expanded": isOpen,
|
|
721
750
|
"aria-controls": isOpen ? listboxId : undefined,
|
|
722
751
|
"aria-labelledby": label ? "".concat(labelId, " ").concat(valueId) : valueId,
|
|
@@ -734,7 +763,7 @@ var Select = /*#__PURE__*/React.forwardRef(function (_ref, forwardedRef) {
|
|
|
734
763
|
});
|
|
735
764
|
setTimeout(function () {
|
|
736
765
|
var _searchRef$current3;
|
|
737
|
-
return
|
|
766
|
+
return autoFocusSearch ? (_searchRef$current3 = searchRef.current) === null || _searchRef$current3 === void 0 ? void 0 : _searchRef$current3.focus() : null;
|
|
738
767
|
}, 0);
|
|
739
768
|
}
|
|
740
769
|
},
|
|
@@ -755,7 +784,7 @@ var Select = /*#__PURE__*/React.forwardRef(function (_ref, forwardedRef) {
|
|
|
755
784
|
type: "button",
|
|
756
785
|
onClick: handleClear,
|
|
757
786
|
"aria-label": "Clear selection",
|
|
758
|
-
className: "absolute right-8 top-1/2 -translate-y-1/2 rounded p-1 text-primary-500 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-primary-500",
|
|
787
|
+
className: cn("absolute right-8 top-1/2 -translate-y-1/2 inline-flex items-center justify-center rounded p-1 text-primary-500 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-primary-500", TOUCH_TARGET),
|
|
759
788
|
children: jsx(X, {
|
|
760
789
|
className: "h-4 w-4",
|
|
761
790
|
"aria-hidden": "true"
|
|
@@ -776,81 +805,47 @@ var Select = /*#__PURE__*/React.forwardRef(function (_ref, forwardedRef) {
|
|
|
776
805
|
type: "hidden",
|
|
777
806
|
name: name,
|
|
778
807
|
value: selectedValues !== null && selectedValues !== void 0 ? selectedValues : ""
|
|
779
|
-
})), isOpen && typeof document !== "undefined" && /*#__PURE__*/createPortal(jsxs("div", {
|
|
780
|
-
ref:
|
|
781
|
-
className:
|
|
808
|
+
})), isOpen && !isSheet && typeof document !== "undefined" && /*#__PURE__*/createPortal(jsxs("div", {
|
|
809
|
+
ref: popoverRef,
|
|
810
|
+
className: "fixed z-popover overflow-auto rounded-md border border-gray-300 bg-white shadow-lg",
|
|
782
811
|
role: "dialog",
|
|
783
|
-
"data-placement":
|
|
812
|
+
"data-placement": floatingPlacement.split("-")[0],
|
|
784
813
|
// Portaled to document.body with position:fixed so the menu
|
|
785
|
-
// escapes any ancestor overflow:hidden|auto|scroll.
|
|
786
|
-
//
|
|
787
|
-
// trigger;
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
}, menuMaxHeight ? {
|
|
793
|
-
maxHeight: "".concat(menuMaxHeight + 10, "px"),
|
|
794
|
-
overflow: "auto"
|
|
795
|
-
} : {}),
|
|
814
|
+
// escapes any ancestor overflow:hidden|auto|scroll. Anchored
|
|
815
|
+
// by useFloatingPosition (flip / shift; width matches the
|
|
816
|
+
// trigger; max-height follows the room left). Transparent
|
|
817
|
+
// until the first position so it never flashes at (0,0).
|
|
818
|
+
style: _objectSpread2(_objectSpread2({}, floatingStyles), isPositioned ? {} : {
|
|
819
|
+
opacity: 0
|
|
820
|
+
}),
|
|
796
821
|
children: [searchable && jsx("div", {
|
|
797
822
|
className: "border-b border-gray-200 p-2",
|
|
798
|
-
children:
|
|
799
|
-
|
|
800
|
-
children: [selectAllOption && multiSelect && jsx("div", {
|
|
801
|
-
className: " text-sm flex items-center gap-2 border-b absolute left-1 top-1/2 h-4 w-4 -translate-y-1/2",
|
|
802
|
-
children: function () {
|
|
803
|
-
var allSelected = selectedValues.length === flatOptions.filter(function (o) {
|
|
804
|
-
return !o.disabled;
|
|
805
|
-
}).length;
|
|
806
|
-
return jsx("button", {
|
|
807
|
-
type: "button",
|
|
808
|
-
onClick: handleSelectAll,
|
|
809
|
-
"aria-label": allSelected ? "Deselect all" : "Select all",
|
|
810
|
-
"aria-pressed": allSelected,
|
|
811
|
-
className: "text-sm rounded focus:outline-none focus:ring-2 focus:ring-primary-500",
|
|
812
|
-
children: allSelected ? jsx(Check, {
|
|
813
|
-
className: "h-4 w-4 text-primary-600",
|
|
814
|
-
"aria-hidden": "true"
|
|
815
|
-
}) : jsx("div", {
|
|
816
|
-
className: "h-4 w-4 rounded border border-gray-300"
|
|
817
|
-
})
|
|
818
|
-
});
|
|
819
|
-
}()
|
|
820
|
-
}), jsx("input", {
|
|
821
|
-
ref: searchRef,
|
|
822
|
-
type: "text",
|
|
823
|
-
className: cn("w-full rounded-md border-0 bg-transparent pl-8 pr-3 py-1 text-sm focus:outline-none focus:ring-0", {
|
|
824
|
-
"pl-2": !(selectAllOption && multiSelect)
|
|
825
|
-
}),
|
|
826
|
-
placeholder: "Search...",
|
|
827
|
-
value: searchTerm,
|
|
828
|
-
onChange: function onChange(e) {
|
|
829
|
-
return setSearchTerm(e.target.value);
|
|
830
|
-
},
|
|
831
|
-
onKeyDown: handleKeyDown,
|
|
832
|
-
"aria-label": "Search options",
|
|
833
|
-
"aria-controls": listboxId,
|
|
834
|
-
"aria-activedescendant": activeDescendant,
|
|
835
|
-
"aria-autocomplete": "list"
|
|
836
|
-
}), jsx(Search, {
|
|
837
|
-
className: "absolute right-2 top-1/2 h-4 w-4 -translate-y-1/2 text-primary-400"
|
|
838
|
-
})]
|
|
839
|
-
})
|
|
840
|
-
}), jsx("div", {
|
|
841
|
-
ref: listboxRef,
|
|
842
|
-
className: "overflow-auto focus:outline-none",
|
|
843
|
-
role: "listbox",
|
|
844
|
-
id: listboxId,
|
|
845
|
-
tabIndex: -1,
|
|
846
|
-
"aria-labelledby": label ? labelId : undefined,
|
|
847
|
-
"aria-label": label ? undefined : "Options",
|
|
848
|
-
"aria-multiselectable": multiSelect,
|
|
849
|
-
"aria-activedescendant": activeDescendant,
|
|
850
|
-
onKeyDown: handleKeyDown,
|
|
851
|
-
children: grouped ? renderGroupedOptions() : renderFlatOptions()
|
|
852
|
-
})]
|
|
823
|
+
children: renderSearch()
|
|
824
|
+
}), renderListbox()]
|
|
853
825
|
}), document.body)]
|
|
826
|
+
}), isSheet && jsxs(Sheet, {
|
|
827
|
+
ref: menuRef,
|
|
828
|
+
open: isOpen,
|
|
829
|
+
onClose: closeMenu,
|
|
830
|
+
placement: "bottom",
|
|
831
|
+
title: label || placeholder,
|
|
832
|
+
initialFocus: listboxRef,
|
|
833
|
+
"data-select-sheet": "",
|
|
834
|
+
children: [searchable && jsx(Sheet.Header, {
|
|
835
|
+
className: "py-2",
|
|
836
|
+
children: renderSearch()
|
|
837
|
+
}), jsx(Sheet.Body, {
|
|
838
|
+
className: "px-0 py-1",
|
|
839
|
+
children: renderListbox()
|
|
840
|
+
}), multiSelect && jsx(Sheet.Footer, {
|
|
841
|
+
children: jsx(Button, {
|
|
842
|
+
type: "button",
|
|
843
|
+
size: "lg",
|
|
844
|
+
className: "w-full",
|
|
845
|
+
onClick: closeMenu,
|
|
846
|
+
children: "Done"
|
|
847
|
+
})
|
|
848
|
+
})]
|
|
854
849
|
}), error ? jsx("p", {
|
|
855
850
|
id: errorId,
|
|
856
851
|
className: "mt-1 text-sm text-error-500",
|