@dreamtree-org/twreact-ui 1.3.8 → 1.3.10
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/DESIGN.md +11 -3
- package/README.md +9 -1
- package/ai-skills/dreamtree-ui.md +2 -2
- package/dist/ai-schema.json +19 -8
- package/dist/charts/index.js +69 -24
- package/dist/components/blocks/AppShell.d.ts.map +1 -1
- package/dist/components/charts/chartTheme.d.ts +2 -5
- package/dist/components/charts/chartTheme.d.ts.map +1 -1
- package/dist/components/core/Accordion.d.ts.map +1 -1
- package/dist/components/core/ColorPicker.d.ts.map +1 -1
- package/dist/components/core/DatePicker.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/PriceRangePicker.d.ts +0 -29
- package/dist/components/core/PriceRangePicker.d.ts.map +1 -1
- package/dist/components/core/Radio.d.ts.map +1 -1
- package/dist/components/core/Rate.d.ts.map +1 -1
- package/dist/components/core/RoundedTag.d.ts.map +1 -1
- package/dist/components/core/Table/ColumnMenu.d.ts.map +1 -1
- package/dist/components/core/Table/Pagination.d.ts.map +1 -1
- package/dist/components/core/Table/Table.d.ts.map +1 -1
- package/dist/components/core/Table/TableRow.d.ts.map +1 -1
- package/dist/components/core/Table/helpers.d.ts +2 -0
- package/dist/components/core/Table/helpers.d.ts.map +1 -1
- package/dist/components/core/Table/useClickOutside.d.ts +5 -2
- package/dist/components/core/Table/useClickOutside.d.ts.map +1 -1
- package/dist/components/core/Tabs.d.ts.map +1 -1
- package/dist/components/core/TextToSpeech.d.ts +2 -7
- package/dist/components/core/TextToSpeech.d.ts.map +1 -1
- package/dist/components/core/Tooltip.d.ts.map +1 -1
- package/dist/components/feedback/Alert.d.ts.map +1 -1
- package/dist/components/feedback/Dialog.d.ts.map +1 -1
- package/dist/components/feedback/Toast.d.ts.map +1 -1
- package/dist/components/navigation/Sidebar.d.ts.map +1 -1
- package/dist/components/utility/Avatar.d.ts.map +1 -1
- package/dist/components/utility/Carousel.d.ts.map +1 -1
- package/dist/components/utility/Portal.d.ts +6 -0
- package/dist/components/utility/Portal.d.ts.map +1 -0
- package/dist/esm/components/blocks/AppShell.js +24 -18
- package/dist/esm/components/charts/CartesianChart.js +2 -1
- package/dist/esm/components/charts/DonutChart.js +2 -1
- package/dist/esm/components/charts/Sparkline.js +2 -1
- package/dist/esm/components/charts/chartTheme.js +1 -22
- package/dist/esm/components/core/Accordion.js +40 -9
- package/dist/esm/components/core/Button.js +8 -8
- package/dist/esm/components/core/ColorPicker.js +19 -7
- package/dist/esm/components/core/DatePicker.js +97 -58
- package/dist/esm/components/core/Form.js +35 -8
- package/dist/esm/components/core/Input.js +3 -1
- package/dist/esm/components/core/PriceRangePicker.js +205 -218
- package/dist/esm/components/core/Radio.js +36 -6
- package/dist/esm/components/core/Rate.js +41 -50
- package/dist/esm/components/core/RoundedTag.js +25 -8
- package/dist/esm/components/core/Table/ColumnMenu.js +2 -4
- package/dist/esm/components/core/Table/Pagination.js +15 -18
- package/dist/esm/components/core/Table/Table.js +11 -5
- package/dist/esm/components/core/Table/TableRow.js +2 -2
- package/dist/esm/components/core/Table/helpers.js +33 -1
- package/dist/esm/components/core/Tabs.js +6 -5
- package/dist/esm/components/core/TextToSpeech.js +121 -28
- package/dist/esm/components/core/Tooltip.js +100 -28
- package/dist/esm/components/feedback/Alert.js +21 -11
- package/dist/esm/components/feedback/Dialog.js +44 -100
- package/dist/esm/components/feedback/Toast.js +24 -5
- package/dist/esm/components/navigation/Sidebar.js +13 -61
- package/dist/esm/components/utility/Avatar.js +23 -10
- package/dist/esm/components/utility/Carousel.js +34 -17
- package/dist/esm/components/utility/Pagination.js +2 -2
- package/dist/esm/hooks/useClickOutside.js +51 -0
- package/dist/esm/hooks/useFocusTrap.js +10 -2
- package/dist/esm/hooks/useMediaQuery.js +61 -0
- package/dist/esm/hooks/useReducedMotion.js +17 -0
- package/dist/esm/hooks/useScrollLock.js +76 -9
- package/dist/esm/hooks/useTheme.js +6 -1
- package/dist/esm/utils/position.js +6 -0
- package/dist/hooks/useClickOutside.d.ts +14 -0
- package/dist/hooks/useClickOutside.d.ts.map +1 -0
- package/dist/hooks/useFloatingPosition.d.ts +37 -0
- package/dist/hooks/useFloatingPosition.d.ts.map +1 -0
- package/dist/hooks/useFocusTrap.d.ts.map +1 -1
- package/dist/hooks/useMediaQuery.d.ts +22 -0
- package/dist/hooks/useMediaQuery.d.ts.map +1 -0
- package/dist/hooks/usePresence.d.ts +16 -0
- package/dist/hooks/usePresence.d.ts.map +1 -0
- package/dist/hooks/usePresentation.d.ts +9 -0
- package/dist/hooks/usePresentation.d.ts.map +1 -0
- package/dist/hooks/useReducedMotion.d.ts +10 -0
- package/dist/hooks/useReducedMotion.d.ts.map +1 -0
- package/dist/hooks/useScrollLock.d.ts +11 -1
- package/dist/hooks/useScrollLock.d.ts.map +1 -1
- package/dist/hooks/useTheme.d.ts.map +1 -1
- package/dist/index.css +1 -1
- package/dist/index.esm.css +1 -1
- package/dist/index.esm.js +1719 -1201
- package/dist/index.js +1718 -1200
- package/dist/tokens/tailwind3-preset.cjs +101 -8
- package/dist/tokens/theme.css +27 -0
- package/dist/tokens/tokens.json +175 -0
- package/dist/tokens/vars.css +2 -0
- package/dist/types/index.d.ts +8 -2
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/position.d.ts.map +1 -1
- package/dist/utils/touchTarget.d.ts +1 -0
- package/dist/utils/touchTarget.d.ts.map +1 -1
- package/mcp/catalog.snapshot.json +83 -53
- package/package.json +2 -1
- package/tokens/lib.cjs +35 -0
- package/tokens/tokens.json +58 -2
- package/dist/esm/components/core/Table/useClickOutside.js +0 -26
|
@@ -1,38 +1,21 @@
|
|
|
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, { useRef, useEffect } from 'react';
|
|
4
|
+
import React, { useRef, useId, 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
|
+
import { useFocusTrap } from '../../hooks/useFocusTrap.js';
|
|
10
|
+
import { useScrollLock } from '../../hooks/useScrollLock.js';
|
|
11
|
+
import { setRefs } from '../../utils/position.js';
|
|
9
12
|
import { TOUCH_TARGET } from '../../utils/touchTarget.js';
|
|
10
13
|
|
|
11
14
|
var _excluded = ["isOpen", "onClose", "title", "children", "size", "dismissible", "showCloseButton", "footer", "className", "onOpen"],
|
|
12
15
|
_excluded2 = ["children", "className"],
|
|
13
16
|
_excluded3 = ["children", "className"],
|
|
14
17
|
_excluded4 = ["children", "className"];
|
|
15
|
-
|
|
16
|
-
// pick the first element to focus when the dialog opens (WAI-ARIA APG dialog).
|
|
17
|
-
var FOCUSABLE_SELECTOR = ['a[href]', 'button:not([disabled])', 'textarea:not([disabled])', 'input:not([disabled])', 'select:not([disabled])', '[tabindex]:not([tabindex="-1"])'].join(',');
|
|
18
|
-
var getFocusable = function getFocusable(root) {
|
|
19
|
-
return root ? Array.from(root.querySelectorAll(FOCUSABLE_SELECTOR)).filter(function (el) {
|
|
20
|
-
return el.getAttribute('aria-hidden') !== 'true';
|
|
21
|
-
}) : [];
|
|
22
|
-
};
|
|
23
|
-
// Assign one node to several refs (callback or object). Lets the dialog keep
|
|
24
|
-
// its internal `dialogRef` for the focus trap while still exposing the panel
|
|
25
|
-
// node to a consumer-supplied `ref` (issue #56).
|
|
26
|
-
var setRefs = function setRefs(node) {
|
|
27
|
-
for (var _len = arguments.length, refs = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
|
|
28
|
-
refs[_key - 1] = arguments[_key];
|
|
29
|
-
}
|
|
30
|
-
refs.forEach(function (ref) {
|
|
31
|
-
if (!ref) return;
|
|
32
|
-
if (typeof ref === 'function') ref(node);else ref.current = node;
|
|
33
|
-
});
|
|
34
|
-
};
|
|
35
|
-
var Dialog = /*#__PURE__*/React.forwardRef(function (_ref, _ref3) {
|
|
18
|
+
var Dialog = /*#__PURE__*/React.forwardRef(function (_ref, _ref2) {
|
|
36
19
|
var isOpen = _ref.isOpen,
|
|
37
20
|
onClose = _ref.onClose,
|
|
38
21
|
title = _ref.title,
|
|
@@ -48,66 +31,27 @@ var Dialog = /*#__PURE__*/React.forwardRef(function (_ref, _ref3) {
|
|
|
48
31
|
onOpen = _ref.onOpen,
|
|
49
32
|
props = _objectWithoutProperties(_ref, _excluded);
|
|
50
33
|
var dialogRef = useRef(null);
|
|
51
|
-
//
|
|
52
|
-
var
|
|
34
|
+
// Unique per instance so two open dialogs never share an aria-labelledby target.
|
|
35
|
+
var titleId = "dialog-title-".concat(useId());
|
|
53
36
|
// Latest onOpen without re-running the open effect when the prop identity changes.
|
|
54
37
|
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
|
+
}
|
|
48
|
+
});
|
|
55
49
|
useEffect(function () {
|
|
56
|
-
var
|
|
57
|
-
if (
|
|
58
|
-
|
|
59
|
-
lastFocusedRef.current = document.activeElement;
|
|
60
|
-
document.body.style.overflow = 'hidden';
|
|
61
|
-
// Move focus into the panel: first focusable descendant, else the panel itself.
|
|
62
|
-
var panel = dialogRef.current;
|
|
63
|
-
var focusables = getFocusable(panel);
|
|
64
|
-
(_ref2 = (_focusables$ = focusables[0]) !== null && _focusables$ !== void 0 ? _focusables$ : panel) === null || _ref2 === void 0 || _ref2.focus();
|
|
65
|
-
(_onOpenRef$current = onOpenRef.current) === null || _onOpenRef$current === void 0 || _onOpenRef$current.call(onOpenRef, {
|
|
66
|
-
dialogRef: panel
|
|
50
|
+
var _onOpenRef$current;
|
|
51
|
+
if (isOpen) (_onOpenRef$current = onOpenRef.current) === null || _onOpenRef$current === void 0 || _onOpenRef$current.call(onOpenRef, {
|
|
52
|
+
dialogRef: dialogRef.current
|
|
67
53
|
});
|
|
68
|
-
return function () {
|
|
69
|
-
var _lastFocusedRef$curre, _lastFocusedRef$curre2;
|
|
70
|
-
document.body.style.overflow = 'unset';
|
|
71
|
-
// Restore focus to whatever opened the dialog (APG focus-restore requirement).
|
|
72
|
-
(_lastFocusedRef$curre = lastFocusedRef.current) === null || _lastFocusedRef$curre === void 0 || (_lastFocusedRef$curre2 = _lastFocusedRef$curre.focus) === null || _lastFocusedRef$curre2 === void 0 || _lastFocusedRef$curre2.call(_lastFocusedRef$curre);
|
|
73
|
-
};
|
|
74
54
|
}, [isOpen, onOpenRef]);
|
|
75
|
-
// Escape-to-close + Tab focus trap. An aria-modal element must keep keyboard
|
|
76
|
-
// focus inside the panel; without this, Tab walks into the page behind it.
|
|
77
|
-
useEffect(function () {
|
|
78
|
-
if (!isOpen) return undefined;
|
|
79
|
-
var handleKeyDown = function handleKeyDown(e) {
|
|
80
|
-
if (e.key === 'Escape') {
|
|
81
|
-
if (dismissible) onClose === null || onClose === void 0 || onClose();
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
if (e.key !== 'Tab') return;
|
|
85
|
-
var panel = dialogRef.current;
|
|
86
|
-
if (!panel) return;
|
|
87
|
-
var focusables = getFocusable(panel);
|
|
88
|
-
if (focusables.length === 0) {
|
|
89
|
-
// Nothing focusable inside — pin focus to the panel.
|
|
90
|
-
e.preventDefault();
|
|
91
|
-
panel.focus();
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
var first = focusables[0];
|
|
95
|
-
var last = focusables[focusables.length - 1];
|
|
96
|
-
var active = document.activeElement;
|
|
97
|
-
var outside = !panel.contains(active) || active === panel;
|
|
98
|
-
if (e.shiftKey && (active === first || outside)) {
|
|
99
|
-
e.preventDefault();
|
|
100
|
-
last.focus();
|
|
101
|
-
} else if (!e.shiftKey && (active === last || outside)) {
|
|
102
|
-
e.preventDefault();
|
|
103
|
-
first.focus();
|
|
104
|
-
}
|
|
105
|
-
};
|
|
106
|
-
document.addEventListener('keydown', handleKeyDown);
|
|
107
|
-
return function () {
|
|
108
|
-
return document.removeEventListener('keydown', handleKeyDown);
|
|
109
|
-
};
|
|
110
|
-
}, [isOpen, dismissible, onClose]);
|
|
111
55
|
if (!isOpen) return null;
|
|
112
56
|
var sizeClasses = {
|
|
113
57
|
sm: 'max-w-md',
|
|
@@ -122,27 +66,27 @@ var Dialog = /*#__PURE__*/React.forwardRef(function (_ref, _ref3) {
|
|
|
122
66
|
}
|
|
123
67
|
};
|
|
124
68
|
return /*#__PURE__*/createPortal(jsx("div", {
|
|
125
|
-
className: "fixed inset-0 z-50 flex items-center justify-center bg-black
|
|
69
|
+
className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4",
|
|
126
70
|
onClick: handleBackdropClick,
|
|
127
71
|
children: jsxs("div", _objectSpread2(_objectSpread2({
|
|
128
72
|
ref: function ref(node) {
|
|
129
|
-
return setRefs(node, dialogRef,
|
|
73
|
+
return setRefs(node, dialogRef, _ref2);
|
|
130
74
|
},
|
|
131
|
-
className: cn('w-full rounded-lg bg-
|
|
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),
|
|
132
76
|
tabIndex: -1,
|
|
133
77
|
role: "dialog",
|
|
134
78
|
"aria-modal": "true",
|
|
135
|
-
"aria-labelledby": title ?
|
|
79
|
+
"aria-labelledby": title ? titleId : undefined
|
|
136
80
|
}, props), {}, {
|
|
137
81
|
children: [(title || showCloseButton) && jsxs("div", {
|
|
138
|
-
className: "flex items-center justify-between border-b border-
|
|
82
|
+
className: "flex items-center justify-between border-b border-line px-6 py-4",
|
|
139
83
|
children: [title && jsx("h2", {
|
|
140
|
-
id:
|
|
141
|
-
className: "text-lg font-semibold text-
|
|
84
|
+
id: titleId,
|
|
85
|
+
className: "text-lg font-semibold text-foreground",
|
|
142
86
|
children: title
|
|
143
87
|
}), showCloseButton && jsx("button", {
|
|
144
88
|
type: "button",
|
|
145
|
-
className: cn('inline-flex items-center justify-center rounded-md p-1 text-
|
|
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),
|
|
146
90
|
onClick: onClose,
|
|
147
91
|
"aria-label": "Close dialog",
|
|
148
92
|
children: jsx(X, {
|
|
@@ -154,7 +98,7 @@ var Dialog = /*#__PURE__*/React.forwardRef(function (_ref, _ref3) {
|
|
|
154
98
|
className: "px-6 py-4",
|
|
155
99
|
children: children
|
|
156
100
|
}), footer && jsx("div", {
|
|
157
|
-
className: "flex items-center justify-end gap-3 border-t border-
|
|
101
|
+
className: "flex items-center justify-end gap-3 border-t border-line px-6 py-4",
|
|
158
102
|
children: footer
|
|
159
103
|
})]
|
|
160
104
|
}))
|
|
@@ -162,32 +106,32 @@ var Dialog = /*#__PURE__*/React.forwardRef(function (_ref, _ref3) {
|
|
|
162
106
|
});
|
|
163
107
|
Dialog.displayName = 'Dialog';
|
|
164
108
|
// Dialog components for composition
|
|
165
|
-
var DialogHeader = function DialogHeader(
|
|
166
|
-
var children =
|
|
167
|
-
className =
|
|
168
|
-
props = _objectWithoutProperties(
|
|
109
|
+
var DialogHeader = function DialogHeader(_ref3) {
|
|
110
|
+
var children = _ref3.children,
|
|
111
|
+
className = _ref3.className,
|
|
112
|
+
props = _objectWithoutProperties(_ref3, _excluded2);
|
|
169
113
|
return jsx("div", _objectSpread2(_objectSpread2({
|
|
170
|
-
className: cn('border-b border-
|
|
114
|
+
className: cn('border-b border-line px-6 py-4', className)
|
|
171
115
|
}, props), {}, {
|
|
172
116
|
children: children
|
|
173
117
|
}));
|
|
174
118
|
};
|
|
175
|
-
var DialogBody = function DialogBody(
|
|
176
|
-
var children =
|
|
177
|
-
className =
|
|
178
|
-
props = _objectWithoutProperties(
|
|
119
|
+
var DialogBody = function DialogBody(_ref4) {
|
|
120
|
+
var children = _ref4.children,
|
|
121
|
+
className = _ref4.className,
|
|
122
|
+
props = _objectWithoutProperties(_ref4, _excluded3);
|
|
179
123
|
return jsx("div", _objectSpread2(_objectSpread2({
|
|
180
124
|
className: cn('px-6 py-4', className)
|
|
181
125
|
}, props), {}, {
|
|
182
126
|
children: children
|
|
183
127
|
}));
|
|
184
128
|
};
|
|
185
|
-
var DialogFooter = function DialogFooter(
|
|
186
|
-
var children =
|
|
187
|
-
className =
|
|
188
|
-
props = _objectWithoutProperties(
|
|
129
|
+
var DialogFooter = function DialogFooter(_ref5) {
|
|
130
|
+
var children = _ref5.children,
|
|
131
|
+
className = _ref5.className,
|
|
132
|
+
props = _objectWithoutProperties(_ref5, _excluded4);
|
|
189
133
|
return jsx("div", _objectSpread2(_objectSpread2({
|
|
190
|
-
className: cn('flex items-center justify-end gap-3 border-t border-
|
|
134
|
+
className: cn('flex items-center justify-end gap-3 border-t border-line px-6 py-4', className)
|
|
191
135
|
}, props), {}, {
|
|
192
136
|
children: children
|
|
193
137
|
}));
|
|
@@ -5,8 +5,23 @@ import { forwardRef, useState, useRef, useCallback, useEffect, useContext, creat
|
|
|
5
5
|
import { createPortal } from 'react-dom';
|
|
6
6
|
import { cn } from '../../utils/cn.js';
|
|
7
7
|
import { Bell, Info, AlertTriangle, XCircle, CheckCircle2, X } from 'lucide-react';
|
|
8
|
+
import { TOUCH_TARGET } from '../../utils/touchTarget.js';
|
|
8
9
|
|
|
9
10
|
var _excluded = ["id", "title", "message", "description", "type", "duration", "onClose", "position", "statusIcon", "actions", "color", "footerNote", "showFooterNote", "portal", "className"];
|
|
11
|
+
// Errors interrupt (role="alert", assertive); everything else waits for the
|
|
12
|
+
// screen reader to finish (role="status", polite). Consumers can still pass
|
|
13
|
+
// their own role / aria-live, which win because ...props spreads last.
|
|
14
|
+
var liveRegionProps = function liveRegionProps(type) {
|
|
15
|
+
return type === 'error' ? {
|
|
16
|
+
role: 'alert',
|
|
17
|
+
'aria-live': 'assertive',
|
|
18
|
+
'aria-atomic': 'true'
|
|
19
|
+
} : {
|
|
20
|
+
role: 'status',
|
|
21
|
+
'aria-live': 'polite',
|
|
22
|
+
'aria-atomic': 'true'
|
|
23
|
+
};
|
|
24
|
+
};
|
|
10
25
|
var STATUS_CONFIG = {
|
|
11
26
|
success: {
|
|
12
27
|
icon: CheckCircle2,
|
|
@@ -147,7 +162,7 @@ var Toast = /*#__PURE__*/forwardRef(function (_ref, ref) {
|
|
|
147
162
|
var statusStyle = STATUS_CONFIG[type] || STATUS_CONFIG.info || STATUS_CONFIG["default"];
|
|
148
163
|
var StatusIconComponent = statusStyle.icon;
|
|
149
164
|
var bodyText = description || message;
|
|
150
|
-
var toastContent = jsxs("div", _objectSpread2(_objectSpread2({
|
|
165
|
+
var toastContent = jsxs("div", _objectSpread2(_objectSpread2(_objectSpread2({
|
|
151
166
|
ref: ref,
|
|
152
167
|
onMouseEnter: function onMouseEnter() {
|
|
153
168
|
return setIsPaused(true);
|
|
@@ -155,14 +170,15 @@ var Toast = /*#__PURE__*/forwardRef(function (_ref, ref) {
|
|
|
155
170
|
onMouseLeave: function onMouseLeave() {
|
|
156
171
|
return setIsPaused(false);
|
|
157
172
|
},
|
|
158
|
-
className: cn('relative overflow-hidden rounded-xl border border-slate-200/90 bg-white/95 shadow-xl backdrop-blur-md transition-all duration-300', 'dark:border-slate-800/80 dark:bg-[#18181b] dark:text-slate-100 dark:shadow-2xl', 'max-w-md w-full', isLeaving ? 'opacity-0 scale-95 translate-y-1' : 'animate-slide-up opacity-100 scale-100', portal ? ['fixed z-50', STANDALONE_POSITION_CLASSES[position] || STANDALONE_POSITION_CLASSES['top-right']] : '', className)
|
|
159
|
-
}, props), {}, {
|
|
173
|
+
className: cn('relative overflow-hidden rounded-xl border border-slate-200/90 bg-white/95 shadow-xl backdrop-blur-md transition-all duration-300', 'dark:border-slate-800/80 dark:bg-[#18181b] dark:text-slate-100 dark:shadow-2xl', 'max-w-md w-full motion-reduce:transition-none', isLeaving ? 'opacity-0 scale-95 translate-y-1' : 'animate-slide-up motion-reduce:animate-none opacity-100 scale-100', portal ? ['fixed z-50', STANDALONE_POSITION_CLASSES[position] || STANDALONE_POSITION_CLASSES['top-right']] : '', className)
|
|
174
|
+
}, liveRegionProps(type)), props), {}, {
|
|
160
175
|
children: [jsx("div", {
|
|
161
176
|
className: "p-4",
|
|
162
177
|
children: jsxs("div", {
|
|
163
178
|
className: "flex items-start gap-3 justify-between",
|
|
164
179
|
children: [jsx("span", {
|
|
165
180
|
className: "flex-shrink-0 mt-0.5",
|
|
181
|
+
"aria-hidden": "true",
|
|
166
182
|
children: statusIcon ? statusIcon : jsx(StatusIconComponent, {
|
|
167
183
|
className: cn('h-5 w-5', statusStyle.iconClass)
|
|
168
184
|
})
|
|
@@ -194,10 +210,11 @@ var Toast = /*#__PURE__*/forwardRef(function (_ref, ref) {
|
|
|
194
210
|
}), jsx("button", {
|
|
195
211
|
type: "button",
|
|
196
212
|
"aria-label": "Close notification",
|
|
197
|
-
className:
|
|
213
|
+
className: cn('inline-flex flex-shrink-0 items-center justify-center rounded-lg p-1 text-slate-400 hover:text-slate-600 dark:hover:text-slate-200 hover:bg-slate-100 dark:hover:bg-zinc-800/70 transition-colors motion-reduce:transition-none outline-none focus-visible:ring-2 focus-visible:ring-primary-500', TOUCH_TARGET),
|
|
198
214
|
onClick: handleClose,
|
|
199
215
|
children: jsx(X, {
|
|
200
|
-
className: "h-4 w-4"
|
|
216
|
+
className: "h-4 w-4",
|
|
217
|
+
"aria-hidden": "true"
|
|
201
218
|
})
|
|
202
219
|
})]
|
|
203
220
|
})
|
|
@@ -250,6 +267,8 @@ var ToastStack = function ToastStack(_ref2) {
|
|
|
250
267
|
}
|
|
251
268
|
var posClass = POSITION_CLASSES[position] || POSITION_CLASSES['top-right'];
|
|
252
269
|
return /*#__PURE__*/createPortal(jsx("div", {
|
|
270
|
+
role: "region",
|
|
271
|
+
"aria-label": "Notifications",
|
|
253
272
|
className: cn('fixed z-50 flex flex-col gap-3 max-h-[calc(100vh-2rem)] overflow-y-auto overflow-x-hidden p-2 pointer-events-none', '[scrollbar-width:none] [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden', posClass, className),
|
|
254
273
|
children: toasts.map(function (t) {
|
|
255
274
|
return jsx("div", {
|
|
@@ -6,6 +6,8 @@ import { cn } from '../../utils/cn.js';
|
|
|
6
6
|
import { ChevronRight, ChevronLeft, ChevronDown } from 'lucide-react';
|
|
7
7
|
import Tooltip from '../core/Tooltip.js';
|
|
8
8
|
import { useLatestRef } from '../../hooks/useLatestRef.js';
|
|
9
|
+
import { useFocusTrap } from '../../hooks/useFocusTrap.js';
|
|
10
|
+
import { useScrollLock } from '../../hooks/useScrollLock.js';
|
|
9
11
|
|
|
10
12
|
var _excluded = ["items", "collapsed", "onToggle", "className", "containerClassName", "stickyOffset", "logo", "footer", "user", "onUserClick", "drawerPosition", "isMobileOpen", "setIsMobileOpen", "showCollapsedTooltips", "tooltipOptions", "linkAs", "expandedIds", "defaultExpandedIds", "onExpandedChange"];
|
|
11
13
|
// useLayoutEffect warns during SSR; fall back to useEffect on the server.
|
|
@@ -15,13 +17,6 @@ var useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect
|
|
|
15
17
|
var isModifiedClick = function isModifiedClick(e) {
|
|
16
18
|
return Boolean(e && (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey || typeof e.button === "number" && e.button !== 0));
|
|
17
19
|
};
|
|
18
|
-
// Focus trap for the mobile drawer (same selector as feedback/Dialog).
|
|
19
|
-
var FOCUSABLE_SELECTOR = ["a[href]", "button:not([disabled])", "textarea:not([disabled])", "input:not([disabled])", "select:not([disabled])", '[tabindex]:not([tabindex="-1"])'].join(",");
|
|
20
|
-
var getFocusable = function getFocusable(root) {
|
|
21
|
-
return root ? Array.from(root.querySelectorAll(FOCUSABLE_SELECTOR)).filter(function (el) {
|
|
22
|
-
return el.getAttribute("aria-hidden") !== "true";
|
|
23
|
-
}) : [];
|
|
24
|
-
};
|
|
25
20
|
var hasChildItems = function hasChildItems(item) {
|
|
26
21
|
return Array.isArray(item.children) && item.children.length > 0;
|
|
27
22
|
};
|
|
@@ -171,12 +166,8 @@ var Sidebar = /*#__PURE__*/React.forwardRef(function (_ref, ref) {
|
|
|
171
166
|
var setIsMobileOpenRef = useLatestRef(setIsMobileOpen);
|
|
172
167
|
var mobileOpen = isMobileOpen !== null && isMobileOpen !== void 0 ? isMobileOpen : false;
|
|
173
168
|
var effectiveCollapsed = mobileOpen ? false : collapsed;
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
return function () {
|
|
177
|
-
document.body.style.overflow = "";
|
|
178
|
-
};
|
|
179
|
-
}, [mobileOpen]);
|
|
169
|
+
// Ref-counted, so a Dialog/Drawer stacked on top never unlocks the page early.
|
|
170
|
+
useScrollLock(mobileOpen);
|
|
180
171
|
// The drawer stays mounted (it slides in/out), so when it is shut it must
|
|
181
172
|
// leave the tab order and the a11y tree. `inert` is set imperatively: React
|
|
182
173
|
// 18 does not know the attribute and React 19 treats `inert=""` as false.
|
|
@@ -185,54 +176,15 @@ var Sidebar = /*#__PURE__*/React.forwardRef(function (_ref, ref) {
|
|
|
185
176
|
if (!drawer) return;
|
|
186
177
|
if (mobileOpen) drawer.removeAttribute("inert");else drawer.setAttribute("inert", "");
|
|
187
178
|
}, [mobileOpen]);
|
|
188
|
-
// Open drawer = modal dialog: initial focus inside, Tab trapped
|
|
189
|
-
// closes, focus returns to the opener
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
var handleKeyDown = function handleKeyDown(e) {
|
|
198
|
-
if (e.key === "Escape") {
|
|
199
|
-
var _setIsMobileOpenRef$c;
|
|
200
|
-
(_setIsMobileOpenRef$c = setIsMobileOpenRef.current) === null || _setIsMobileOpenRef$c === void 0 || _setIsMobileOpenRef$c.call(setIsMobileOpenRef, false);
|
|
201
|
-
return;
|
|
202
|
-
}
|
|
203
|
-
if (e.key !== "Tab" || !drawer) return;
|
|
204
|
-
// md+ viewports hide the drawer (md:hidden): nothing to trap.
|
|
205
|
-
if (window.getComputedStyle(drawer).display === "none") return;
|
|
206
|
-
var nodes = getFocusable(drawer);
|
|
207
|
-
if (nodes.length === 0) {
|
|
208
|
-
e.preventDefault();
|
|
209
|
-
drawer.focus();
|
|
210
|
-
return;
|
|
211
|
-
}
|
|
212
|
-
var first = nodes[0];
|
|
213
|
-
var last = nodes[nodes.length - 1];
|
|
214
|
-
var active = document.activeElement;
|
|
215
|
-
var outside = !drawer.contains(active) || active === drawer;
|
|
216
|
-
if (e.shiftKey && (active === first || outside)) {
|
|
217
|
-
e.preventDefault();
|
|
218
|
-
last.focus();
|
|
219
|
-
} else if (!e.shiftKey && (active === last || outside)) {
|
|
220
|
-
e.preventDefault();
|
|
221
|
-
first.focus();
|
|
222
|
-
}
|
|
223
|
-
};
|
|
224
|
-
document.addEventListener("keydown", handleKeyDown);
|
|
225
|
-
return function () {
|
|
226
|
-
document.removeEventListener("keydown", handleKeyDown);
|
|
227
|
-
// Restore focus only if it is still in the drawer (or was dropped to
|
|
228
|
-
// <body> when the drawer went inert) — never steal it from elsewhere.
|
|
229
|
-
var active = document.activeElement;
|
|
230
|
-
var focusLost = !active || active === document.body || (drawer === null || drawer === void 0 ? void 0 : drawer.contains(active));
|
|
231
|
-
if (focusLost && previouslyFocused && previouslyFocused !== document.body && typeof previouslyFocused.focus === "function" && document.contains(previouslyFocused)) {
|
|
232
|
-
previouslyFocused.focus();
|
|
233
|
-
}
|
|
234
|
-
};
|
|
235
|
-
}, [mobileOpen, setIsMobileOpenRef]);
|
|
179
|
+
// Open drawer = modal dialog: initial focus inside, Tab trapped (skipped
|
|
180
|
+
// while md:hidden hides it), Escape closes, focus returns to the opener.
|
|
181
|
+
useFocusTrap(drawerRef, {
|
|
182
|
+
active: mobileOpen,
|
|
183
|
+
onEscape: function onEscape() {
|
|
184
|
+
var _setIsMobileOpenRef$c;
|
|
185
|
+
return (_setIsMobileOpenRef$c = setIsMobileOpenRef.current) === null || _setIsMobileOpenRef$c === void 0 ? void 0 : _setIsMobileOpenRef$c.call(setIsMobileOpenRef, false);
|
|
186
|
+
}
|
|
187
|
+
});
|
|
236
188
|
var closeMobile = function closeMobile() {
|
|
237
189
|
if (mobileOpen && setIsMobileOpen) setIsMobileOpen(false);
|
|
238
190
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { objectWithoutProperties as _objectWithoutProperties, objectSpread2 as _objectSpread2 } from '../../_virtual/_rollupPluginBabelHelpers.js';
|
|
2
|
+
import { objectWithoutProperties as _objectWithoutProperties, slicedToArray as _slicedToArray, objectSpread2 as _objectSpread2 } from '../../_virtual/_rollupPluginBabelHelpers.js';
|
|
3
3
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
4
4
|
import React from 'react';
|
|
5
5
|
import { cn } from '../../utils/cn.js';
|
|
@@ -17,6 +17,22 @@ var Avatar = /*#__PURE__*/React.forwardRef(function (_ref, ref) {
|
|
|
17
17
|
status = _ref.status,
|
|
18
18
|
className = _ref.className,
|
|
19
19
|
props = _objectWithoutProperties(_ref, _excluded);
|
|
20
|
+
// State-driven fallback: a broken image swaps to initials / icon without
|
|
21
|
+
// touching the DOM directly. Reset whenever `src` changes
|
|
22
|
+
// (adjust-during-render pattern).
|
|
23
|
+
var _React$useState = React.useState(false),
|
|
24
|
+
_React$useState2 = _slicedToArray(_React$useState, 2),
|
|
25
|
+
imgFailed = _React$useState2[0],
|
|
26
|
+
setImgFailed = _React$useState2[1];
|
|
27
|
+
var _React$useState3 = React.useState(src),
|
|
28
|
+
_React$useState4 = _slicedToArray(_React$useState3, 2),
|
|
29
|
+
prevSrc = _React$useState4[0],
|
|
30
|
+
setPrevSrc = _React$useState4[1];
|
|
31
|
+
if (src !== prevSrc) {
|
|
32
|
+
setPrevSrc(src);
|
|
33
|
+
setImgFailed(false);
|
|
34
|
+
}
|
|
35
|
+
var showImage = Boolean(src) && !imgFailed;
|
|
20
36
|
var sizes = {
|
|
21
37
|
xs: 'h-6 w-6 text-xs',
|
|
22
38
|
sm: 'h-8 w-8 text-sm',
|
|
@@ -48,14 +64,14 @@ var Avatar = /*#__PURE__*/React.forwardRef(function (_ref, ref) {
|
|
|
48
64
|
return jsxs("div", {
|
|
49
65
|
ref: ref,
|
|
50
66
|
className: "relative inline-block",
|
|
51
|
-
children: [
|
|
67
|
+
children: [jsx("div", _objectSpread2(_objectSpread2({
|
|
52
68
|
className: cn('flex items-center justify-center bg-gray-100 text-primary-600 font-medium', sizes[size], {
|
|
53
69
|
'rounded-full': shape === 'circle',
|
|
54
70
|
'rounded-md': shape === 'square',
|
|
55
71
|
'rounded-lg': shape === 'rounded'
|
|
56
72
|
}, className)
|
|
57
73
|
}, props), {}, {
|
|
58
|
-
children:
|
|
74
|
+
children: showImage ? jsx("img", {
|
|
59
75
|
src: src,
|
|
60
76
|
alt: alt || name,
|
|
61
77
|
className: cn('h-full w-full object-cover', {
|
|
@@ -63,22 +79,19 @@ var Avatar = /*#__PURE__*/React.forwardRef(function (_ref, ref) {
|
|
|
63
79
|
'rounded-md': shape === 'square',
|
|
64
80
|
'rounded-lg': shape === 'rounded'
|
|
65
81
|
}),
|
|
66
|
-
onError: function onError(
|
|
67
|
-
|
|
68
|
-
e.target.nextSibling.style.display = 'flex';
|
|
82
|
+
onError: function onError() {
|
|
83
|
+
return setImgFailed(true);
|
|
69
84
|
}
|
|
70
|
-
}) :
|
|
85
|
+
}) : jsx("div", {
|
|
71
86
|
className: cn('flex items-center justify-center h-full w-full', {
|
|
72
87
|
'rounded-full': shape === 'circle',
|
|
73
88
|
'rounded-md': shape === 'square',
|
|
74
89
|
'rounded-lg': shape === 'rounded'
|
|
75
|
-
}, {
|
|
76
|
-
'hidden': src
|
|
77
90
|
}),
|
|
78
91
|
children: name ? getInitials(name) : jsx(User, {
|
|
79
92
|
className: "h-1/2 w-1/2"
|
|
80
93
|
})
|
|
81
|
-
})
|
|
94
|
+
})
|
|
82
95
|
})), status && jsx("span", {
|
|
83
96
|
className: cn('absolute bottom-0 right-0 rounded-full border-2 border-white', statusColors[status], statusSizes[size])
|
|
84
97
|
})]
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { slicedToArray as _slicedToArray } from '../../_virtual/_rollupPluginBabelHelpers.js';
|
|
2
|
+
import { objectWithoutProperties as _objectWithoutProperties, slicedToArray as _slicedToArray, objectSpread2 as _objectSpread2 } from '../../_virtual/_rollupPluginBabelHelpers.js';
|
|
3
3
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
4
4
|
import React from 'react';
|
|
5
5
|
import { ChevronLeft, ChevronRight } from 'lucide-react';
|
|
6
6
|
import { cn } from '../../utils/cn.js';
|
|
7
|
+
import { TOUCH_TARGET } from '../../utils/touchTarget.js';
|
|
7
8
|
|
|
9
|
+
var _excluded = ["children", "autoPlay", "interval", "showDots", "className", "itemClassName", "onMouseEnter", "onMouseLeave"];
|
|
10
|
+
// Arrows stay visible at rest (touch devices have no hover) and use the surface /
|
|
11
|
+
// foreground role tokens so they read on light and dark themes alike.
|
|
12
|
+
var ARROW_CLASSES = "absolute top-1/2 -translate-y-1/2 z-10 inline-flex items-center justify-center p-2 rounded-full bg-surface/80 backdrop-blur-md border border-line text-foreground shadow-lg transition-colors motion-reduce:transition-none hover:bg-surface focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500";
|
|
8
13
|
var Carousel = /*#__PURE__*/React.forwardRef(function Carousel(props, ref) {
|
|
9
14
|
var children = props.children,
|
|
10
15
|
_props$autoPlay = props.autoPlay,
|
|
@@ -14,7 +19,10 @@ var Carousel = /*#__PURE__*/React.forwardRef(function Carousel(props, ref) {
|
|
|
14
19
|
_props$showDots = props.showDots,
|
|
15
20
|
showDots = _props$showDots === void 0 ? true : _props$showDots,
|
|
16
21
|
className = props.className,
|
|
17
|
-
itemClassName = props.itemClassName
|
|
22
|
+
itemClassName = props.itemClassName,
|
|
23
|
+
_onMouseEnter = props.onMouseEnter,
|
|
24
|
+
_onMouseLeave = props.onMouseLeave,
|
|
25
|
+
rest = _objectWithoutProperties(props, _excluded);
|
|
18
26
|
var _React$useState = React.useState(0),
|
|
19
27
|
_React$useState2 = _slicedToArray(_React$useState, 2),
|
|
20
28
|
currentIndex = _React$useState2[0],
|
|
@@ -44,17 +52,20 @@ var Carousel = /*#__PURE__*/React.forwardRef(function Carousel(props, ref) {
|
|
|
44
52
|
};
|
|
45
53
|
}, [autoPlay, isPaused, next, interval]);
|
|
46
54
|
var childrenArray = React.Children.toArray(children);
|
|
47
|
-
return jsxs("div", {
|
|
55
|
+
return jsxs("div", _objectSpread2(_objectSpread2({
|
|
48
56
|
ref: ref,
|
|
49
|
-
className: cn("relative
|
|
50
|
-
onMouseEnter: function onMouseEnter() {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
onMouseLeave: function onMouseLeave() {
|
|
54
|
-
return setIsPaused(false);
|
|
57
|
+
className: cn("relative overflow-hidden rounded-xl bg-surface border border-line shadow-sm", className),
|
|
58
|
+
onMouseEnter: function onMouseEnter(e) {
|
|
59
|
+
setIsPaused(true);
|
|
60
|
+
_onMouseEnter === null || _onMouseEnter === void 0 || _onMouseEnter(e);
|
|
55
61
|
},
|
|
62
|
+
onMouseLeave: function onMouseLeave(e) {
|
|
63
|
+
setIsPaused(false);
|
|
64
|
+
_onMouseLeave === null || _onMouseLeave === void 0 || _onMouseLeave(e);
|
|
65
|
+
}
|
|
66
|
+
}, rest), {}, {
|
|
56
67
|
children: [jsx("div", {
|
|
57
|
-
className: "flex transition-transform duration-500 ease-out",
|
|
68
|
+
className: "flex transition-transform duration-500 ease-out motion-reduce:transition-none",
|
|
58
69
|
style: {
|
|
59
70
|
transform: "translateX(-".concat(currentIndex * 100, "%)")
|
|
60
71
|
},
|
|
@@ -65,32 +76,38 @@ var Carousel = /*#__PURE__*/React.forwardRef(function Carousel(props, ref) {
|
|
|
65
76
|
}, index);
|
|
66
77
|
})
|
|
67
78
|
}), jsx("button", {
|
|
79
|
+
type: "button",
|
|
68
80
|
onClick: prev,
|
|
69
|
-
className: "
|
|
81
|
+
className: cn(ARROW_CLASSES, TOUCH_TARGET, "left-4"),
|
|
70
82
|
"aria-label": "Previous slide",
|
|
71
83
|
children: jsx(ChevronLeft, {
|
|
72
|
-
className: "w-5 h-5
|
|
84
|
+
className: "w-5 h-5",
|
|
85
|
+
"aria-hidden": "true"
|
|
73
86
|
})
|
|
74
87
|
}), jsx("button", {
|
|
88
|
+
type: "button",
|
|
75
89
|
onClick: next,
|
|
76
|
-
className: "
|
|
90
|
+
className: cn(ARROW_CLASSES, TOUCH_TARGET, "right-4"),
|
|
77
91
|
"aria-label": "Next slide",
|
|
78
92
|
children: jsx(ChevronRight, {
|
|
79
|
-
className: "w-5 h-5
|
|
93
|
+
className: "w-5 h-5",
|
|
94
|
+
"aria-hidden": "true"
|
|
80
95
|
})
|
|
81
96
|
}), showDots && jsx("div", {
|
|
82
97
|
className: "absolute bottom-4 left-1/2 -translate-x-1/2 flex gap-2 z-10",
|
|
83
98
|
children: childrenArray.map(function (_, index) {
|
|
84
99
|
return jsx("button", {
|
|
100
|
+
type: "button",
|
|
85
101
|
onClick: function onClick() {
|
|
86
102
|
return setCurrentIndex(index);
|
|
87
103
|
},
|
|
88
|
-
className: cn("
|
|
89
|
-
"aria-label": "Go to slide ".concat(index + 1)
|
|
104
|
+
className: cn("h-2 rounded-full transition-all duration-300 motion-reduce:transition-none focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-2", currentIndex === index ? "bg-primary-600 w-6" : "w-2 bg-muted/40 hover:bg-muted/70"),
|
|
105
|
+
"aria-label": "Go to slide ".concat(index + 1),
|
|
106
|
+
"aria-current": currentIndex === index ? "true" : undefined
|
|
90
107
|
}, index);
|
|
91
108
|
})
|
|
92
109
|
})]
|
|
93
|
-
});
|
|
110
|
+
}));
|
|
94
111
|
});
|
|
95
112
|
Carousel.displayName = "Carousel";
|
|
96
113
|
|
|
@@ -50,7 +50,7 @@ var Pagination = /*#__PURE__*/React.forwardRef(function (_ref, ref) {
|
|
|
50
50
|
className: cn('flex items-center justify-center space-x-1', className),
|
|
51
51
|
"aria-label": "Pagination"
|
|
52
52
|
}, props), {}, {
|
|
53
|
-
children: [showFirstLast &&
|
|
53
|
+
children: [showFirstLast && visiblePages[0] > 1 && jsx("button", {
|
|
54
54
|
className: "rounded-md border border-gray-300 bg-white px-3 py-2 text-sm font-medium hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2",
|
|
55
55
|
onClick: function onClick() {
|
|
56
56
|
return handlePageClick(1);
|
|
@@ -96,7 +96,7 @@ var Pagination = /*#__PURE__*/React.forwardRef(function (_ref, ref) {
|
|
|
96
96
|
children: jsx(ChevronRight, {
|
|
97
97
|
className: "h-4 w-4"
|
|
98
98
|
})
|
|
99
|
-
}), showFirstLast &&
|
|
99
|
+
}), showFirstLast && visiblePages[visiblePages.length - 1] < totalPages && jsx("button", {
|
|
100
100
|
className: "rounded-md border border-gray-300 bg-white px-3 py-2 text-sm font-medium hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2",
|
|
101
101
|
onClick: function onClick() {
|
|
102
102
|
return handlePageClick(totalPages);
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useEffect } from 'react';
|
|
3
|
+
import { useLatestRef } from './useLatestRef.js';
|
|
4
|
+
|
|
5
|
+
// Internal (not exported). Calls `onClickOutside` when a press lands outside
|
|
6
|
+
// every element in `refs`.
|
|
7
|
+
/** Events to listen to: `pointerdown` where supported, else mouse + touch. */
|
|
8
|
+
function outsidePressEvents() {
|
|
9
|
+
if (typeof window !== "undefined" && typeof window.PointerEvent === "function") {
|
|
10
|
+
return ["pointerdown"];
|
|
11
|
+
}
|
|
12
|
+
return ["mousedown", "touchstart"];
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* @param {React.RefObject|React.RefObject[]} refs - one ref or an array of
|
|
16
|
+
* refs; a press inside any of them is "inside". Refs with no node are ignored.
|
|
17
|
+
* @param {(event: Event) => void} onClickOutside - called with the event.
|
|
18
|
+
* @param {{ enabled?: boolean }} [options] - `enabled` (default true) attaches
|
|
19
|
+
* or detaches the listeners.
|
|
20
|
+
*/
|
|
21
|
+
function useClickOutside(refs, onClickOutside) {
|
|
22
|
+
var _ref = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {},
|
|
23
|
+
_ref$enabled = _ref.enabled,
|
|
24
|
+
enabled = _ref$enabled === void 0 ? true : _ref$enabled;
|
|
25
|
+
var refsRef = useLatestRef(refs);
|
|
26
|
+
var handlerRef = useLatestRef(onClickOutside);
|
|
27
|
+
useEffect(function () {
|
|
28
|
+
if (!enabled || typeof document === "undefined") return undefined;
|
|
29
|
+
var handler = function handler(event) {
|
|
30
|
+
var _handlerRef$current;
|
|
31
|
+
var current = refsRef.current;
|
|
32
|
+
var list = Array.isArray(current) ? current : [current];
|
|
33
|
+
var inside = list.some(function (ref) {
|
|
34
|
+
var node = ref === null || ref === void 0 ? void 0 : ref.current;
|
|
35
|
+
return Boolean(node && typeof node.contains === "function" && node.contains(event.target));
|
|
36
|
+
});
|
|
37
|
+
if (!inside) (_handlerRef$current = handlerRef.current) === null || _handlerRef$current === void 0 || _handlerRef$current.call(handlerRef, event);
|
|
38
|
+
};
|
|
39
|
+
var events = outsidePressEvents();
|
|
40
|
+
events.forEach(function (type) {
|
|
41
|
+
return document.addEventListener(type, handler, true);
|
|
42
|
+
});
|
|
43
|
+
return function () {
|
|
44
|
+
return events.forEach(function (type) {
|
|
45
|
+
return document.removeEventListener(type, handler, true);
|
|
46
|
+
});
|
|
47
|
+
};
|
|
48
|
+
}, [enabled, refsRef, handlerRef]);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export { outsidePressEvents, useClickOutside };
|