@dreamtree-org/twreact-ui 1.3.9 → 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/README.md +2 -0
- package/dist/ai-schema.json +19 -8
- package/dist/components/blocks/AppShell.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/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/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/esm/components/blocks/AppShell.js +24 -18
- package/dist/esm/components/core/Accordion.js +40 -9
- 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/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/useFocusTrap.js +10 -2
- package/dist/hooks/useFocusTrap.d.ts.map +1 -1
- package/dist/index.css +1 -1
- package/dist/index.esm.css +1 -1
- package/dist/index.esm.js +1760 -1350
- package/dist/index.js +1759 -1349
- package/dist/types/index.d.ts +8 -2
- package/dist/types/index.d.ts.map +1 -1
- package/mcp/catalog.snapshot.json +82 -52
- package/package.json +1 -1
|
@@ -6,6 +6,24 @@ import { createPortal } from 'react-dom';
|
|
|
6
6
|
import { cn } from '../../utils/cn.js';
|
|
7
7
|
|
|
8
8
|
var _excluded = ["position", "trigger", "content", "customTrigger", "children", "open", "defaultOpen", "onChange", "delay", "offset", "textClass", "bgClass", "className", "portal", "id"];
|
|
9
|
+
// Grace period (ms) before a hover tooltip closes after the pointer leaves the
|
|
10
|
+
// trigger, so the pointer can cross the `offset` gap onto the tooltip itself
|
|
11
|
+
// (WCAG 1.4.13 "hoverable").
|
|
12
|
+
var HOVER_CLOSE_GRACE = 100;
|
|
13
|
+
/**
|
|
14
|
+
* Nearest ancestor that establishes the containing block for an absolutely
|
|
15
|
+
* positioned element. Uses `offsetParent` where the browser provides it and
|
|
16
|
+
* falls back to walking computed styles (jsdom has no layout).
|
|
17
|
+
*/
|
|
18
|
+
var getContainingBlock = function getContainingBlock(el) {
|
|
19
|
+
if (el.offsetParent) return el.offsetParent;
|
|
20
|
+
for (var p = el.parentElement; p && p !== document.body; p = p.parentElement) {
|
|
21
|
+
var cs = window.getComputedStyle(p);
|
|
22
|
+
if (cs.position && cs.position !== "static") return p;
|
|
23
|
+
if (cs.transform && cs.transform !== "none") return p;
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
};
|
|
9
27
|
var NATIVE_FOCUSABLE_TAGS = new Set(["button", "input", "select", "textarea"]);
|
|
10
28
|
/**
|
|
11
29
|
* True when `el` is a single intrinsic element that is keyboard-focusable on
|
|
@@ -62,9 +80,9 @@ var Tooltip = /*#__PURE__*/forwardRef(function Tooltip(_ref, forwardedRef) {
|
|
|
62
80
|
_ref$offset = _ref.offset,
|
|
63
81
|
offset = _ref$offset === void 0 ? 8 : _ref$offset,
|
|
64
82
|
_ref$textClass = _ref.textClass,
|
|
65
|
-
textClass = _ref$textClass === void 0 ? "text-
|
|
83
|
+
textClass = _ref$textClass === void 0 ? "text-foreground" : _ref$textClass,
|
|
66
84
|
_ref$bgClass = _ref.bgClass,
|
|
67
|
-
bgClass = _ref$bgClass === void 0 ? "bg-
|
|
85
|
+
bgClass = _ref$bgClass === void 0 ? "bg-surface" : _ref$bgClass,
|
|
68
86
|
_ref$className = _ref.className,
|
|
69
87
|
className = _ref$className === void 0 ? "" : _ref$className,
|
|
70
88
|
_ref$portal = _ref.portal,
|
|
@@ -74,6 +92,7 @@ var Tooltip = /*#__PURE__*/forwardRef(function Tooltip(_ref, forwardedRef) {
|
|
|
74
92
|
var triggerRef = useRef(null);
|
|
75
93
|
var tooltipRef = useRef(null);
|
|
76
94
|
var delayRef = useRef(null);
|
|
95
|
+
var closeTimerRef = useRef(null);
|
|
77
96
|
var _useState = useState(Boolean(controlledOpen !== null && controlledOpen !== void 0 ? controlledOpen : defaultOpen)),
|
|
78
97
|
_useState2 = _slicedToArray(_useState, 2),
|
|
79
98
|
open = _useState2[0],
|
|
@@ -176,10 +195,22 @@ var Tooltip = /*#__PURE__*/forwardRef(function Tooltip(_ref, forwardedRef) {
|
|
|
176
195
|
var pad = 8;
|
|
177
196
|
left = Math.max(pad, Math.min(left, winW - ttW - pad));
|
|
178
197
|
top = Math.max(pad, Math.min(top, winH - ttH - pad));
|
|
198
|
+
// `left`/`top` are viewport coords; convert to the containing block of the
|
|
199
|
+
// absolutely positioned tooltip. Portaled into <body> (or inline with no
|
|
200
|
+
// positioned ancestor) that is the document, so add page scroll; inline
|
|
201
|
+
// inside a positioned ancestor, offset by that ancestor instead.
|
|
202
|
+
var cb = getContainingBlock(el);
|
|
203
|
+
var originX = -window.scrollX;
|
|
204
|
+
var originY = -window.scrollY;
|
|
205
|
+
if (cb && cb !== document.body && cb !== document.documentElement) {
|
|
206
|
+
var cr = cb.getBoundingClientRect();
|
|
207
|
+
originX = cr.left + (cb.clientLeft || 0) - cb.scrollLeft;
|
|
208
|
+
originY = cr.top + (cb.clientTop || 0) - cb.scrollTop;
|
|
209
|
+
}
|
|
179
210
|
setPlacement(chosen);
|
|
180
211
|
setCoords({
|
|
181
|
-
left: Math.round(left
|
|
182
|
-
top: Math.round(top
|
|
212
|
+
left: Math.round(left - originX),
|
|
213
|
+
top: Math.round(top - originY)
|
|
183
214
|
});
|
|
184
215
|
}, [offset, position]);
|
|
185
216
|
useEffect(function () {
|
|
@@ -202,9 +233,13 @@ var Tooltip = /*#__PURE__*/forwardRef(function Tooltip(_ref, forwardedRef) {
|
|
|
202
233
|
};
|
|
203
234
|
}, [open, computePosition]);
|
|
204
235
|
// open/close helpers
|
|
236
|
+
var cancelScheduledClose = useCallback(function () {
|
|
237
|
+
clearTimeout(closeTimerRef.current);
|
|
238
|
+
}, []);
|
|
205
239
|
var openTooltip = useCallback(function () {
|
|
206
240
|
var immediate = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
|
|
207
241
|
clearTimeout(delayRef.current);
|
|
242
|
+
clearTimeout(closeTimerRef.current);
|
|
208
243
|
if (immediate || !delay) {
|
|
209
244
|
setOpen(true);
|
|
210
245
|
notify(true);
|
|
@@ -217,9 +252,22 @@ var Tooltip = /*#__PURE__*/forwardRef(function Tooltip(_ref, forwardedRef) {
|
|
|
217
252
|
}, [delay, notify]);
|
|
218
253
|
var closeTooltip = useCallback(function () {
|
|
219
254
|
clearTimeout(delayRef.current);
|
|
255
|
+
clearTimeout(closeTimerRef.current);
|
|
220
256
|
setOpen(false);
|
|
221
257
|
notify(false);
|
|
222
258
|
}, [notify]);
|
|
259
|
+
// Hover close is deferred so the pointer can move onto the tooltip.
|
|
260
|
+
var scheduleClose = useCallback(function () {
|
|
261
|
+
clearTimeout(delayRef.current);
|
|
262
|
+
clearTimeout(closeTimerRef.current);
|
|
263
|
+
closeTimerRef.current = setTimeout(closeTooltip, HOVER_CLOSE_GRACE);
|
|
264
|
+
}, [closeTooltip]);
|
|
265
|
+
useEffect(function () {
|
|
266
|
+
return function () {
|
|
267
|
+
clearTimeout(delayRef.current);
|
|
268
|
+
clearTimeout(closeTimerRef.current);
|
|
269
|
+
};
|
|
270
|
+
}, []);
|
|
223
271
|
// triggers normalization
|
|
224
272
|
var triggers = useMemo(function () {
|
|
225
273
|
return Array.isArray(trigger) ? trigger : [trigger];
|
|
@@ -228,34 +276,23 @@ var Tooltip = /*#__PURE__*/forwardRef(function Tooltip(_ref, forwardedRef) {
|
|
|
228
276
|
useEffect(function () {
|
|
229
277
|
var t = triggerRef.current;
|
|
230
278
|
if (!t) return;
|
|
279
|
+
// The tooltip element itself keeps the tip open via its onMouseEnter /
|
|
280
|
+
// onMouseLeave props (see tooltipNode) — it doesn't exist yet here.
|
|
231
281
|
if (triggers.includes("hover")) {
|
|
232
282
|
var onMouseEnter = function onMouseEnter() {
|
|
233
283
|
return openTooltip();
|
|
234
284
|
};
|
|
235
285
|
var onMouseLeave = function onMouseLeave() {
|
|
236
|
-
return
|
|
286
|
+
return scheduleClose();
|
|
237
287
|
};
|
|
238
288
|
t.addEventListener("mouseenter", onMouseEnter);
|
|
239
289
|
t.addEventListener("mouseleave", onMouseLeave);
|
|
240
|
-
// also attach to tooltip to prevent flicker
|
|
241
|
-
var tip = function tip() {
|
|
242
|
-
return tooltipRef.current;
|
|
243
|
-
};
|
|
244
|
-
var tryTip = tip();
|
|
245
|
-
if (tryTip) {
|
|
246
|
-
tryTip.addEventListener("mouseenter", onMouseEnter);
|
|
247
|
-
tryTip.addEventListener("mouseleave", onMouseLeave);
|
|
248
|
-
}
|
|
249
290
|
return function () {
|
|
250
291
|
t.removeEventListener("mouseenter", onMouseEnter);
|
|
251
292
|
t.removeEventListener("mouseleave", onMouseLeave);
|
|
252
|
-
if (tryTip) {
|
|
253
|
-
tryTip.removeEventListener("mouseenter", onMouseEnter);
|
|
254
|
-
tryTip.removeEventListener("mouseleave", onMouseLeave);
|
|
255
|
-
}
|
|
256
293
|
};
|
|
257
294
|
}
|
|
258
|
-
}, [triggers, openTooltip,
|
|
295
|
+
}, [triggers, openTooltip, scheduleClose]);
|
|
259
296
|
// click handlers (toggle and outside click)
|
|
260
297
|
useEffect(function () {
|
|
261
298
|
var t = triggerRef.current;
|
|
@@ -283,16 +320,21 @@ var Tooltip = /*#__PURE__*/forwardRef(function Tooltip(_ref, forwardedRef) {
|
|
|
283
320
|
};
|
|
284
321
|
}
|
|
285
322
|
}, [triggers, notify]);
|
|
286
|
-
// focus handlers: use focusin / focusout which bubble and handle inner focusables
|
|
323
|
+
// focus handlers: use focusin / focusout which bubble and handle inner focusables.
|
|
324
|
+
// A hover tooltip also opens on keyboard focus (WCAG 1.4.13), unless click
|
|
325
|
+
// is a trigger too (focus-on-click would open it and the click toggle it shut).
|
|
326
|
+
var opensOnFocus = triggers.includes("focus") || triggers.includes("hover") && !triggers.includes("click");
|
|
287
327
|
useEffect(function () {
|
|
288
328
|
var t = triggerRef.current;
|
|
289
329
|
if (!t) return;
|
|
290
|
-
if (
|
|
330
|
+
if (opensOnFocus) {
|
|
291
331
|
var onFocusIn = function onFocusIn() {
|
|
292
332
|
return openTooltip(true);
|
|
293
333
|
};
|
|
294
|
-
var onFocusOut = function onFocusOut() {
|
|
295
|
-
|
|
334
|
+
var onFocusOut = function onFocusOut(e) {
|
|
335
|
+
var tip = tooltipRef.current;
|
|
336
|
+
if (e.relatedTarget && tip && tip.contains(e.relatedTarget)) return;
|
|
337
|
+
closeTooltip();
|
|
296
338
|
};
|
|
297
339
|
t.addEventListener("focusin", onFocusIn);
|
|
298
340
|
t.addEventListener("focusout", onFocusOut);
|
|
@@ -301,7 +343,22 @@ var Tooltip = /*#__PURE__*/forwardRef(function Tooltip(_ref, forwardedRef) {
|
|
|
301
343
|
t.removeEventListener("focusout", onFocusOut);
|
|
302
344
|
};
|
|
303
345
|
}
|
|
304
|
-
}, [
|
|
346
|
+
}, [opensOnFocus, openTooltip, closeTooltip]);
|
|
347
|
+
// Dismiss on a pointer/tap outside. Touch has no mouseleave, so without this
|
|
348
|
+
// a tapped-open hover tooltip would never close.
|
|
349
|
+
useEffect(function () {
|
|
350
|
+
if (!open) return;
|
|
351
|
+
var onPointerDown = function onPointerDown(e) {
|
|
352
|
+
var t = triggerRef.current;
|
|
353
|
+
var tip = tooltipRef.current;
|
|
354
|
+
if (t && t.contains(e.target) || tip && tip.contains(e.target)) return;
|
|
355
|
+
closeTooltip();
|
|
356
|
+
};
|
|
357
|
+
document.addEventListener("pointerdown", onPointerDown);
|
|
358
|
+
return function () {
|
|
359
|
+
return document.removeEventListener("pointerdown", onPointerDown);
|
|
360
|
+
};
|
|
361
|
+
}, [open, closeTooltip]);
|
|
305
362
|
// Escape key closes tooltip
|
|
306
363
|
useEffect(function () {
|
|
307
364
|
if (!open) return;
|
|
@@ -345,20 +402,35 @@ var Tooltip = /*#__PURE__*/forwardRef(function Tooltip(_ref, forwardedRef) {
|
|
|
345
402
|
"aria-describedby": id,
|
|
346
403
|
children: children
|
|
347
404
|
});
|
|
405
|
+
// Hovering the tooltip itself keeps it open (attached as props so they are
|
|
406
|
+
// live as soon as the tip mounts); consumer handlers in ...rest still run.
|
|
407
|
+
var hoverable = triggers.includes("hover");
|
|
408
|
+
var restMouseEnter = rest.onMouseEnter,
|
|
409
|
+
restMouseLeave = rest.onMouseLeave;
|
|
410
|
+
var tipHoverProps = hoverable ? {
|
|
411
|
+
onMouseEnter: function onMouseEnter(e) {
|
|
412
|
+
cancelScheduledClose();
|
|
413
|
+
restMouseEnter === null || restMouseEnter === void 0 || restMouseEnter(e);
|
|
414
|
+
},
|
|
415
|
+
onMouseLeave: function onMouseLeave(e) {
|
|
416
|
+
scheduleClose();
|
|
417
|
+
restMouseLeave === null || restMouseLeave === void 0 || restMouseLeave(e);
|
|
418
|
+
}
|
|
419
|
+
} : {};
|
|
348
420
|
// Tooltip box element
|
|
349
|
-
var tooltipNode = open ? jsx("div", _objectSpread2(_objectSpread2({
|
|
421
|
+
var tooltipNode = open ? jsx("div", _objectSpread2(_objectSpread2(_objectSpread2({
|
|
350
422
|
ref: tooltipRef,
|
|
351
423
|
id: id,
|
|
352
424
|
role: "tooltip"
|
|
353
|
-
}, rest), {}, {
|
|
354
|
-
className: cn("absolute z-50 transform-gpu transition-opacity duration-150", className),
|
|
425
|
+
}, rest), tipHoverProps), {}, {
|
|
426
|
+
className: cn("absolute z-50 transform-gpu transition-opacity duration-150 motion-reduce:transition-none", className),
|
|
355
427
|
style: {
|
|
356
428
|
left: coords.left,
|
|
357
429
|
top: coords.top,
|
|
358
430
|
position: "absolute"
|
|
359
431
|
},
|
|
360
432
|
children: jsx("div", {
|
|
361
|
-
className: cn("max-w-xs rounded-md border border-
|
|
433
|
+
className: cn("max-w-xs rounded-md border border-line px-3 py-2 text-sm shadow-lg shadow-black/5", bgClass, textClass),
|
|
362
434
|
children: renderContent()
|
|
363
435
|
})
|
|
364
436
|
})) : null;
|
|
@@ -4,8 +4,12 @@ import { jsx, jsxs } from 'react/jsx-runtime';
|
|
|
4
4
|
import React from 'react';
|
|
5
5
|
import { cn } from '../../utils/cn.js';
|
|
6
6
|
import { Info, AlertTriangle, AlertCircle, CheckCircle, X } from 'lucide-react';
|
|
7
|
+
import { TOUCH_TARGET } from '../../utils/touchTarget.js';
|
|
7
8
|
|
|
8
9
|
var _excluded = ["variant", "title", "children", "dismissible", "onDismiss", "className"];
|
|
10
|
+
// error / warning need attention now (role="alert", assertive); info / success
|
|
11
|
+
// are neutral and must not interrupt (role="status", polite).
|
|
12
|
+
var URGENT_VARIANTS = new Set(['error', 'warning']);
|
|
9
13
|
var Alert = /*#__PURE__*/React.forwardRef(function (_ref, ref) {
|
|
10
14
|
var _ref$variant = _ref.variant,
|
|
11
15
|
variant = _ref$variant === void 0 ? 'info' : _ref$variant,
|
|
@@ -20,7 +24,8 @@ var Alert = /*#__PURE__*/React.forwardRef(function (_ref, ref) {
|
|
|
20
24
|
success: {
|
|
21
25
|
container: 'bg-success-50 border-success-200',
|
|
22
26
|
icon: jsx(CheckCircle, {
|
|
23
|
-
className: "h-5 w-5 text-success-500"
|
|
27
|
+
className: "h-5 w-5 text-success-500",
|
|
28
|
+
"aria-hidden": "true"
|
|
24
29
|
}),
|
|
25
30
|
title: 'text-success-800',
|
|
26
31
|
content: 'text-success-700'
|
|
@@ -28,7 +33,8 @@ var Alert = /*#__PURE__*/React.forwardRef(function (_ref, ref) {
|
|
|
28
33
|
error: {
|
|
29
34
|
container: 'bg-error-50 border-error-200',
|
|
30
35
|
icon: jsx(AlertCircle, {
|
|
31
|
-
className: "h-5 w-5 text-error-500"
|
|
36
|
+
className: "h-5 w-5 text-error-500",
|
|
37
|
+
"aria-hidden": "true"
|
|
32
38
|
}),
|
|
33
39
|
title: 'text-error-800',
|
|
34
40
|
content: 'text-error-700'
|
|
@@ -36,7 +42,8 @@ var Alert = /*#__PURE__*/React.forwardRef(function (_ref, ref) {
|
|
|
36
42
|
warning: {
|
|
37
43
|
container: 'bg-warning-50 border-warning-200',
|
|
38
44
|
icon: jsx(AlertTriangle, {
|
|
39
|
-
className: "h-5 w-5 text-warning-500"
|
|
45
|
+
className: "h-5 w-5 text-warning-500",
|
|
46
|
+
"aria-hidden": "true"
|
|
40
47
|
}),
|
|
41
48
|
title: 'text-warning-800',
|
|
42
49
|
content: 'text-warning-700'
|
|
@@ -44,7 +51,8 @@ var Alert = /*#__PURE__*/React.forwardRef(function (_ref, ref) {
|
|
|
44
51
|
info: {
|
|
45
52
|
container: 'bg-primary-50 border-primary-200',
|
|
46
53
|
icon: jsx(Info, {
|
|
47
|
-
className: "h-5 w-5 text-primary-500"
|
|
54
|
+
className: "h-5 w-5 text-primary-500",
|
|
55
|
+
"aria-hidden": "true"
|
|
48
56
|
}),
|
|
49
57
|
title: 'text-primary-800',
|
|
50
58
|
content: 'text-primary-700'
|
|
@@ -54,7 +62,7 @@ var Alert = /*#__PURE__*/React.forwardRef(function (_ref, ref) {
|
|
|
54
62
|
return jsx("div", _objectSpread2(_objectSpread2({
|
|
55
63
|
ref: ref,
|
|
56
64
|
className: cn('rounded-md border p-4', variantStyles.container, className),
|
|
57
|
-
role:
|
|
65
|
+
role: URGENT_VARIANTS.has(variant) ? 'alert' : 'status'
|
|
58
66
|
}, props), {}, {
|
|
59
67
|
children: jsxs("div", {
|
|
60
68
|
className: "flex",
|
|
@@ -76,15 +84,17 @@ var Alert = /*#__PURE__*/React.forwardRef(function (_ref, ref) {
|
|
|
76
84
|
className: "ml-auto pl-3",
|
|
77
85
|
children: jsx("button", {
|
|
78
86
|
type: "button",
|
|
79
|
-
className: cn('inline-flex rounded-md p-1.5 focus:outline-none focus:ring-2 focus:ring-offset-2', {
|
|
80
|
-
'text-success-500 hover:bg-success-100 focus:ring-success-600': variant === 'success',
|
|
81
|
-
'text-error-500 hover:bg-error-100 focus:ring-error-600': variant === 'error',
|
|
82
|
-
'text-warning-500 hover:bg-warning-100 focus:ring-warning-600': variant === 'warning',
|
|
83
|
-
'text-primary-500 hover:bg-primary-100 focus:ring-primary-600': variant === 'info'
|
|
87
|
+
className: cn('inline-flex items-center justify-center rounded-md p-1.5 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2', TOUCH_TARGET, {
|
|
88
|
+
'text-success-500 hover:bg-success-100 focus-visible:ring-success-600': variant === 'success',
|
|
89
|
+
'text-error-500 hover:bg-error-100 focus-visible:ring-error-600': variant === 'error',
|
|
90
|
+
'text-warning-500 hover:bg-warning-100 focus-visible:ring-warning-600': variant === 'warning',
|
|
91
|
+
'text-primary-500 hover:bg-primary-100 focus-visible:ring-primary-600': variant === 'info'
|
|
84
92
|
}),
|
|
85
93
|
onClick: onDismiss,
|
|
94
|
+
"aria-label": "Dismiss",
|
|
86
95
|
children: jsx(X, {
|
|
87
|
-
className: "h-4 w-4"
|
|
96
|
+
className: "h-4 w-4",
|
|
97
|
+
"aria-hidden": "true"
|
|
88
98
|
})
|
|
89
99
|
})
|
|
90
100
|
})]
|
|
@@ -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", {
|