@kuraykaraaslan/kui-react 1.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/LICENSE +17 -0
- package/README.md +168 -0
- package/dist/AdvancedDataTable-F3DNXDKX.mjs +11 -0
- package/dist/DataTable-2G27T4E6.mjs +11 -0
- package/dist/DateRangePicker-AL32QB6L.mjs +11 -0
- package/dist/DropdownMenu-f5yV9dzM.d.mts +22 -0
- package/dist/DropdownMenu-f5yV9dzM.d.ts +22 -0
- package/dist/MapView-FERKPCDB.mjs +10 -0
- package/dist/ServerDataTable-RZV3K6KQ.mjs +11 -0
- package/dist/Tooltip-Bof5GvOc.d.mts +248 -0
- package/dist/Tooltip-Bof5GvOc.d.ts +248 -0
- package/dist/VideoPlayer-P3I6ESXJ.mjs +9 -0
- package/dist/app.d.mts +620 -0
- package/dist/app.d.ts +620 -0
- package/dist/app.js +7061 -0
- package/dist/app.mjs +100 -0
- package/dist/chunk-24BCQSLI.mjs +1 -0
- package/dist/chunk-45I3EDB2.mjs +90 -0
- package/dist/chunk-4IWCD7ID.mjs +1450 -0
- package/dist/chunk-5E2HXWFI.mjs +105 -0
- package/dist/chunk-C7AYI4XM.mjs +402 -0
- package/dist/chunk-J4D44TUA.mjs +1267 -0
- package/dist/chunk-KTEWZKNE.mjs +1020 -0
- package/dist/chunk-LMUQHL4Z.mjs +3829 -0
- package/dist/chunk-MD5OQ4J2.mjs +527 -0
- package/dist/chunk-MPJRPYIZ.mjs +1 -0
- package/dist/chunk-MPWUEQ7J.mjs +2422 -0
- package/dist/chunk-MTT5TKAJ.mjs +93 -0
- package/dist/chunk-RBDK7MWQ.mjs +46 -0
- package/dist/chunk-SVFQZPNZ.mjs +3648 -0
- package/dist/chunk-TZWBBMSG.mjs +1 -0
- package/dist/chunk-XA7J6PVJ.mjs +1488 -0
- package/dist/chunk-ZLYBRYWQ.mjs +726 -0
- package/dist/common.d.mts +921 -0
- package/dist/common.d.ts +921 -0
- package/dist/common.js +4991 -0
- package/dist/common.mjs +172 -0
- package/dist/index.d.mts +10 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +17563 -0
- package/dist/index.mjs +349 -0
- package/dist/ui.d.mts +937 -0
- package/dist/ui.d.ts +937 -0
- package/dist/ui.js +10095 -0
- package/dist/ui.mjs +163 -0
- package/package.json +114 -0
- package/styles/index.css +129 -0
|
@@ -0,0 +1,1488 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import {
|
|
3
|
+
__spreadProps,
|
|
4
|
+
__spreadValues,
|
|
5
|
+
cn
|
|
6
|
+
} from "./chunk-RBDK7MWQ.mjs";
|
|
7
|
+
|
|
8
|
+
// modules/ui/SkipLink.tsx
|
|
9
|
+
import { jsx } from "react/jsx-runtime";
|
|
10
|
+
function SkipLink({
|
|
11
|
+
href = "#main-content",
|
|
12
|
+
label = "Skip to main content",
|
|
13
|
+
className
|
|
14
|
+
}) {
|
|
15
|
+
return /* @__PURE__ */ jsx(
|
|
16
|
+
"a",
|
|
17
|
+
{
|
|
18
|
+
href,
|
|
19
|
+
className: cn(
|
|
20
|
+
"sr-only focus:not-sr-only focus:fixed focus:top-4 focus:left-4 focus:z-[100]",
|
|
21
|
+
"px-4 py-2 rounded-md text-sm font-medium",
|
|
22
|
+
"bg-primary text-primary-fg",
|
|
23
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus",
|
|
24
|
+
className
|
|
25
|
+
),
|
|
26
|
+
children: label
|
|
27
|
+
}
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
function LiveRegion({
|
|
31
|
+
message,
|
|
32
|
+
politeness = "polite",
|
|
33
|
+
className
|
|
34
|
+
}) {
|
|
35
|
+
return /* @__PURE__ */ jsx(
|
|
36
|
+
"div",
|
|
37
|
+
{
|
|
38
|
+
role: "status",
|
|
39
|
+
"aria-live": politeness,
|
|
40
|
+
"aria-atomic": "true",
|
|
41
|
+
className: cn("sr-only", className),
|
|
42
|
+
children: message
|
|
43
|
+
}
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
function Announcer({
|
|
47
|
+
message,
|
|
48
|
+
politeness = "polite"
|
|
49
|
+
}) {
|
|
50
|
+
return /* @__PURE__ */ jsx(LiveRegion, { message, politeness });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// modules/ui/Breadcrumb.tsx
|
|
54
|
+
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
55
|
+
import { faChevronRight } from "@fortawesome/free-solid-svg-icons";
|
|
56
|
+
import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
57
|
+
function Breadcrumb({
|
|
58
|
+
items,
|
|
59
|
+
separator,
|
|
60
|
+
maxItems,
|
|
61
|
+
className
|
|
62
|
+
}) {
|
|
63
|
+
const sep = separator != null ? separator : /* @__PURE__ */ jsx2(FontAwesomeIcon, { icon: faChevronRight, className: "w-2.5 h-2.5 text-text-disabled", "aria-hidden": "true" });
|
|
64
|
+
let displayed = items;
|
|
65
|
+
let truncated = false;
|
|
66
|
+
if (maxItems && items.length > maxItems) {
|
|
67
|
+
truncated = true;
|
|
68
|
+
displayed = [items[0], { label: "\u2026", href: void 0 }, ...items.slice(-(maxItems - 1))];
|
|
69
|
+
}
|
|
70
|
+
return /* @__PURE__ */ jsx2("nav", { "aria-label": "Breadcrumb", className, children: /* @__PURE__ */ jsx2("ol", { className: "flex flex-wrap items-center gap-1 text-sm", children: displayed.map((item, i) => {
|
|
71
|
+
const isLast = i === displayed.length - 1;
|
|
72
|
+
const isEllipsis = item.label === "\u2026" && truncated;
|
|
73
|
+
return /* @__PURE__ */ jsx2("li", { className: "flex items-center gap-1", children: !isLast && item.href ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
74
|
+
/* @__PURE__ */ jsx2(
|
|
75
|
+
"a",
|
|
76
|
+
{
|
|
77
|
+
href: item.href,
|
|
78
|
+
className: cn(
|
|
79
|
+
"text-text-secondary hover:text-text-primary transition-colors",
|
|
80
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus rounded"
|
|
81
|
+
),
|
|
82
|
+
children: item.label
|
|
83
|
+
}
|
|
84
|
+
),
|
|
85
|
+
sep
|
|
86
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
87
|
+
/* @__PURE__ */ jsx2(
|
|
88
|
+
"span",
|
|
89
|
+
{
|
|
90
|
+
className: cn(
|
|
91
|
+
isLast ? "text-text-primary font-medium" : "text-text-secondary",
|
|
92
|
+
isEllipsis && "select-none"
|
|
93
|
+
),
|
|
94
|
+
"aria-current": isLast ? "page" : void 0,
|
|
95
|
+
"aria-hidden": isEllipsis ? "true" : void 0,
|
|
96
|
+
children: item.label
|
|
97
|
+
}
|
|
98
|
+
),
|
|
99
|
+
!isLast && sep
|
|
100
|
+
] }) }, i);
|
|
101
|
+
}) }) });
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// modules/ui/Overlays/Drawer/index.tsx
|
|
105
|
+
import { useRef } from "react";
|
|
106
|
+
import { createPortal } from "react-dom";
|
|
107
|
+
import { FontAwesomeIcon as FontAwesomeIcon2 } from "@fortawesome/react-fontawesome";
|
|
108
|
+
import { faXmark } from "@fortawesome/free-solid-svg-icons";
|
|
109
|
+
|
|
110
|
+
// modules/ui/Overlays/shared/useFocusTrap.ts
|
|
111
|
+
import { useEffect, useCallback } from "react";
|
|
112
|
+
var FOCUSABLE = 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
|
|
113
|
+
var layerStack = [];
|
|
114
|
+
function isTopLayer(ref) {
|
|
115
|
+
return layerStack[layerStack.length - 1] === ref;
|
|
116
|
+
}
|
|
117
|
+
function useFocusTrap(ref, { active, onEscape, handleEscape = true }) {
|
|
118
|
+
useEffect(() => {
|
|
119
|
+
if (!active) return;
|
|
120
|
+
layerStack.push(ref);
|
|
121
|
+
return () => {
|
|
122
|
+
const idx = layerStack.lastIndexOf(ref);
|
|
123
|
+
if (idx !== -1) layerStack.splice(idx, 1);
|
|
124
|
+
};
|
|
125
|
+
}, [active, ref]);
|
|
126
|
+
useEffect(() => {
|
|
127
|
+
if (!active) return;
|
|
128
|
+
const prev = document.activeElement;
|
|
129
|
+
const t = window.setTimeout(() => {
|
|
130
|
+
const root = ref.current;
|
|
131
|
+
if (!root) return;
|
|
132
|
+
const firstFocusable = root.querySelector(FOCUSABLE);
|
|
133
|
+
(firstFocusable != null ? firstFocusable : root).focus();
|
|
134
|
+
}, 0);
|
|
135
|
+
return () => {
|
|
136
|
+
var _a;
|
|
137
|
+
window.clearTimeout(t);
|
|
138
|
+
(_a = prev == null ? void 0 : prev.focus) == null ? void 0 : _a.call(prev);
|
|
139
|
+
};
|
|
140
|
+
}, [active, ref]);
|
|
141
|
+
const handleKeyDown = useCallback(
|
|
142
|
+
(e) => {
|
|
143
|
+
if (!isTopLayer(ref)) return;
|
|
144
|
+
if (handleEscape && e.key === "Escape") {
|
|
145
|
+
onEscape == null ? void 0 : onEscape();
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
if (e.key !== "Tab") return;
|
|
149
|
+
const container = ref.current;
|
|
150
|
+
if (!container) return;
|
|
151
|
+
const focusable = Array.from(
|
|
152
|
+
container.querySelectorAll(FOCUSABLE)
|
|
153
|
+
).filter((el) => el.dataset.focusGuard !== "true");
|
|
154
|
+
if (focusable.length === 0) {
|
|
155
|
+
e.preventDefault();
|
|
156
|
+
container.focus();
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
const first = focusable[0];
|
|
160
|
+
const last = focusable[focusable.length - 1];
|
|
161
|
+
if (e.shiftKey) {
|
|
162
|
+
if (document.activeElement === first || !container.contains(document.activeElement)) {
|
|
163
|
+
e.preventDefault();
|
|
164
|
+
last.focus();
|
|
165
|
+
}
|
|
166
|
+
} else {
|
|
167
|
+
if (document.activeElement === last) {
|
|
168
|
+
e.preventDefault();
|
|
169
|
+
first.focus();
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
[ref, onEscape, handleEscape]
|
|
174
|
+
);
|
|
175
|
+
useEffect(() => {
|
|
176
|
+
if (!active) return;
|
|
177
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
178
|
+
return () => document.removeEventListener("keydown", handleKeyDown);
|
|
179
|
+
}, [active, handleKeyDown]);
|
|
180
|
+
}
|
|
181
|
+
function isFocusTrapTopLayer(ref) {
|
|
182
|
+
return isTopLayer(ref);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// modules/ui/Overlays/shared/useScrollLock.ts
|
|
186
|
+
import { useEffect as useEffect2 } from "react";
|
|
187
|
+
var lockCount = 0;
|
|
188
|
+
var originalBodyStyle = {
|
|
189
|
+
overflow: "",
|
|
190
|
+
paddingRight: "",
|
|
191
|
+
position: "",
|
|
192
|
+
top: "",
|
|
193
|
+
width: ""
|
|
194
|
+
};
|
|
195
|
+
var savedScrollY = 0;
|
|
196
|
+
function isIOS() {
|
|
197
|
+
if (typeof navigator === "undefined") return false;
|
|
198
|
+
return /iP(ad|hone|od)/.test(navigator.userAgent) || navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1;
|
|
199
|
+
}
|
|
200
|
+
function lock() {
|
|
201
|
+
if (lockCount === 0) {
|
|
202
|
+
const body = document.body;
|
|
203
|
+
const html = document.documentElement;
|
|
204
|
+
const scrollbarWidth = window.innerWidth - html.clientWidth;
|
|
205
|
+
originalBodyStyle = {
|
|
206
|
+
overflow: body.style.overflow,
|
|
207
|
+
paddingRight: body.style.paddingRight,
|
|
208
|
+
position: body.style.position,
|
|
209
|
+
top: body.style.top,
|
|
210
|
+
width: body.style.width
|
|
211
|
+
};
|
|
212
|
+
if (isIOS()) {
|
|
213
|
+
savedScrollY = window.scrollY;
|
|
214
|
+
body.style.position = "fixed";
|
|
215
|
+
body.style.top = `-${savedScrollY}px`;
|
|
216
|
+
body.style.width = "100%";
|
|
217
|
+
} else {
|
|
218
|
+
body.style.overflow = "hidden";
|
|
219
|
+
if (scrollbarWidth > 0) {
|
|
220
|
+
const currentPad = parseFloat(getComputedStyle(body).paddingRight) || 0;
|
|
221
|
+
body.style.paddingRight = `${currentPad + scrollbarWidth}px`;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
lockCount += 1;
|
|
226
|
+
}
|
|
227
|
+
function unlock() {
|
|
228
|
+
lockCount = Math.max(0, lockCount - 1);
|
|
229
|
+
if (lockCount === 0) {
|
|
230
|
+
const body = document.body;
|
|
231
|
+
const wasIOS = body.style.position === "fixed";
|
|
232
|
+
body.style.overflow = originalBodyStyle.overflow;
|
|
233
|
+
body.style.paddingRight = originalBodyStyle.paddingRight;
|
|
234
|
+
body.style.position = originalBodyStyle.position;
|
|
235
|
+
body.style.top = originalBodyStyle.top;
|
|
236
|
+
body.style.width = originalBodyStyle.width;
|
|
237
|
+
if (wasIOS) {
|
|
238
|
+
window.scrollTo(0, savedScrollY);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
function useScrollLock(active) {
|
|
243
|
+
useEffect2(() => {
|
|
244
|
+
if (!active) return;
|
|
245
|
+
lock();
|
|
246
|
+
return () => unlock();
|
|
247
|
+
}, [active]);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// modules/ui/Overlays/shared/usePresence.ts
|
|
251
|
+
import { useEffect as useEffect3, useState } from "react";
|
|
252
|
+
var EXIT_MS = 250;
|
|
253
|
+
function usePresence(open) {
|
|
254
|
+
const [state, setState] = useState(open ? "open" : "closed");
|
|
255
|
+
useEffect3(() => {
|
|
256
|
+
if (open) {
|
|
257
|
+
setState("open");
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
setState((prev) => prev === "closed" ? "closed" : "closing");
|
|
261
|
+
const t = window.setTimeout(() => setState("closed"), EXIT_MS);
|
|
262
|
+
return () => window.clearTimeout(t);
|
|
263
|
+
}, [open]);
|
|
264
|
+
return {
|
|
265
|
+
mounted: state !== "closed",
|
|
266
|
+
state
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// modules/ui/Overlays/shared/usePortal.ts
|
|
271
|
+
import { useEffect as useEffect4, useState as useState2 } from "react";
|
|
272
|
+
function usePortal(target) {
|
|
273
|
+
const [node, setNode] = useState2(null);
|
|
274
|
+
useEffect4(() => {
|
|
275
|
+
if (typeof document === "undefined") return;
|
|
276
|
+
if (target instanceof Element) {
|
|
277
|
+
setNode(target);
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
if (typeof target === "string") {
|
|
281
|
+
setNode(document.querySelector(target));
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
setNode(document.body);
|
|
285
|
+
}, [target]);
|
|
286
|
+
return node;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// modules/ui/Overlays/shared/useRouteClose.ts
|
|
290
|
+
function useRouteClose(_opts) {
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// modules/ui/Overlays/Drawer/index.tsx
|
|
294
|
+
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
295
|
+
function Drawer({
|
|
296
|
+
open,
|
|
297
|
+
onClose,
|
|
298
|
+
title,
|
|
299
|
+
side = "right",
|
|
300
|
+
children,
|
|
301
|
+
footer,
|
|
302
|
+
closeOnRouteChange,
|
|
303
|
+
// TODO M5: reducedMotion
|
|
304
|
+
reducedMotion: _reducedMotion,
|
|
305
|
+
portalTarget,
|
|
306
|
+
className,
|
|
307
|
+
ref
|
|
308
|
+
}) {
|
|
309
|
+
const panelRef = useRef(null);
|
|
310
|
+
const { mounted, state } = usePresence(open);
|
|
311
|
+
useFocusTrap(panelRef, { active: open, onEscape: onClose });
|
|
312
|
+
useScrollLock(open);
|
|
313
|
+
useRouteClose({ active: open, closeOnRouteChange, onClose });
|
|
314
|
+
const portalNode = usePortal(portalTarget);
|
|
315
|
+
if (!mounted) return null;
|
|
316
|
+
const node = /* @__PURE__ */ jsxs2(
|
|
317
|
+
"div",
|
|
318
|
+
{
|
|
319
|
+
className: cn(
|
|
320
|
+
"fixed inset-0 z-[100] flex",
|
|
321
|
+
state !== "open" && "pointer-events-none"
|
|
322
|
+
),
|
|
323
|
+
"aria-modal": "true",
|
|
324
|
+
role: "dialog",
|
|
325
|
+
"aria-label": title,
|
|
326
|
+
"data-state": state,
|
|
327
|
+
children: [
|
|
328
|
+
/* @__PURE__ */ jsx3(
|
|
329
|
+
"div",
|
|
330
|
+
{
|
|
331
|
+
className: cn(
|
|
332
|
+
"absolute inset-0 bg-black/50 transition-opacity duration-200",
|
|
333
|
+
state === "open" ? "opacity-100" : "opacity-0"
|
|
334
|
+
),
|
|
335
|
+
onClick: onClose,
|
|
336
|
+
"aria-hidden": "true"
|
|
337
|
+
}
|
|
338
|
+
),
|
|
339
|
+
/* @__PURE__ */ jsxs2(
|
|
340
|
+
"div",
|
|
341
|
+
{
|
|
342
|
+
ref: (node2) => {
|
|
343
|
+
panelRef.current = node2;
|
|
344
|
+
if (typeof ref === "function") ref(node2);
|
|
345
|
+
else if (ref) ref.current = node2;
|
|
346
|
+
},
|
|
347
|
+
tabIndex: -1,
|
|
348
|
+
"data-state": state,
|
|
349
|
+
className: cn(
|
|
350
|
+
"relative z-[101] flex flex-col w-80 max-w-full h-full bg-surface-raised border-border shadow-xl",
|
|
351
|
+
"transition-transform duration-200 focus-visible:outline-none",
|
|
352
|
+
side === "right" ? "ml-auto border-l" : "mr-auto border-r",
|
|
353
|
+
state === "open" ? "translate-x-0" : side === "right" ? "translate-x-full" : "-translate-x-full",
|
|
354
|
+
className
|
|
355
|
+
),
|
|
356
|
+
children: [
|
|
357
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex items-center justify-between gap-3 px-4 py-4 border-b border-border shrink-0", children: [
|
|
358
|
+
/* @__PURE__ */ jsx3("h2", { className: "text-base font-semibold text-text-primary", children: title }),
|
|
359
|
+
/* @__PURE__ */ jsx3(
|
|
360
|
+
"button",
|
|
361
|
+
{
|
|
362
|
+
type: "button",
|
|
363
|
+
onClick: onClose,
|
|
364
|
+
"aria-label": "Close drawer",
|
|
365
|
+
className: "text-text-disabled hover:text-text-primary transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus rounded",
|
|
366
|
+
children: /* @__PURE__ */ jsx3(FontAwesomeIcon2, { icon: faXmark, className: "w-4 h-4", "aria-hidden": "true" })
|
|
367
|
+
}
|
|
368
|
+
)
|
|
369
|
+
] }),
|
|
370
|
+
/* @__PURE__ */ jsx3("div", { className: "flex-1 min-h-0 overflow-y-auto px-4 py-4", children }),
|
|
371
|
+
footer && /* @__PURE__ */ jsx3("div", { className: "px-4 py-4 border-t border-border shrink-0", children: footer })
|
|
372
|
+
]
|
|
373
|
+
}
|
|
374
|
+
)
|
|
375
|
+
]
|
|
376
|
+
}
|
|
377
|
+
);
|
|
378
|
+
if (!portalNode) return null;
|
|
379
|
+
return createPortal(node, portalNode);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// modules/ui/EmptyState.tsx
|
|
383
|
+
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
384
|
+
function EmptyState({
|
|
385
|
+
icon,
|
|
386
|
+
title,
|
|
387
|
+
description,
|
|
388
|
+
action,
|
|
389
|
+
className
|
|
390
|
+
}) {
|
|
391
|
+
return /* @__PURE__ */ jsxs3(
|
|
392
|
+
"div",
|
|
393
|
+
{
|
|
394
|
+
className: cn(
|
|
395
|
+
"flex flex-col items-center justify-center text-center py-16 px-6",
|
|
396
|
+
className
|
|
397
|
+
),
|
|
398
|
+
children: [
|
|
399
|
+
icon && /* @__PURE__ */ jsx4("div", { className: "h-12 w-12 rounded-full bg-surface-sunken flex items-center justify-center text-text-disabled text-2xl mb-4", "aria-hidden": "true", children: icon }),
|
|
400
|
+
/* @__PURE__ */ jsx4("h3", { className: "text-sm font-semibold text-text-primary", children: title }),
|
|
401
|
+
description && /* @__PURE__ */ jsx4("p", { className: "mt-1 text-sm text-text-secondary max-w-xs", children: description }),
|
|
402
|
+
action && /* @__PURE__ */ jsx4("div", { className: "mt-4", children: action })
|
|
403
|
+
]
|
|
404
|
+
}
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// modules/ui/Overlays/Modal/index.tsx
|
|
409
|
+
import { useRef as useRef2 } from "react";
|
|
410
|
+
import { createPortal as createPortal2 } from "react-dom";
|
|
411
|
+
import { FontAwesomeIcon as FontAwesomeIcon3 } from "@fortawesome/react-fontawesome";
|
|
412
|
+
import { faXmark as faXmark2 } from "@fortawesome/free-solid-svg-icons";
|
|
413
|
+
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
414
|
+
var sizeMap = { sm: "max-w-sm", md: "max-w-md", lg: "max-w-lg" };
|
|
415
|
+
function Modal({
|
|
416
|
+
open,
|
|
417
|
+
onClose,
|
|
418
|
+
title,
|
|
419
|
+
description,
|
|
420
|
+
children,
|
|
421
|
+
footer,
|
|
422
|
+
size = "md",
|
|
423
|
+
fullscreen = false,
|
|
424
|
+
scrollable = false,
|
|
425
|
+
closeOnBackdropClick = true,
|
|
426
|
+
closeOnRouteChange,
|
|
427
|
+
// TODO M5: reducedMotion
|
|
428
|
+
reducedMotion: _reducedMotion,
|
|
429
|
+
portalTarget,
|
|
430
|
+
className,
|
|
431
|
+
ref
|
|
432
|
+
}) {
|
|
433
|
+
const panelRef = useRef2(null);
|
|
434
|
+
const titleId = "modal-title";
|
|
435
|
+
const descId = description ? "modal-desc" : void 0;
|
|
436
|
+
const { mounted, state } = usePresence(open);
|
|
437
|
+
useFocusTrap(panelRef, { active: open, onEscape: onClose });
|
|
438
|
+
useScrollLock(open);
|
|
439
|
+
useRouteClose({ active: open, closeOnRouteChange, onClose });
|
|
440
|
+
const portalNode = usePortal(portalTarget);
|
|
441
|
+
if (!mounted) return null;
|
|
442
|
+
const sizeClass = sizeMap[size];
|
|
443
|
+
const node = /* @__PURE__ */ jsxs4(
|
|
444
|
+
"div",
|
|
445
|
+
{
|
|
446
|
+
className: cn(
|
|
447
|
+
"fixed inset-0 z-[100] flex p-4 transition-opacity duration-200",
|
|
448
|
+
fullscreen ? "items-stretch justify-stretch" : "items-center justify-center",
|
|
449
|
+
state === "open" ? "opacity-100" : "opacity-0"
|
|
450
|
+
),
|
|
451
|
+
"aria-modal": "true",
|
|
452
|
+
role: "dialog",
|
|
453
|
+
"aria-labelledby": titleId,
|
|
454
|
+
"aria-describedby": descId,
|
|
455
|
+
"data-state": state,
|
|
456
|
+
children: [
|
|
457
|
+
/* @__PURE__ */ jsx5(
|
|
458
|
+
"div",
|
|
459
|
+
{
|
|
460
|
+
className: "absolute inset-0 bg-black/50",
|
|
461
|
+
onClick: closeOnBackdropClick ? onClose : void 0,
|
|
462
|
+
"aria-hidden": "true"
|
|
463
|
+
}
|
|
464
|
+
),
|
|
465
|
+
/* @__PURE__ */ jsxs4(
|
|
466
|
+
"div",
|
|
467
|
+
{
|
|
468
|
+
ref: (node2) => {
|
|
469
|
+
panelRef.current = node2;
|
|
470
|
+
if (typeof ref === "function") ref(node2);
|
|
471
|
+
else if (ref) ref.current = node2;
|
|
472
|
+
},
|
|
473
|
+
tabIndex: -1,
|
|
474
|
+
"data-state": state,
|
|
475
|
+
className: cn(
|
|
476
|
+
"relative z-[101] w-full border border-border bg-surface-raised shadow-xl flex flex-col",
|
|
477
|
+
"transition-all duration-200 focus-visible:outline-none",
|
|
478
|
+
state === "open" ? "scale-100 opacity-100" : "scale-95 opacity-0",
|
|
479
|
+
fullscreen ? "rounded-none max-w-none max-h-none h-full" : cn("rounded-xl", sizeClass),
|
|
480
|
+
className
|
|
481
|
+
),
|
|
482
|
+
children: [
|
|
483
|
+
/* @__PURE__ */ jsxs4("div", { className: "flex items-start justify-between gap-3 px-6 py-4 border-b border-border shrink-0", children: [
|
|
484
|
+
/* @__PURE__ */ jsxs4("div", { children: [
|
|
485
|
+
/* @__PURE__ */ jsx5("h2", { id: titleId, className: "text-base font-semibold text-text-primary", children: title }),
|
|
486
|
+
description && /* @__PURE__ */ jsx5("p", { id: descId, className: "text-sm text-text-secondary mt-0.5", children: description })
|
|
487
|
+
] }),
|
|
488
|
+
/* @__PURE__ */ jsx5(
|
|
489
|
+
"button",
|
|
490
|
+
{
|
|
491
|
+
type: "button",
|
|
492
|
+
onClick: onClose,
|
|
493
|
+
"aria-label": "Close dialog",
|
|
494
|
+
className: "shrink-0 text-text-disabled hover:text-text-primary transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus rounded",
|
|
495
|
+
children: /* @__PURE__ */ jsx5(FontAwesomeIcon3, { icon: faXmark2, className: "w-4 h-4", "aria-hidden": "true" })
|
|
496
|
+
}
|
|
497
|
+
)
|
|
498
|
+
] }),
|
|
499
|
+
children && /* @__PURE__ */ jsx5("div", { className: cn("px-6 py-4 flex-1", scrollable && "overflow-y-auto"), children }),
|
|
500
|
+
footer && /* @__PURE__ */ jsx5("div", { className: "px-6 py-4 border-t border-border flex justify-end gap-2 shrink-0", children: footer })
|
|
501
|
+
]
|
|
502
|
+
}
|
|
503
|
+
)
|
|
504
|
+
]
|
|
505
|
+
}
|
|
506
|
+
);
|
|
507
|
+
if (!portalNode) return null;
|
|
508
|
+
return createPortal2(node, portalNode);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// modules/ui/MultiSelect.tsx
|
|
512
|
+
import { useEffect as useEffect7, useMemo as useMemo3, useRef as useRef5, useState as useState5 } from "react";
|
|
513
|
+
import { FontAwesomeIcon as FontAwesomeIcon4 } from "@fortawesome/react-fontawesome";
|
|
514
|
+
import { faChevronUp, faChevronDown, faCheck, faXmark as faXmark3, faMagnifyingGlass } from "@fortawesome/free-solid-svg-icons";
|
|
515
|
+
|
|
516
|
+
// modules/ui/ComboBox/hooks/useFilter.ts
|
|
517
|
+
import { useMemo } from "react";
|
|
518
|
+
function filterOptions(options, query) {
|
|
519
|
+
const q = query.trim().toLowerCase();
|
|
520
|
+
if (!q) return options;
|
|
521
|
+
return options.filter((opt) => opt.label.toLowerCase().includes(q) || (opt.description ? opt.description.toLowerCase().includes(q) : false));
|
|
522
|
+
}
|
|
523
|
+
function useFilter(options, query) {
|
|
524
|
+
return useMemo(() => filterOptions(options, query), [options, query]);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// modules/ui/ComboBox/hooks/useAsync.ts
|
|
528
|
+
import { useCallback as useCallback2, useEffect as useEffect5, useMemo as useMemo2, useRef as useRef3, useState as useState3 } from "react";
|
|
529
|
+
var TTL_MS = 5 * 60 * 1e3;
|
|
530
|
+
function useAsync(enabled, query, onSearch, debounceMs = 300) {
|
|
531
|
+
const [results, setResults] = useState3(null);
|
|
532
|
+
const [loading, setLoading] = useState3(false);
|
|
533
|
+
const cacheRef = useRef3(/* @__PURE__ */ new Map());
|
|
534
|
+
const abortRef = useRef3(null);
|
|
535
|
+
const debounceRef = useRef3(null);
|
|
536
|
+
const cacheKey = query.trim().toLowerCase();
|
|
537
|
+
useEffect5(() => {
|
|
538
|
+
var _a;
|
|
539
|
+
if (!enabled || !onSearch) return;
|
|
540
|
+
const hit = cacheRef.current.get(cacheKey);
|
|
541
|
+
if (hit && Date.now() - hit.ts < TTL_MS) {
|
|
542
|
+
setResults(hit.data);
|
|
543
|
+
setLoading(false);
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
(_a = abortRef.current) == null ? void 0 : _a.abort();
|
|
547
|
+
if (debounceRef.current) clearTimeout(debounceRef.current);
|
|
548
|
+
const controller = new AbortController();
|
|
549
|
+
abortRef.current = controller;
|
|
550
|
+
setLoading(true);
|
|
551
|
+
debounceRef.current = setTimeout(() => {
|
|
552
|
+
let value;
|
|
553
|
+
try {
|
|
554
|
+
value = onSearch(query.trim(), controller.signal);
|
|
555
|
+
} catch (err) {
|
|
556
|
+
if (!controller.signal.aborted) {
|
|
557
|
+
setResults([]);
|
|
558
|
+
setLoading(false);
|
|
559
|
+
}
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
Promise.resolve(value).then((next) => {
|
|
563
|
+
if (controller.signal.aborted) return;
|
|
564
|
+
cacheRef.current.set(cacheKey, { ts: Date.now(), data: next });
|
|
565
|
+
setResults(next);
|
|
566
|
+
}).catch((err) => {
|
|
567
|
+
if (controller.signal.aborted) return;
|
|
568
|
+
const name = err == null ? void 0 : err.name;
|
|
569
|
+
if (name === "AbortError") return;
|
|
570
|
+
setResults([]);
|
|
571
|
+
}).finally(() => {
|
|
572
|
+
if (controller.signal.aborted) return;
|
|
573
|
+
setLoading(false);
|
|
574
|
+
});
|
|
575
|
+
}, debounceMs);
|
|
576
|
+
return () => {
|
|
577
|
+
if (debounceRef.current) clearTimeout(debounceRef.current);
|
|
578
|
+
controller.abort();
|
|
579
|
+
};
|
|
580
|
+
}, [enabled, cacheKey, query, onSearch, debounceMs]);
|
|
581
|
+
useEffect5(() => {
|
|
582
|
+
if (!enabled) {
|
|
583
|
+
setResults(null);
|
|
584
|
+
setLoading(false);
|
|
585
|
+
}
|
|
586
|
+
}, [enabled]);
|
|
587
|
+
const appendResults = useCallback2((next) => {
|
|
588
|
+
setResults((prev) => {
|
|
589
|
+
const merged = prev ? [...prev, ...next] : next;
|
|
590
|
+
cacheRef.current.set(cacheKey, { ts: Date.now(), data: merged });
|
|
591
|
+
return merged;
|
|
592
|
+
});
|
|
593
|
+
}, [cacheKey]);
|
|
594
|
+
return useMemo2(() => ({ results, loading, appendResults }), [results, loading, appendResults]);
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// modules/ui/ComboBox/hooks/useLoadMore.ts
|
|
598
|
+
import { useEffect as useEffect6, useRef as useRef4, useState as useState4 } from "react";
|
|
599
|
+
function useLoadMore(open, sentinelRef, onLoadMore, onAppend) {
|
|
600
|
+
const [loadingMore, setLoadingMore] = useState4(false);
|
|
601
|
+
const inFlightRef = useRef4(false);
|
|
602
|
+
useEffect6(() => {
|
|
603
|
+
var _a;
|
|
604
|
+
if (!open || !onLoadMore) return;
|
|
605
|
+
const node = sentinelRef.current;
|
|
606
|
+
if (!node || typeof IntersectionObserver === "undefined") return;
|
|
607
|
+
const io = new IntersectionObserver(
|
|
608
|
+
(entries) => {
|
|
609
|
+
const entry = entries[0];
|
|
610
|
+
if (!(entry == null ? void 0 : entry.isIntersecting) || inFlightRef.current) return;
|
|
611
|
+
inFlightRef.current = true;
|
|
612
|
+
setLoadingMore(true);
|
|
613
|
+
Promise.resolve(onLoadMore()).then((next) => {
|
|
614
|
+
if (next && next.length > 0) onAppend(next);
|
|
615
|
+
}).catch(() => {
|
|
616
|
+
}).finally(() => {
|
|
617
|
+
inFlightRef.current = false;
|
|
618
|
+
setLoadingMore(false);
|
|
619
|
+
});
|
|
620
|
+
},
|
|
621
|
+
{ root: (_a = node.closest("[data-combobox-list]")) != null ? _a : null, rootMargin: "40px" }
|
|
622
|
+
);
|
|
623
|
+
io.observe(node);
|
|
624
|
+
return () => io.disconnect();
|
|
625
|
+
}, [open, onLoadMore, onAppend, sentinelRef]);
|
|
626
|
+
return loadingMore;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// modules/ui/MultiSelect.tsx
|
|
630
|
+
import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
631
|
+
function MultiSelect({
|
|
632
|
+
id,
|
|
633
|
+
label,
|
|
634
|
+
options,
|
|
635
|
+
value,
|
|
636
|
+
onChange,
|
|
637
|
+
placeholder = "Select\u2026",
|
|
638
|
+
hint,
|
|
639
|
+
error,
|
|
640
|
+
disabled,
|
|
641
|
+
searchable,
|
|
642
|
+
className,
|
|
643
|
+
onSearch,
|
|
644
|
+
onLoadMore,
|
|
645
|
+
debounceMs = 300
|
|
646
|
+
}) {
|
|
647
|
+
const [internal, setInternal] = useState5(value != null ? value : []);
|
|
648
|
+
const [open, setOpen] = useState5(false);
|
|
649
|
+
const [search, setSearch] = useState5("");
|
|
650
|
+
const containerRef = useRef5(null);
|
|
651
|
+
const searchRef = useRef5(null);
|
|
652
|
+
const sentinelRef = useRef5(null);
|
|
653
|
+
const selected = value !== void 0 ? value : internal;
|
|
654
|
+
const { results: asyncResults, loading, appendResults } = useAsync(
|
|
655
|
+
open && !!onSearch,
|
|
656
|
+
search,
|
|
657
|
+
onSearch,
|
|
658
|
+
debounceMs
|
|
659
|
+
);
|
|
660
|
+
const loadingMore = useLoadMore(open, sentinelRef, onLoadMore, appendResults);
|
|
661
|
+
const sourceOptions = asyncResults != null ? asyncResults : options;
|
|
662
|
+
const localFiltered = useFilter(sourceOptions, searchable ? search : "");
|
|
663
|
+
const filtered = onSearch ? sourceOptions : searchable ? localFiltered : sourceOptions;
|
|
664
|
+
function toggle(v) {
|
|
665
|
+
const next = selected.includes(v) ? selected.filter((s) => s !== v) : [...selected, v];
|
|
666
|
+
if (value === void 0) setInternal(next);
|
|
667
|
+
onChange == null ? void 0 : onChange(next);
|
|
668
|
+
}
|
|
669
|
+
function remove(v, e) {
|
|
670
|
+
e.stopPropagation();
|
|
671
|
+
toggle(v);
|
|
672
|
+
}
|
|
673
|
+
useEffect7(() => {
|
|
674
|
+
if (!open) {
|
|
675
|
+
setSearch("");
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
if (searchable || onSearch) setTimeout(() => {
|
|
679
|
+
var _a;
|
|
680
|
+
return (_a = searchRef.current) == null ? void 0 : _a.focus();
|
|
681
|
+
}, 30);
|
|
682
|
+
function onOutside(e) {
|
|
683
|
+
if (containerRef.current && !containerRef.current.contains(e.target)) setOpen(false);
|
|
684
|
+
}
|
|
685
|
+
document.addEventListener("mousedown", onOutside);
|
|
686
|
+
return () => document.removeEventListener("mousedown", onOutside);
|
|
687
|
+
}, [open, searchable, onSearch]);
|
|
688
|
+
function onKeyDown(e) {
|
|
689
|
+
if (e.key === "Escape") setOpen(false);
|
|
690
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
691
|
+
e.preventDefault();
|
|
692
|
+
setOpen((o) => !o);
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
const hintId = hint ? `${id}-hint` : void 0;
|
|
696
|
+
const errorId = error ? `${id}-error` : void 0;
|
|
697
|
+
const describedBy = [hintId, errorId].filter(Boolean).join(" ") || void 0;
|
|
698
|
+
const listboxId = `${id}-listbox`;
|
|
699
|
+
const optionsById = useMemo3(() => {
|
|
700
|
+
const m = /* @__PURE__ */ new Map();
|
|
701
|
+
for (const o of options) m.set(o.value, o);
|
|
702
|
+
for (const o of sourceOptions) if (!m.has(o.value)) m.set(o.value, o);
|
|
703
|
+
return m;
|
|
704
|
+
}, [options, sourceOptions]);
|
|
705
|
+
return /* @__PURE__ */ jsxs5("div", { ref: containerRef, className: cn("space-y-1", className), children: [
|
|
706
|
+
/* @__PURE__ */ jsx6("label", { id: `${id}-label`, className: "block text-sm font-medium text-text-primary", children: label }),
|
|
707
|
+
/* @__PURE__ */ jsxs5("div", { className: "relative", children: [
|
|
708
|
+
/* @__PURE__ */ jsxs5(
|
|
709
|
+
"div",
|
|
710
|
+
{
|
|
711
|
+
role: "combobox",
|
|
712
|
+
tabIndex: disabled ? -1 : 0,
|
|
713
|
+
"aria-haspopup": "listbox",
|
|
714
|
+
"aria-expanded": open,
|
|
715
|
+
"aria-controls": listboxId,
|
|
716
|
+
"aria-autocomplete": "list",
|
|
717
|
+
"aria-labelledby": `${id}-label`,
|
|
718
|
+
"aria-describedby": describedBy,
|
|
719
|
+
"aria-disabled": disabled,
|
|
720
|
+
id,
|
|
721
|
+
onClick: () => !disabled && setOpen((o) => !o),
|
|
722
|
+
onKeyDown,
|
|
723
|
+
className: cn(
|
|
724
|
+
"min-h-[2.5rem] w-full rounded-md border px-3 py-1.5 text-sm transition-colors cursor-pointer",
|
|
725
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus",
|
|
726
|
+
"flex flex-wrap gap-1 items-center",
|
|
727
|
+
error ? "border-error ring-1 ring-error bg-error-subtle" : "border-border bg-surface-base",
|
|
728
|
+
disabled && "opacity-50 cursor-not-allowed bg-surface-sunken"
|
|
729
|
+
),
|
|
730
|
+
children: [
|
|
731
|
+
selected.length === 0 ? /* @__PURE__ */ jsx6("span", { className: "text-text-disabled", children: placeholder }) : selected.map((v) => {
|
|
732
|
+
var _a, _b;
|
|
733
|
+
const opt = optionsById.get(v);
|
|
734
|
+
return /* @__PURE__ */ jsxs5(
|
|
735
|
+
"span",
|
|
736
|
+
{
|
|
737
|
+
className: "inline-flex items-center gap-1 rounded-full bg-primary-subtle text-primary text-xs font-medium px-2 py-0.5",
|
|
738
|
+
children: [
|
|
739
|
+
(opt == null ? void 0 : opt.icon) && /* @__PURE__ */ jsx6("span", { className: "shrink-0", children: opt.icon }),
|
|
740
|
+
(_a = opt == null ? void 0 : opt.label) != null ? _a : v,
|
|
741
|
+
/* @__PURE__ */ jsx6(
|
|
742
|
+
"button",
|
|
743
|
+
{
|
|
744
|
+
type: "button",
|
|
745
|
+
"aria-label": `Remove ${(_b = opt == null ? void 0 : opt.label) != null ? _b : v}`,
|
|
746
|
+
onClick: (e) => remove(v, e),
|
|
747
|
+
className: "hover:opacity-70 focus-visible:outline-none",
|
|
748
|
+
children: /* @__PURE__ */ jsx6(FontAwesomeIcon4, { icon: faXmark3, className: "w-2.5 h-2.5" })
|
|
749
|
+
}
|
|
750
|
+
)
|
|
751
|
+
]
|
|
752
|
+
},
|
|
753
|
+
v
|
|
754
|
+
);
|
|
755
|
+
}),
|
|
756
|
+
/* @__PURE__ */ jsx6(
|
|
757
|
+
FontAwesomeIcon4,
|
|
758
|
+
{
|
|
759
|
+
icon: open ? faChevronUp : faChevronDown,
|
|
760
|
+
className: "ml-auto w-3 h-3 text-text-disabled",
|
|
761
|
+
"aria-hidden": "true"
|
|
762
|
+
}
|
|
763
|
+
)
|
|
764
|
+
]
|
|
765
|
+
}
|
|
766
|
+
),
|
|
767
|
+
open && /* @__PURE__ */ jsxs5("div", { className: "absolute z-20 w-full rounded-md border border-border bg-surface-raised shadow-lg overflow-hidden top-full left-0 mt-1", children: [
|
|
768
|
+
(searchable || onSearch) && /* @__PURE__ */ jsx6("div", { className: "p-2 border-b border-border", children: /* @__PURE__ */ jsxs5("div", { className: "relative", children: [
|
|
769
|
+
/* @__PURE__ */ jsx6(
|
|
770
|
+
FontAwesomeIcon4,
|
|
771
|
+
{
|
|
772
|
+
icon: faMagnifyingGlass,
|
|
773
|
+
"aria-hidden": "true",
|
|
774
|
+
className: "pointer-events-none absolute left-2 top-1/2 -translate-y-1/2 w-3 h-3 text-text-disabled"
|
|
775
|
+
}
|
|
776
|
+
),
|
|
777
|
+
/* @__PURE__ */ jsx6(
|
|
778
|
+
"input",
|
|
779
|
+
{
|
|
780
|
+
ref: searchRef,
|
|
781
|
+
type: "text",
|
|
782
|
+
value: search,
|
|
783
|
+
onChange: (e) => setSearch(e.target.value),
|
|
784
|
+
placeholder: "Search\u2026",
|
|
785
|
+
"aria-autocomplete": "list",
|
|
786
|
+
"aria-controls": listboxId,
|
|
787
|
+
className: cn(
|
|
788
|
+
"block w-full rounded-md border border-border bg-surface-base pl-7 pr-3 py-1.5 text-sm",
|
|
789
|
+
"text-text-primary placeholder:text-text-disabled",
|
|
790
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus"
|
|
791
|
+
)
|
|
792
|
+
}
|
|
793
|
+
)
|
|
794
|
+
] }) }),
|
|
795
|
+
/* @__PURE__ */ jsx6(
|
|
796
|
+
"ul",
|
|
797
|
+
{
|
|
798
|
+
id: listboxId,
|
|
799
|
+
role: "listbox",
|
|
800
|
+
"aria-labelledby": `${id}-label`,
|
|
801
|
+
"aria-multiselectable": "true",
|
|
802
|
+
"data-combobox-list": true,
|
|
803
|
+
className: "py-1 max-h-48 overflow-y-auto",
|
|
804
|
+
children: loading ? Array.from({ length: 3 }).map((_, i) => /* @__PURE__ */ jsx6("li", { className: "px-3 py-2", "aria-hidden": "true", children: /* @__PURE__ */ jsx6("div", { className: "h-3 w-full animate-pulse rounded bg-surface-overlay" }) }, `sk-${i}`)) : filtered.length === 0 ? /* @__PURE__ */ jsx6("li", { className: "px-3 py-4 text-sm text-center text-text-secondary", children: "No results found." }) : /* @__PURE__ */ jsxs5(Fragment2, { children: [
|
|
805
|
+
filtered.map((opt) => {
|
|
806
|
+
const checked = selected.includes(opt.value);
|
|
807
|
+
return /* @__PURE__ */ jsxs5(
|
|
808
|
+
"li",
|
|
809
|
+
{
|
|
810
|
+
role: "option",
|
|
811
|
+
"aria-selected": checked,
|
|
812
|
+
"aria-disabled": opt.disabled,
|
|
813
|
+
onClick: () => !opt.disabled && toggle(opt.value),
|
|
814
|
+
onKeyDown: (e) => {
|
|
815
|
+
if ((e.key === "Enter" || e.key === " ") && !opt.disabled) {
|
|
816
|
+
e.preventDefault();
|
|
817
|
+
toggle(opt.value);
|
|
818
|
+
}
|
|
819
|
+
},
|
|
820
|
+
tabIndex: opt.disabled ? -1 : 0,
|
|
821
|
+
className: cn(
|
|
822
|
+
"flex items-center gap-2 px-3 py-2 text-sm cursor-pointer select-none",
|
|
823
|
+
"hover:bg-surface-overlay transition-colors",
|
|
824
|
+
"focus-visible:outline-none focus-visible:bg-surface-overlay",
|
|
825
|
+
checked && "text-primary font-medium",
|
|
826
|
+
opt.disabled && "opacity-50 cursor-not-allowed"
|
|
827
|
+
),
|
|
828
|
+
children: [
|
|
829
|
+
/* @__PURE__ */ jsx6(
|
|
830
|
+
"span",
|
|
831
|
+
{
|
|
832
|
+
"aria-hidden": "true",
|
|
833
|
+
className: cn(
|
|
834
|
+
"h-4 w-4 rounded border-2 flex items-center justify-center shrink-0 text-[10px]",
|
|
835
|
+
checked ? "bg-primary border-primary text-primary-fg" : "border-border bg-surface-base"
|
|
836
|
+
),
|
|
837
|
+
children: checked && /* @__PURE__ */ jsx6(FontAwesomeIcon4, { icon: faCheck, className: "w-2.5 h-2.5" })
|
|
838
|
+
}
|
|
839
|
+
),
|
|
840
|
+
opt.icon && /* @__PURE__ */ jsx6("span", { className: "shrink-0", "aria-hidden": "true", children: opt.icon }),
|
|
841
|
+
opt.label
|
|
842
|
+
]
|
|
843
|
+
},
|
|
844
|
+
opt.value
|
|
845
|
+
);
|
|
846
|
+
}),
|
|
847
|
+
onLoadMore && /* @__PURE__ */ jsx6("li", { ref: sentinelRef, "aria-hidden": "true", "data-combobox-sentinel": true, className: "h-1" }),
|
|
848
|
+
loadingMore && /* @__PURE__ */ jsx6("li", { className: "px-3 py-2 text-xs text-text-secondary", "aria-live": "polite", children: "Loading more\u2026" })
|
|
849
|
+
] })
|
|
850
|
+
}
|
|
851
|
+
)
|
|
852
|
+
] })
|
|
853
|
+
] }),
|
|
854
|
+
hint && !error && /* @__PURE__ */ jsx6("p", { id: hintId, className: "text-xs text-text-secondary", children: hint }),
|
|
855
|
+
error && /* @__PURE__ */ jsx6("p", { id: errorId, className: "text-xs text-error", role: "alert", children: error })
|
|
856
|
+
] });
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
// modules/ui/Skeleton.tsx
|
|
860
|
+
import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
861
|
+
var base = "animate-pulse bg-surface-sunken";
|
|
862
|
+
function SkeletonLine({ width = "w-full", className }) {
|
|
863
|
+
return /* @__PURE__ */ jsx7("div", { className: cn(base, "h-3 rounded", width, className) });
|
|
864
|
+
}
|
|
865
|
+
function SkeletonAvatar({ size = "md", className }) {
|
|
866
|
+
const s = { sm: "h-8 w-8", md: "h-10 w-10", lg: "h-12 w-12" }[size];
|
|
867
|
+
return /* @__PURE__ */ jsx7("div", { className: cn(base, "rounded-full shrink-0", s, className) });
|
|
868
|
+
}
|
|
869
|
+
function SkeletonText({ lines = 3, className }) {
|
|
870
|
+
return /* @__PURE__ */ jsx7("div", { className: cn("space-y-2", className), "aria-busy": "true", "aria-label": "Loading content", children: Array.from({ length: lines }).map((_, i) => /* @__PURE__ */ jsx7(SkeletonLine, { width: i === lines - 1 ? "w-4/5" : "w-full" }, i)) });
|
|
871
|
+
}
|
|
872
|
+
function SkeletonCard({ className }) {
|
|
873
|
+
return /* @__PURE__ */ jsxs6(
|
|
874
|
+
"div",
|
|
875
|
+
{
|
|
876
|
+
className: cn("bg-surface-raised border border-border rounded-xl p-6 space-y-4", className),
|
|
877
|
+
"aria-busy": "true",
|
|
878
|
+
"aria-label": "Loading content",
|
|
879
|
+
children: [
|
|
880
|
+
/* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-3", children: [
|
|
881
|
+
/* @__PURE__ */ jsx7(SkeletonAvatar, {}),
|
|
882
|
+
/* @__PURE__ */ jsxs6("div", { className: "flex-1 space-y-2", children: [
|
|
883
|
+
/* @__PURE__ */ jsx7(SkeletonLine, { width: "w-2/3" }),
|
|
884
|
+
/* @__PURE__ */ jsx7(SkeletonLine, { width: "w-1/2" })
|
|
885
|
+
] })
|
|
886
|
+
] }),
|
|
887
|
+
/* @__PURE__ */ jsx7(SkeletonText, { lines: 3 }),
|
|
888
|
+
/* @__PURE__ */ jsxs6("div", { className: "flex justify-between", children: [
|
|
889
|
+
/* @__PURE__ */ jsx7("div", { className: cn(base, "h-6 w-16 rounded") }),
|
|
890
|
+
/* @__PURE__ */ jsx7("div", { className: cn(base, "h-6 w-20 rounded") })
|
|
891
|
+
] })
|
|
892
|
+
]
|
|
893
|
+
}
|
|
894
|
+
);
|
|
895
|
+
}
|
|
896
|
+
function SkeletonTableRow({ cols = 4, className }) {
|
|
897
|
+
const widths = ["w-28", "w-40", "w-20", "w-16"];
|
|
898
|
+
return /* @__PURE__ */ jsx7("tr", { className: cn("border-b border-border", className), children: Array.from({ length: cols }).map((_, i) => {
|
|
899
|
+
var _a;
|
|
900
|
+
return /* @__PURE__ */ jsx7("td", { className: "px-4 py-3", children: /* @__PURE__ */ jsx7("div", { className: cn(base, "h-4 rounded", (_a = widths[i]) != null ? _a : "w-24") }) }, i);
|
|
901
|
+
}) });
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
// modules/ui/Stepper.tsx
|
|
905
|
+
import { FontAwesomeIcon as FontAwesomeIcon5 } from "@fortawesome/react-fontawesome";
|
|
906
|
+
import { faCheck as faCheck2, faXmark as faXmark4 } from "@fortawesome/free-solid-svg-icons";
|
|
907
|
+
import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
908
|
+
var stateStyles = {
|
|
909
|
+
complete: {
|
|
910
|
+
circle: "bg-success text-text-inverse border-success",
|
|
911
|
+
text: "text-text-primary",
|
|
912
|
+
line: "bg-success"
|
|
913
|
+
},
|
|
914
|
+
active: {
|
|
915
|
+
circle: "bg-primary text-primary-fg border-primary",
|
|
916
|
+
text: "text-text-primary font-semibold",
|
|
917
|
+
line: "bg-border"
|
|
918
|
+
},
|
|
919
|
+
error: {
|
|
920
|
+
circle: "bg-error text-text-inverse border-error",
|
|
921
|
+
text: "text-error-fg",
|
|
922
|
+
line: "bg-border"
|
|
923
|
+
},
|
|
924
|
+
pending: {
|
|
925
|
+
circle: "bg-surface-base text-text-disabled border-border",
|
|
926
|
+
text: "text-text-disabled",
|
|
927
|
+
line: "bg-border"
|
|
928
|
+
}
|
|
929
|
+
};
|
|
930
|
+
function StepIcon({ state, index }) {
|
|
931
|
+
if (state === "complete") return /* @__PURE__ */ jsx8(FontAwesomeIcon5, { icon: faCheck2, className: "w-3.5 h-3.5", "aria-hidden": "true" });
|
|
932
|
+
if (state === "error") return /* @__PURE__ */ jsx8(FontAwesomeIcon5, { icon: faXmark4, className: "w-3.5 h-3.5", "aria-hidden": "true" });
|
|
933
|
+
return /* @__PURE__ */ jsx8("span", { children: index + 1 });
|
|
934
|
+
}
|
|
935
|
+
function Stepper({
|
|
936
|
+
steps,
|
|
937
|
+
orientation = "horizontal",
|
|
938
|
+
className
|
|
939
|
+
}) {
|
|
940
|
+
if (orientation === "vertical") {
|
|
941
|
+
return /* @__PURE__ */ jsx8("ol", { className: cn("flex flex-col gap-0", className), children: steps.map((step, i) => {
|
|
942
|
+
var _a;
|
|
943
|
+
const state = (_a = step.state) != null ? _a : "pending";
|
|
944
|
+
const s = stateStyles[state];
|
|
945
|
+
const isLast = i === steps.length - 1;
|
|
946
|
+
return /* @__PURE__ */ jsxs7("li", { className: "flex gap-3 items-start", children: [
|
|
947
|
+
/* @__PURE__ */ jsxs7("div", { className: "flex flex-col items-center shrink-0", children: [
|
|
948
|
+
/* @__PURE__ */ jsx8(
|
|
949
|
+
"div",
|
|
950
|
+
{
|
|
951
|
+
className: cn(
|
|
952
|
+
"h-8 w-8 rounded-full border-2 flex items-center justify-center text-xs font-bold shrink-0",
|
|
953
|
+
s.circle
|
|
954
|
+
),
|
|
955
|
+
"aria-label": `Step ${i + 1}: ${step.label} \u2014 ${state}`,
|
|
956
|
+
children: /* @__PURE__ */ jsx8(StepIcon, { state, index: i })
|
|
957
|
+
}
|
|
958
|
+
),
|
|
959
|
+
!isLast && /* @__PURE__ */ jsx8("div", { className: cn("w-0.5 flex-1 min-h-[2rem] mt-1", s.line), "aria-hidden": "true" })
|
|
960
|
+
] }),
|
|
961
|
+
/* @__PURE__ */ jsxs7("div", { className: cn("pb-6", isLast && "pb-0"), children: [
|
|
962
|
+
/* @__PURE__ */ jsx8("p", { className: cn("text-sm", s.text), children: step.label }),
|
|
963
|
+
step.description && /* @__PURE__ */ jsx8("p", { className: "text-xs text-text-secondary mt-0.5", children: step.description })
|
|
964
|
+
] })
|
|
965
|
+
] }, i);
|
|
966
|
+
}) });
|
|
967
|
+
}
|
|
968
|
+
return /* @__PURE__ */ jsx8("ol", { className: cn("flex items-center", className), children: steps.map((step, i) => {
|
|
969
|
+
var _a, _b;
|
|
970
|
+
const state = (_a = step.state) != null ? _a : "pending";
|
|
971
|
+
const s = stateStyles[state];
|
|
972
|
+
const isLast = i === steps.length - 1;
|
|
973
|
+
return /* @__PURE__ */ jsxs7("li", { className: cn("flex items-center", !isLast && "flex-1"), children: [
|
|
974
|
+
/* @__PURE__ */ jsxs7("div", { className: "flex flex-col items-center gap-1 shrink-0", children: [
|
|
975
|
+
/* @__PURE__ */ jsx8(
|
|
976
|
+
"div",
|
|
977
|
+
{
|
|
978
|
+
className: cn(
|
|
979
|
+
"h-8 w-8 rounded-full border-2 flex items-center justify-center text-xs font-bold",
|
|
980
|
+
s.circle
|
|
981
|
+
),
|
|
982
|
+
"aria-label": `Step ${i + 1}: ${step.label} \u2014 ${state}`,
|
|
983
|
+
children: /* @__PURE__ */ jsx8(StepIcon, { state, index: i })
|
|
984
|
+
}
|
|
985
|
+
),
|
|
986
|
+
/* @__PURE__ */ jsxs7("div", { className: "text-center", children: [
|
|
987
|
+
/* @__PURE__ */ jsx8("p", { className: cn("text-xs whitespace-nowrap", s.text), children: step.label }),
|
|
988
|
+
step.description && /* @__PURE__ */ jsx8("p", { className: "text-xs text-text-secondary", children: step.description })
|
|
989
|
+
] })
|
|
990
|
+
] }),
|
|
991
|
+
!isLast && /* @__PURE__ */ jsx8(
|
|
992
|
+
"div",
|
|
993
|
+
{
|
|
994
|
+
className: cn("h-0.5 flex-1 mx-2 mt-[-1.25rem]", stateStyles[(_b = steps[i].state) != null ? _b : "pending"].line),
|
|
995
|
+
"aria-hidden": "true"
|
|
996
|
+
}
|
|
997
|
+
)
|
|
998
|
+
] }, i);
|
|
999
|
+
}) });
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
// modules/ui/Toast/hooks/useToastStore.ts
|
|
1003
|
+
import { create } from "zustand";
|
|
1004
|
+
var VARIANT_DURATION = {
|
|
1005
|
+
success: 5e3,
|
|
1006
|
+
info: 5e3,
|
|
1007
|
+
warning: 5e3,
|
|
1008
|
+
error: null,
|
|
1009
|
+
// persistent — user must dismiss / be replaced via promise()
|
|
1010
|
+
loading: null
|
|
1011
|
+
// persistent — resolves via toast.promise / toast.update
|
|
1012
|
+
};
|
|
1013
|
+
function getEffectiveDuration(item) {
|
|
1014
|
+
if (item.duration === 0) return null;
|
|
1015
|
+
if (item.duration !== void 0) return item.duration;
|
|
1016
|
+
return VARIANT_DURATION[item.variant];
|
|
1017
|
+
}
|
|
1018
|
+
var useToastStore = create((set) => ({
|
|
1019
|
+
toasts: [],
|
|
1020
|
+
max: 5,
|
|
1021
|
+
add: (item) => {
|
|
1022
|
+
const id = typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `toast-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
1023
|
+
set((s) => {
|
|
1024
|
+
const next = [...s.toasts, __spreadProps(__spreadValues({ closeButton: true }, item), { id })];
|
|
1025
|
+
while (next.length > s.max) next.shift();
|
|
1026
|
+
return { toasts: next };
|
|
1027
|
+
});
|
|
1028
|
+
return id;
|
|
1029
|
+
},
|
|
1030
|
+
update: (id, patch) => set((s) => ({
|
|
1031
|
+
toasts: s.toasts.map((t) => t.id === id ? __spreadValues(__spreadValues({}, t), patch) : t)
|
|
1032
|
+
})),
|
|
1033
|
+
remove: (id) => set((s) => ({ toasts: s.toasts.filter((t) => t.id !== id) })),
|
|
1034
|
+
clear: () => set({ toasts: [] }),
|
|
1035
|
+
setMax: (max) => set({ max: Math.max(1, max) })
|
|
1036
|
+
}));
|
|
1037
|
+
|
|
1038
|
+
// modules/ui/Toast/index.tsx
|
|
1039
|
+
import { useEffect as useEffect9, useMemo as useMemo4 } from "react";
|
|
1040
|
+
|
|
1041
|
+
// modules/ui/Toast/parts/ToastItem.tsx
|
|
1042
|
+
import { useCallback as useCallback3, useEffect as useEffect8, useRef as useRef6, useState as useState6 } from "react";
|
|
1043
|
+
import { FontAwesomeIcon as FontAwesomeIcon6 } from "@fortawesome/react-fontawesome";
|
|
1044
|
+
import {
|
|
1045
|
+
faCircleCheck,
|
|
1046
|
+
faTriangleExclamation,
|
|
1047
|
+
faCircleXmark,
|
|
1048
|
+
faCircleInfo,
|
|
1049
|
+
faSpinner,
|
|
1050
|
+
faXmark as faXmark5
|
|
1051
|
+
} from "@fortawesome/free-solid-svg-icons";
|
|
1052
|
+
|
|
1053
|
+
// modules/ui/Toast/parts/ProgressBar.tsx
|
|
1054
|
+
import { jsx as jsx9 } from "react/jsx-runtime";
|
|
1055
|
+
function ProgressBar({ progress, colorClass }) {
|
|
1056
|
+
return /* @__PURE__ */ jsx9("div", { className: "absolute bottom-0 left-0 right-0 h-0.5 bg-black/5", children: /* @__PURE__ */ jsx9(
|
|
1057
|
+
"div",
|
|
1058
|
+
{
|
|
1059
|
+
className: cn("h-full rounded-full transition-none", colorClass),
|
|
1060
|
+
style: { width: `${progress}%`, opacity: 0.5 },
|
|
1061
|
+
"aria-hidden": "true"
|
|
1062
|
+
}
|
|
1063
|
+
) });
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
// modules/ui/Toast/parts/ToastItem.tsx
|
|
1067
|
+
import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1068
|
+
var variantMap = {
|
|
1069
|
+
success: {
|
|
1070
|
+
container: "bg-success-subtle border-success",
|
|
1071
|
+
iconColor: "text-success-fg",
|
|
1072
|
+
progressColor: "bg-success",
|
|
1073
|
+
defaultIcon: /* @__PURE__ */ jsx10(FontAwesomeIcon6, { icon: faCircleCheck, className: "size-4 shrink-0" })
|
|
1074
|
+
},
|
|
1075
|
+
warning: {
|
|
1076
|
+
container: "bg-warning-subtle border-warning",
|
|
1077
|
+
iconColor: "text-warning",
|
|
1078
|
+
progressColor: "bg-warning",
|
|
1079
|
+
defaultIcon: /* @__PURE__ */ jsx10(FontAwesomeIcon6, { icon: faTriangleExclamation, className: "size-4 shrink-0" })
|
|
1080
|
+
},
|
|
1081
|
+
error: {
|
|
1082
|
+
container: "bg-error-subtle border-error",
|
|
1083
|
+
iconColor: "text-error",
|
|
1084
|
+
progressColor: "bg-error",
|
|
1085
|
+
defaultIcon: /* @__PURE__ */ jsx10(FontAwesomeIcon6, { icon: faCircleXmark, className: "size-4 shrink-0" })
|
|
1086
|
+
},
|
|
1087
|
+
info: {
|
|
1088
|
+
container: "bg-info-subtle border-info",
|
|
1089
|
+
iconColor: "text-info",
|
|
1090
|
+
progressColor: "bg-info",
|
|
1091
|
+
defaultIcon: /* @__PURE__ */ jsx10(FontAwesomeIcon6, { icon: faCircleInfo, className: "size-4 shrink-0" })
|
|
1092
|
+
},
|
|
1093
|
+
loading: {
|
|
1094
|
+
container: "bg-surface-raised border-border",
|
|
1095
|
+
iconColor: "text-text-secondary",
|
|
1096
|
+
progressColor: "bg-primary",
|
|
1097
|
+
defaultIcon: /* @__PURE__ */ jsx10(FontAwesomeIcon6, { icon: faSpinner, className: "size-4 shrink-0 animate-spin" })
|
|
1098
|
+
}
|
|
1099
|
+
};
|
|
1100
|
+
function ariaFor(variant) {
|
|
1101
|
+
if (variant === "warning" || variant === "error") return { role: "alert", live: "assertive" };
|
|
1102
|
+
return { role: "status", live: "polite" };
|
|
1103
|
+
}
|
|
1104
|
+
var TICK_MS = 50;
|
|
1105
|
+
var EXIT_MS2 = 250;
|
|
1106
|
+
function ToastItem({ item, onRemove, reducedMotion = false }) {
|
|
1107
|
+
var _a;
|
|
1108
|
+
const duration = getEffectiveDuration(item);
|
|
1109
|
+
const hasDuration = duration !== null;
|
|
1110
|
+
const [progress, setProgress] = useState6(100);
|
|
1111
|
+
const [paused, setPaused] = useState6(false);
|
|
1112
|
+
const [show, setShow] = useState6(reducedMotion);
|
|
1113
|
+
const [exiting, setExiting] = useState6(false);
|
|
1114
|
+
const remainingRef = useRef6(duration != null ? duration : 0);
|
|
1115
|
+
const lastTickRef = useRef6(0);
|
|
1116
|
+
useEffect8(() => {
|
|
1117
|
+
if (reducedMotion) return;
|
|
1118
|
+
const id = requestAnimationFrame(() => setShow(true));
|
|
1119
|
+
return () => cancelAnimationFrame(id);
|
|
1120
|
+
}, [reducedMotion]);
|
|
1121
|
+
const dismiss = useCallback3(() => {
|
|
1122
|
+
setExiting(true);
|
|
1123
|
+
setTimeout(onRemove, reducedMotion ? 0 : EXIT_MS2);
|
|
1124
|
+
}, [onRemove, reducedMotion]);
|
|
1125
|
+
useEffect8(() => {
|
|
1126
|
+
if (!hasDuration || paused || exiting) return;
|
|
1127
|
+
lastTickRef.current = Date.now();
|
|
1128
|
+
const id = setInterval(() => {
|
|
1129
|
+
const elapsed = Date.now() - lastTickRef.current;
|
|
1130
|
+
lastTickRef.current = Date.now();
|
|
1131
|
+
remainingRef.current = Math.max(0, remainingRef.current - elapsed);
|
|
1132
|
+
setProgress(remainingRef.current / duration * 100);
|
|
1133
|
+
if (remainingRef.current <= 0) {
|
|
1134
|
+
clearInterval(id);
|
|
1135
|
+
dismiss();
|
|
1136
|
+
}
|
|
1137
|
+
}, TICK_MS);
|
|
1138
|
+
return () => clearInterval(id);
|
|
1139
|
+
}, [hasDuration, paused, exiting, duration, dismiss]);
|
|
1140
|
+
useEffect8(() => {
|
|
1141
|
+
if (!hasDuration) return;
|
|
1142
|
+
const handler = () => setPaused(document.hidden);
|
|
1143
|
+
document.addEventListener("visibilitychange", handler);
|
|
1144
|
+
return () => document.removeEventListener("visibilitychange", handler);
|
|
1145
|
+
}, [hasDuration]);
|
|
1146
|
+
useEffect8(() => {
|
|
1147
|
+
remainingRef.current = duration != null ? duration : 0;
|
|
1148
|
+
setProgress(100);
|
|
1149
|
+
setExiting(false);
|
|
1150
|
+
}, [duration]);
|
|
1151
|
+
const { container, iconColor, progressColor, defaultIcon } = variantMap[item.variant];
|
|
1152
|
+
const icon = (_a = item.icon) != null ? _a : defaultIcon;
|
|
1153
|
+
const showClose = item.closeButton !== false;
|
|
1154
|
+
const { role, live } = ariaFor(item.variant);
|
|
1155
|
+
return /* @__PURE__ */ jsxs8(
|
|
1156
|
+
"div",
|
|
1157
|
+
{
|
|
1158
|
+
role,
|
|
1159
|
+
"aria-live": live,
|
|
1160
|
+
onMouseEnter: () => hasDuration && setPaused(true),
|
|
1161
|
+
onMouseLeave: () => hasDuration && setPaused(false),
|
|
1162
|
+
className: cn(
|
|
1163
|
+
"relative w-80 rounded-xl border shadow-lg overflow-hidden pointer-events-auto",
|
|
1164
|
+
reducedMotion ? "" : "transition-all duration-250 ease-out",
|
|
1165
|
+
show && !exiting ? "opacity-100 translate-y-0 scale-100" : "opacity-0 translate-y-3 scale-95",
|
|
1166
|
+
container
|
|
1167
|
+
),
|
|
1168
|
+
children: [
|
|
1169
|
+
/* @__PURE__ */ jsxs8("div", { className: "flex items-start gap-3 px-4 pt-4 pb-3", children: [
|
|
1170
|
+
/* @__PURE__ */ jsx10("span", { className: cn("mt-0.5", iconColor), "aria-hidden": "true", children: icon }),
|
|
1171
|
+
/* @__PURE__ */ jsxs8("div", { className: "flex-1 min-w-0", children: [
|
|
1172
|
+
item.title && /* @__PURE__ */ jsx10("p", { className: "text-sm font-semibold text-text-primary leading-snug", children: item.title }),
|
|
1173
|
+
/* @__PURE__ */ jsx10("p", { className: cn("text-sm text-text-secondary leading-snug", item.title && "mt-0.5"), children: item.message }),
|
|
1174
|
+
item.actions && item.actions.length > 0 && /* @__PURE__ */ jsx10("div", { className: "flex flex-wrap gap-x-3 gap-y-1 mt-2.5", children: item.actions.map((action, i) => /* @__PURE__ */ jsx10(
|
|
1175
|
+
"button",
|
|
1176
|
+
{
|
|
1177
|
+
type: "button",
|
|
1178
|
+
onClick: () => action.onClick(dismiss),
|
|
1179
|
+
className: cn(
|
|
1180
|
+
"text-xs font-semibold rounded transition-opacity hover:opacity-70",
|
|
1181
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus",
|
|
1182
|
+
action.variant === "danger" ? "text-error" : "text-text-primary underline underline-offset-2"
|
|
1183
|
+
),
|
|
1184
|
+
children: action.label
|
|
1185
|
+
},
|
|
1186
|
+
i
|
|
1187
|
+
)) })
|
|
1188
|
+
] }),
|
|
1189
|
+
showClose && /* @__PURE__ */ jsx10(
|
|
1190
|
+
"button",
|
|
1191
|
+
{
|
|
1192
|
+
type: "button",
|
|
1193
|
+
"aria-label": "Dismiss",
|
|
1194
|
+
onClick: dismiss,
|
|
1195
|
+
className: cn(
|
|
1196
|
+
"shrink-0 mt-0.5 rounded text-text-secondary hover:text-text-primary transition-colors",
|
|
1197
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus"
|
|
1198
|
+
),
|
|
1199
|
+
children: /* @__PURE__ */ jsx10(FontAwesomeIcon6, { icon: faXmark5, className: "size-3.5" })
|
|
1200
|
+
}
|
|
1201
|
+
)
|
|
1202
|
+
] }),
|
|
1203
|
+
hasDuration && /* @__PURE__ */ jsx10(ProgressBar, { progress, colorClass: progressColor })
|
|
1204
|
+
]
|
|
1205
|
+
}
|
|
1206
|
+
);
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
// modules/ui/Toast/parts/Region.tsx
|
|
1210
|
+
import { jsx as jsx11 } from "react/jsx-runtime";
|
|
1211
|
+
var positionMap = {
|
|
1212
|
+
"top-right": "top-4 right-4 items-end",
|
|
1213
|
+
"top-left": "top-4 left-4 items-start",
|
|
1214
|
+
"top-center": "top-4 left-1/2 -translate-x-1/2 items-center",
|
|
1215
|
+
"bottom-right": "bottom-4 right-4 items-end",
|
|
1216
|
+
"bottom-left": "bottom-4 left-4 items-start",
|
|
1217
|
+
"bottom-center": "bottom-4 left-1/2 -translate-x-1/2 items-center"
|
|
1218
|
+
};
|
|
1219
|
+
function Region({
|
|
1220
|
+
position,
|
|
1221
|
+
items,
|
|
1222
|
+
onRemove,
|
|
1223
|
+
gap = 2,
|
|
1224
|
+
reducedMotion,
|
|
1225
|
+
className
|
|
1226
|
+
}) {
|
|
1227
|
+
const ordered = position.startsWith("bottom") ? [...items].reverse() : items;
|
|
1228
|
+
return /* @__PURE__ */ jsx11(
|
|
1229
|
+
"div",
|
|
1230
|
+
{
|
|
1231
|
+
className: cn(
|
|
1232
|
+
"fixed z-[90] flex flex-col pointer-events-none",
|
|
1233
|
+
positionMap[position],
|
|
1234
|
+
className
|
|
1235
|
+
),
|
|
1236
|
+
style: { gap: `${gap * 0.25}rem` },
|
|
1237
|
+
children: ordered.map((t) => /* @__PURE__ */ jsx11(
|
|
1238
|
+
ToastItem,
|
|
1239
|
+
{
|
|
1240
|
+
item: t,
|
|
1241
|
+
position,
|
|
1242
|
+
reducedMotion,
|
|
1243
|
+
onRemove: () => onRemove(t.id)
|
|
1244
|
+
},
|
|
1245
|
+
t.id
|
|
1246
|
+
))
|
|
1247
|
+
}
|
|
1248
|
+
);
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
// modules/ui/Toast/index.tsx
|
|
1252
|
+
import { Fragment as Fragment3, jsx as jsx12 } from "react/jsx-runtime";
|
|
1253
|
+
function _add(item) {
|
|
1254
|
+
return useToastStore.getState().add(item);
|
|
1255
|
+
}
|
|
1256
|
+
function _base(message, opts) {
|
|
1257
|
+
return _add(__spreadValues({ variant: "info", message }, opts));
|
|
1258
|
+
}
|
|
1259
|
+
var _toast = _base;
|
|
1260
|
+
_toast.success = (message, opts) => _add(__spreadValues({ variant: "success", message }, opts));
|
|
1261
|
+
_toast.error = (message, opts) => _add(__spreadValues({ variant: "error", message }, opts));
|
|
1262
|
+
_toast.warning = (message, opts) => _add(__spreadValues({ variant: "warning", message }, opts));
|
|
1263
|
+
_toast.info = (message, opts) => _add(__spreadValues({ variant: "info", message }, opts));
|
|
1264
|
+
_toast.loading = (message, opts) => _add(__spreadValues({ variant: "loading", message }, opts));
|
|
1265
|
+
_toast.update = (id, patch) => useToastStore.getState().update(id, patch);
|
|
1266
|
+
_toast.dismiss = (id) => useToastStore.getState().remove(id);
|
|
1267
|
+
_toast.clear = () => useToastStore.getState().clear();
|
|
1268
|
+
_toast.promise = (promise, messages, opts) => {
|
|
1269
|
+
const id = _add(__spreadValues({ variant: "loading", message: messages.loading }, opts));
|
|
1270
|
+
promise.then((data) => useToastStore.getState().update(id, {
|
|
1271
|
+
variant: "success",
|
|
1272
|
+
message: typeof messages.success === "function" ? messages.success(data) : messages.success,
|
|
1273
|
+
// Reset to variant default duration when resolving from loading.
|
|
1274
|
+
duration: void 0
|
|
1275
|
+
})).catch((err) => useToastStore.getState().update(id, {
|
|
1276
|
+
variant: "error",
|
|
1277
|
+
message: typeof messages.error === "function" ? messages.error(err) : messages.error,
|
|
1278
|
+
duration: void 0
|
|
1279
|
+
}));
|
|
1280
|
+
return id;
|
|
1281
|
+
};
|
|
1282
|
+
var toast = _toast;
|
|
1283
|
+
function Toaster({
|
|
1284
|
+
position = "top-right",
|
|
1285
|
+
max = 5,
|
|
1286
|
+
gap = 2,
|
|
1287
|
+
reducedMotion = false,
|
|
1288
|
+
messages: _messages
|
|
1289
|
+
} = {}) {
|
|
1290
|
+
var _a;
|
|
1291
|
+
const toasts = useToastStore((s) => s.toasts);
|
|
1292
|
+
const remove = useToastStore((s) => s.remove);
|
|
1293
|
+
const setMax = useToastStore((s) => s.setMax);
|
|
1294
|
+
useEffect9(() => {
|
|
1295
|
+
setMax(max);
|
|
1296
|
+
}, [max, setMax]);
|
|
1297
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
1298
|
+
for (const t of toasts) {
|
|
1299
|
+
const pos = (_a = t.position) != null ? _a : position;
|
|
1300
|
+
const bucket = buckets.get(pos);
|
|
1301
|
+
if (bucket) bucket.push(t);
|
|
1302
|
+
else buckets.set(pos, [t]);
|
|
1303
|
+
}
|
|
1304
|
+
if (!buckets.has(position)) buckets.set(position, []);
|
|
1305
|
+
return /* @__PURE__ */ jsx12(Fragment3, { children: Array.from(buckets.entries()).map(([pos, items]) => /* @__PURE__ */ jsx12(
|
|
1306
|
+
Region,
|
|
1307
|
+
{
|
|
1308
|
+
position: pos,
|
|
1309
|
+
items,
|
|
1310
|
+
gap,
|
|
1311
|
+
reducedMotion,
|
|
1312
|
+
onRemove: remove
|
|
1313
|
+
},
|
|
1314
|
+
pos
|
|
1315
|
+
)) });
|
|
1316
|
+
}
|
|
1317
|
+
var ToastProvider = Toaster;
|
|
1318
|
+
function ToastRegion({
|
|
1319
|
+
children,
|
|
1320
|
+
position = "top-right",
|
|
1321
|
+
className
|
|
1322
|
+
}) {
|
|
1323
|
+
const positionClass = {
|
|
1324
|
+
"top-right": "top-4 right-4 items-end",
|
|
1325
|
+
"top-left": "top-4 left-4 items-start",
|
|
1326
|
+
"top-center": "top-4 left-1/2 -translate-x-1/2 items-center",
|
|
1327
|
+
"bottom-right": "bottom-4 right-4 items-end",
|
|
1328
|
+
"bottom-left": "bottom-4 left-4 items-start",
|
|
1329
|
+
"bottom-center": "bottom-4 left-1/2 -translate-x-1/2 items-center"
|
|
1330
|
+
};
|
|
1331
|
+
return /* @__PURE__ */ jsx12(
|
|
1332
|
+
"div",
|
|
1333
|
+
{
|
|
1334
|
+
className: [
|
|
1335
|
+
"fixed z-50 flex flex-col gap-2 pointer-events-none",
|
|
1336
|
+
positionClass[position],
|
|
1337
|
+
className != null ? className : ""
|
|
1338
|
+
].filter(Boolean).join(" "),
|
|
1339
|
+
children
|
|
1340
|
+
}
|
|
1341
|
+
);
|
|
1342
|
+
}
|
|
1343
|
+
function Toast({
|
|
1344
|
+
variant = "info",
|
|
1345
|
+
message,
|
|
1346
|
+
duration,
|
|
1347
|
+
onDismiss,
|
|
1348
|
+
action
|
|
1349
|
+
}) {
|
|
1350
|
+
useEffect9(() => {
|
|
1351
|
+
const id = useToastStore.getState().add({
|
|
1352
|
+
variant,
|
|
1353
|
+
message,
|
|
1354
|
+
duration,
|
|
1355
|
+
actions: action ? [{ label: action.label, onClick: (d) => {
|
|
1356
|
+
action.onClick();
|
|
1357
|
+
d();
|
|
1358
|
+
} }] : void 0
|
|
1359
|
+
});
|
|
1360
|
+
if (!onDismiss) return;
|
|
1361
|
+
const eff = getEffectiveDuration({ variant, duration });
|
|
1362
|
+
if (eff === null) return;
|
|
1363
|
+
const timer = setTimeout(onDismiss, eff);
|
|
1364
|
+
return () => clearTimeout(timer);
|
|
1365
|
+
}, []);
|
|
1366
|
+
return null;
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
// modules/ui/Tooltip.tsx
|
|
1370
|
+
import { useRef as useRef7, useState as useState7 } from "react";
|
|
1371
|
+
import { jsx as jsx13, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1372
|
+
var themeMap = {
|
|
1373
|
+
default: "bg-surface-overlay text-text-primary border border-border",
|
|
1374
|
+
dark: "bg-gray-900 text-white border-transparent",
|
|
1375
|
+
light: "bg-white text-gray-900 border border-border shadow-md"
|
|
1376
|
+
};
|
|
1377
|
+
var arrowBaseClass = "absolute w-2 h-2 rotate-45 border border-border";
|
|
1378
|
+
var arrowPlacementClass = {
|
|
1379
|
+
top: "bottom-[-5px] left-1/2 -translate-x-1/2 border-t-0 border-l-0",
|
|
1380
|
+
bottom: "top-[-5px] left-1/2 -translate-x-1/2 border-b-0 border-r-0",
|
|
1381
|
+
left: "right-[-5px] top-1/2 -translate-y-1/2 border-l-0 border-b-0",
|
|
1382
|
+
right: "left-[-5px] top-1/2 -translate-y-1/2 border-r-0 border-t-0"
|
|
1383
|
+
};
|
|
1384
|
+
var arrowThemeMap = {
|
|
1385
|
+
default: "bg-surface-overlay",
|
|
1386
|
+
dark: "bg-gray-900 border-transparent",
|
|
1387
|
+
light: "bg-white"
|
|
1388
|
+
};
|
|
1389
|
+
var placementClass = {
|
|
1390
|
+
top: "bottom-full left-1/2 -translate-x-1/2 mb-2",
|
|
1391
|
+
bottom: "top-full left-1/2 -translate-x-1/2 mt-2",
|
|
1392
|
+
left: "right-full top-1/2 -translate-y-1/2 mr-2",
|
|
1393
|
+
right: "left-full top-1/2 -translate-y-1/2 ml-2"
|
|
1394
|
+
};
|
|
1395
|
+
function Tooltip({
|
|
1396
|
+
content,
|
|
1397
|
+
placement = "top",
|
|
1398
|
+
theme = "default",
|
|
1399
|
+
arrow = false,
|
|
1400
|
+
delay = 0,
|
|
1401
|
+
children,
|
|
1402
|
+
className
|
|
1403
|
+
}) {
|
|
1404
|
+
const [visible, setVisible] = useState7(false);
|
|
1405
|
+
const timer = useRef7(null);
|
|
1406
|
+
const id = useRef7(`tooltip-${Math.random().toString(36).slice(2)}`).current;
|
|
1407
|
+
function show() {
|
|
1408
|
+
if (delay > 0) {
|
|
1409
|
+
timer.current = setTimeout(() => setVisible(true), delay);
|
|
1410
|
+
} else {
|
|
1411
|
+
setVisible(true);
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1414
|
+
function hide() {
|
|
1415
|
+
if (timer.current) clearTimeout(timer.current);
|
|
1416
|
+
setVisible(false);
|
|
1417
|
+
}
|
|
1418
|
+
return /* @__PURE__ */ jsxs9(
|
|
1419
|
+
"span",
|
|
1420
|
+
{
|
|
1421
|
+
className: cn("relative inline-flex items-center", className),
|
|
1422
|
+
onMouseEnter: show,
|
|
1423
|
+
onMouseLeave: hide,
|
|
1424
|
+
onFocus: show,
|
|
1425
|
+
onBlur: hide,
|
|
1426
|
+
children: [
|
|
1427
|
+
/* @__PURE__ */ jsx13("span", { "aria-describedby": id, children }),
|
|
1428
|
+
/* @__PURE__ */ jsxs9(
|
|
1429
|
+
"span",
|
|
1430
|
+
{
|
|
1431
|
+
id,
|
|
1432
|
+
role: "tooltip",
|
|
1433
|
+
className: cn(
|
|
1434
|
+
"absolute z-[80] whitespace-nowrap rounded-md px-2.5 py-1.5 text-xs font-medium shadow-md",
|
|
1435
|
+
"transition-opacity duration-150 pointer-events-none",
|
|
1436
|
+
themeMap[theme],
|
|
1437
|
+
placementClass[placement],
|
|
1438
|
+
visible ? "opacity-100" : "opacity-0"
|
|
1439
|
+
),
|
|
1440
|
+
children: [
|
|
1441
|
+
content,
|
|
1442
|
+
arrow && /* @__PURE__ */ jsx13(
|
|
1443
|
+
"span",
|
|
1444
|
+
{
|
|
1445
|
+
"aria-hidden": "true",
|
|
1446
|
+
className: cn(
|
|
1447
|
+
arrowBaseClass,
|
|
1448
|
+
arrowPlacementClass[placement],
|
|
1449
|
+
arrowThemeMap[theme]
|
|
1450
|
+
)
|
|
1451
|
+
}
|
|
1452
|
+
)
|
|
1453
|
+
]
|
|
1454
|
+
}
|
|
1455
|
+
)
|
|
1456
|
+
]
|
|
1457
|
+
}
|
|
1458
|
+
);
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
export {
|
|
1462
|
+
SkipLink,
|
|
1463
|
+
LiveRegion,
|
|
1464
|
+
Announcer,
|
|
1465
|
+
Breadcrumb,
|
|
1466
|
+
useFilter,
|
|
1467
|
+
useAsync,
|
|
1468
|
+
useLoadMore,
|
|
1469
|
+
useFocusTrap,
|
|
1470
|
+
isFocusTrapTopLayer,
|
|
1471
|
+
Drawer,
|
|
1472
|
+
EmptyState,
|
|
1473
|
+
Modal,
|
|
1474
|
+
MultiSelect,
|
|
1475
|
+
SkeletonLine,
|
|
1476
|
+
SkeletonAvatar,
|
|
1477
|
+
SkeletonText,
|
|
1478
|
+
SkeletonCard,
|
|
1479
|
+
SkeletonTableRow,
|
|
1480
|
+
Stepper,
|
|
1481
|
+
getEffectiveDuration,
|
|
1482
|
+
useToastStore,
|
|
1483
|
+
toast,
|
|
1484
|
+
ToastProvider,
|
|
1485
|
+
ToastRegion,
|
|
1486
|
+
Toast,
|
|
1487
|
+
Tooltip
|
|
1488
|
+
};
|