@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,2422 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import {
|
|
3
|
+
SkeletonCard,
|
|
4
|
+
SkeletonTableRow,
|
|
5
|
+
isFocusTrapTopLayer,
|
|
6
|
+
useAsync,
|
|
7
|
+
useFilter,
|
|
8
|
+
useFocusTrap,
|
|
9
|
+
useLoadMore
|
|
10
|
+
} from "./chunk-XA7J6PVJ.mjs";
|
|
11
|
+
import {
|
|
12
|
+
__objRest,
|
|
13
|
+
__spreadProps,
|
|
14
|
+
__spreadValues,
|
|
15
|
+
cn
|
|
16
|
+
} from "./chunk-RBDK7MWQ.mjs";
|
|
17
|
+
|
|
18
|
+
// modules/ui/BrandLogo.tsx
|
|
19
|
+
import { jsx } from "react/jsx-runtime";
|
|
20
|
+
function BrandLogo({ children, size = "md", className }) {
|
|
21
|
+
return /* @__PURE__ */ jsx(
|
|
22
|
+
"span",
|
|
23
|
+
{
|
|
24
|
+
className: cn(
|
|
25
|
+
"flex items-center justify-center rounded-2xl bg-primary text-primary-fg font-bold shadow-sm",
|
|
26
|
+
size === "sm" && "h-8 w-8 text-sm",
|
|
27
|
+
size === "md" && "h-12 w-12 text-lg",
|
|
28
|
+
size === "lg" && "h-16 w-16 text-2xl",
|
|
29
|
+
size === "xl" && "h-20 w-20 text-3xl",
|
|
30
|
+
size === "2xl" && "h-24 w-24 text-4xl",
|
|
31
|
+
className
|
|
32
|
+
),
|
|
33
|
+
children
|
|
34
|
+
}
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// modules/ui/Checkbox.tsx
|
|
39
|
+
import { useEffect, useRef } from "react";
|
|
40
|
+
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
41
|
+
function Checkbox(_a) {
|
|
42
|
+
var _b = _a, {
|
|
43
|
+
id,
|
|
44
|
+
label,
|
|
45
|
+
hint,
|
|
46
|
+
error,
|
|
47
|
+
disabled,
|
|
48
|
+
indeterminate,
|
|
49
|
+
className
|
|
50
|
+
} = _b, props = __objRest(_b, [
|
|
51
|
+
"id",
|
|
52
|
+
"label",
|
|
53
|
+
"hint",
|
|
54
|
+
"error",
|
|
55
|
+
"disabled",
|
|
56
|
+
"indeterminate",
|
|
57
|
+
"className"
|
|
58
|
+
]);
|
|
59
|
+
const ref = useRef(null);
|
|
60
|
+
const hintId = hint ? `${id}-hint` : void 0;
|
|
61
|
+
const errorId = error ? `${id}-error` : void 0;
|
|
62
|
+
const describedBy = [hintId, errorId].filter(Boolean).join(" ") || void 0;
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
if (ref.current) ref.current.indeterminate = !!indeterminate;
|
|
65
|
+
}, [indeterminate]);
|
|
66
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("flex items-start gap-3", className), children: [
|
|
67
|
+
/* @__PURE__ */ jsx2(
|
|
68
|
+
"input",
|
|
69
|
+
__spreadValues({
|
|
70
|
+
ref,
|
|
71
|
+
id,
|
|
72
|
+
type: "checkbox",
|
|
73
|
+
disabled,
|
|
74
|
+
"aria-describedby": describedBy,
|
|
75
|
+
"aria-invalid": !!error,
|
|
76
|
+
"aria-checked": indeterminate ? "mixed" : void 0,
|
|
77
|
+
"data-testid": `checkbox-${id}`,
|
|
78
|
+
className: cn(
|
|
79
|
+
"mt-0.5 h-4 w-4 rounded border-border text-primary",
|
|
80
|
+
"focus-visible:ring-2 focus-visible:ring-border-focus",
|
|
81
|
+
"disabled:opacity-50 disabled:cursor-not-allowed",
|
|
82
|
+
error && "border-error"
|
|
83
|
+
)
|
|
84
|
+
}, props)
|
|
85
|
+
),
|
|
86
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
87
|
+
/* @__PURE__ */ jsx2("label", { htmlFor: id, className: cn("text-sm font-medium", disabled ? "text-text-disabled" : "text-text-primary"), children: label }),
|
|
88
|
+
hint && !error && /* @__PURE__ */ jsx2("p", { id: hintId, className: "text-xs text-text-secondary mt-0.5", children: hint }),
|
|
89
|
+
error && /* @__PURE__ */ jsx2("p", { id: errorId, className: "text-xs text-error mt-0.5", role: "alert", children: error })
|
|
90
|
+
] })
|
|
91
|
+
] });
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// modules/ui/FileInput/index.tsx
|
|
95
|
+
import { useRef as useRef2, useState, useEffect as useEffect2, useCallback } from "react";
|
|
96
|
+
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
97
|
+
import { faFolderOpen, faXmark } from "@fortawesome/free-solid-svg-icons";
|
|
98
|
+
|
|
99
|
+
// modules/ui/FileInput/types.ts
|
|
100
|
+
var DEFAULT_MESSAGES = {
|
|
101
|
+
invalidSize: (limit) => `File exceeds ${limit} limit`,
|
|
102
|
+
invalidType: "File type not allowed",
|
|
103
|
+
tooMany: (max) => `Too many files \u2014 limit is ${max}`,
|
|
104
|
+
uploadFailed: "Upload failed. Please try again.",
|
|
105
|
+
uploadSuccess: "Files uploaded successfully."
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
// modules/ui/FileInput/index.tsx
|
|
109
|
+
import { Fragment, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
110
|
+
function formatBytes(bytes) {
|
|
111
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
112
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
113
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
114
|
+
}
|
|
115
|
+
function matchesAccept(file, accept) {
|
|
116
|
+
if (!accept) return true;
|
|
117
|
+
const patterns = accept.split(",").map((p) => p.trim().toLowerCase()).filter(Boolean);
|
|
118
|
+
if (patterns.length === 0) return true;
|
|
119
|
+
const name = file.name.toLowerCase();
|
|
120
|
+
const mime = (file.type || "").toLowerCase();
|
|
121
|
+
return patterns.some((p) => {
|
|
122
|
+
if (p.startsWith(".")) return name.endsWith(p);
|
|
123
|
+
if (p.endsWith("/*")) {
|
|
124
|
+
const prefix = p.slice(0, -1);
|
|
125
|
+
return mime.startsWith(prefix);
|
|
126
|
+
}
|
|
127
|
+
return mime === p;
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
function FileInput({
|
|
131
|
+
id,
|
|
132
|
+
label,
|
|
133
|
+
hint,
|
|
134
|
+
multiple = false,
|
|
135
|
+
accept,
|
|
136
|
+
maxSizeBytes,
|
|
137
|
+
maxFiles,
|
|
138
|
+
allowedTypes,
|
|
139
|
+
disabled,
|
|
140
|
+
required,
|
|
141
|
+
name,
|
|
142
|
+
enablePaste = false,
|
|
143
|
+
onFiles,
|
|
144
|
+
onUpload,
|
|
145
|
+
uploadLabel = "Upload",
|
|
146
|
+
className,
|
|
147
|
+
messages
|
|
148
|
+
}) {
|
|
149
|
+
const inputRef = useRef2(null);
|
|
150
|
+
const rootRef = useRef2(null);
|
|
151
|
+
const [entries, setEntries] = useState([]);
|
|
152
|
+
const [dragging, setDragging] = useState(false);
|
|
153
|
+
const [uploadState, setUploadState] = useState("idle");
|
|
154
|
+
const [errorMsg, setErrorMsg] = useState("");
|
|
155
|
+
const [globalError, setGlobalError] = useState("");
|
|
156
|
+
const isDisabled = disabled || uploadState === "uploading";
|
|
157
|
+
const msg = __spreadValues(__spreadValues({}, DEFAULT_MESSAGES), messages);
|
|
158
|
+
const validate = useCallback(
|
|
159
|
+
(file) => {
|
|
160
|
+
if (maxSizeBytes && file.size > maxSizeBytes) {
|
|
161
|
+
return msg.invalidSize(formatBytes(maxSizeBytes));
|
|
162
|
+
}
|
|
163
|
+
if (allowedTypes && allowedTypes.length > 0 && !allowedTypes.includes(file.type)) {
|
|
164
|
+
return msg.invalidType;
|
|
165
|
+
}
|
|
166
|
+
if (accept && !matchesAccept(file, accept)) {
|
|
167
|
+
return msg.invalidType;
|
|
168
|
+
}
|
|
169
|
+
return void 0;
|
|
170
|
+
},
|
|
171
|
+
[maxSizeBytes, allowedTypes, accept, msg]
|
|
172
|
+
);
|
|
173
|
+
const addFiles = useCallback(
|
|
174
|
+
(files) => {
|
|
175
|
+
if (!files) return;
|
|
176
|
+
const arr = Array.from(files);
|
|
177
|
+
if (arr.length === 0) return;
|
|
178
|
+
const newEntries = arr.map((file) => ({
|
|
179
|
+
file,
|
|
180
|
+
error: validate(file)
|
|
181
|
+
}));
|
|
182
|
+
setEntries((prev) => {
|
|
183
|
+
const combined = multiple ? [...prev, ...newEntries] : newEntries;
|
|
184
|
+
if (maxFiles && combined.length > maxFiles) {
|
|
185
|
+
setGlobalError(msg.tooMany(maxFiles));
|
|
186
|
+
return combined.slice(0, maxFiles);
|
|
187
|
+
}
|
|
188
|
+
setGlobalError("");
|
|
189
|
+
return combined;
|
|
190
|
+
});
|
|
191
|
+
const valid = newEntries.filter((e) => !e.error).map((e) => e.file);
|
|
192
|
+
if (valid.length > 0) onFiles == null ? void 0 : onFiles(valid);
|
|
193
|
+
setUploadState("idle");
|
|
194
|
+
},
|
|
195
|
+
[multiple, validate, maxFiles, msg, onFiles]
|
|
196
|
+
);
|
|
197
|
+
function removeEntry(i) {
|
|
198
|
+
setEntries((prev) => prev.filter((_, idx) => idx !== i));
|
|
199
|
+
if (inputRef.current) inputRef.current.value = "";
|
|
200
|
+
setGlobalError("");
|
|
201
|
+
}
|
|
202
|
+
async function handleUpload() {
|
|
203
|
+
if (!onUpload) return;
|
|
204
|
+
const validFiles = entries.filter((e) => !e.error).map((e) => e.file);
|
|
205
|
+
if (validFiles.length === 0) return;
|
|
206
|
+
setUploadState("uploading");
|
|
207
|
+
setErrorMsg("");
|
|
208
|
+
try {
|
|
209
|
+
await onUpload(validFiles);
|
|
210
|
+
setUploadState("success");
|
|
211
|
+
setEntries([]);
|
|
212
|
+
if (inputRef.current) inputRef.current.value = "";
|
|
213
|
+
} catch (e) {
|
|
214
|
+
setUploadState("error");
|
|
215
|
+
setErrorMsg(e instanceof Error ? e.message : msg.uploadFailed);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
useEffect2(() => {
|
|
219
|
+
if (!enablePaste || isDisabled) return;
|
|
220
|
+
function onPaste(ev) {
|
|
221
|
+
var _a;
|
|
222
|
+
const root = rootRef.current;
|
|
223
|
+
if (!root) return;
|
|
224
|
+
const active = document.activeElement;
|
|
225
|
+
const inside = active && (root === active || root.contains(active));
|
|
226
|
+
if (!inside) return;
|
|
227
|
+
const items = (_a = ev.clipboardData) == null ? void 0 : _a.items;
|
|
228
|
+
if (!items || items.length === 0) return;
|
|
229
|
+
const files = [];
|
|
230
|
+
for (let i = 0; i < items.length; i++) {
|
|
231
|
+
const it = items[i];
|
|
232
|
+
if (it.kind === "file") {
|
|
233
|
+
const f = it.getAsFile();
|
|
234
|
+
if (f) {
|
|
235
|
+
const named = f.name && f.name !== "image.png" ? f : new File([f], `pasted-${Date.now()}.${(f.type.split("/")[1] || "bin").replace("+xml", "")}`, {
|
|
236
|
+
type: f.type,
|
|
237
|
+
lastModified: Date.now()
|
|
238
|
+
});
|
|
239
|
+
files.push(named);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
if (files.length > 0) {
|
|
244
|
+
ev.preventDefault();
|
|
245
|
+
addFiles(files);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
document.addEventListener("paste", onPaste);
|
|
249
|
+
return () => document.removeEventListener("paste", onPaste);
|
|
250
|
+
}, [enablePaste, isDisabled, addFiles]);
|
|
251
|
+
const showError = globalError || (uploadState === "error" ? errorMsg : "");
|
|
252
|
+
return /* @__PURE__ */ jsxs2("div", { ref: rootRef, className: cn("space-y-2", className), tabIndex: enablePaste ? -1 : void 0, children: [
|
|
253
|
+
label && /* @__PURE__ */ jsxs2("label", { htmlFor: id, className: "block text-sm font-medium text-text-primary", children: [
|
|
254
|
+
label,
|
|
255
|
+
required && /* @__PURE__ */ jsx3("span", { className: "text-error", children: " *" })
|
|
256
|
+
] }),
|
|
257
|
+
/* @__PURE__ */ jsxs2(
|
|
258
|
+
"div",
|
|
259
|
+
{
|
|
260
|
+
className: cn(
|
|
261
|
+
"relative rounded-lg border-2 border-dashed border-border bg-surface-base transition-colors",
|
|
262
|
+
"flex flex-col items-center justify-center gap-2 px-6 py-8 text-center",
|
|
263
|
+
dragging && "border-primary bg-primary-subtle",
|
|
264
|
+
isDisabled && "opacity-50 cursor-not-allowed"
|
|
265
|
+
),
|
|
266
|
+
onDragOver: (e) => {
|
|
267
|
+
e.preventDefault();
|
|
268
|
+
if (!isDisabled) setDragging(true);
|
|
269
|
+
},
|
|
270
|
+
onDragLeave: () => setDragging(false),
|
|
271
|
+
onDrop: (e) => {
|
|
272
|
+
e.preventDefault();
|
|
273
|
+
setDragging(false);
|
|
274
|
+
if (!isDisabled) addFiles(e.dataTransfer.files);
|
|
275
|
+
},
|
|
276
|
+
children: [
|
|
277
|
+
/* @__PURE__ */ jsx3(FontAwesomeIcon, { icon: faFolderOpen, className: "w-8 h-8 text-text-disabled", "aria-hidden": "true" }),
|
|
278
|
+
/* @__PURE__ */ jsxs2("p", { className: "text-sm text-text-secondary", children: [
|
|
279
|
+
"Drag & drop files here, or",
|
|
280
|
+
" ",
|
|
281
|
+
/* @__PURE__ */ jsx3(
|
|
282
|
+
"button",
|
|
283
|
+
{
|
|
284
|
+
type: "button",
|
|
285
|
+
disabled: isDisabled,
|
|
286
|
+
onClick: () => {
|
|
287
|
+
var _a;
|
|
288
|
+
return (_a = inputRef.current) == null ? void 0 : _a.click();
|
|
289
|
+
},
|
|
290
|
+
className: "text-primary underline underline-offset-2 hover:opacity-70 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus rounded disabled:cursor-not-allowed",
|
|
291
|
+
children: "browse"
|
|
292
|
+
}
|
|
293
|
+
),
|
|
294
|
+
enablePaste && /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
295
|
+
" ",
|
|
296
|
+
"or paste"
|
|
297
|
+
] })
|
|
298
|
+
] }),
|
|
299
|
+
hint && /* @__PURE__ */ jsx3("p", { className: "text-xs text-text-disabled", children: hint }),
|
|
300
|
+
/* @__PURE__ */ jsx3(
|
|
301
|
+
"input",
|
|
302
|
+
{
|
|
303
|
+
ref: inputRef,
|
|
304
|
+
id,
|
|
305
|
+
name,
|
|
306
|
+
type: "file",
|
|
307
|
+
multiple,
|
|
308
|
+
accept,
|
|
309
|
+
disabled: isDisabled,
|
|
310
|
+
required,
|
|
311
|
+
className: "sr-only",
|
|
312
|
+
onChange: (e) => addFiles(e.target.files)
|
|
313
|
+
}
|
|
314
|
+
)
|
|
315
|
+
]
|
|
316
|
+
}
|
|
317
|
+
),
|
|
318
|
+
entries.length > 0 && /* @__PURE__ */ jsx3("ul", { className: "space-y-1.5", "aria-label": "Selected files", children: entries.map((entry, i) => /* @__PURE__ */ jsxs2(
|
|
319
|
+
"li",
|
|
320
|
+
{
|
|
321
|
+
className: cn(
|
|
322
|
+
"flex items-center gap-3 rounded-md border px-3 py-2 text-sm",
|
|
323
|
+
entry.error ? "border-error bg-error-subtle text-error-fg" : "border-border bg-surface-raised text-text-primary"
|
|
324
|
+
),
|
|
325
|
+
children: [
|
|
326
|
+
/* @__PURE__ */ jsxs2("span", { className: "flex-1 truncate min-w-0", children: [
|
|
327
|
+
/* @__PURE__ */ jsx3("span", { className: "font-medium", children: entry.file.name }),
|
|
328
|
+
/* @__PURE__ */ jsx3("span", { className: "ml-2 text-xs text-text-secondary", children: formatBytes(entry.file.size) })
|
|
329
|
+
] }),
|
|
330
|
+
entry.error && /* @__PURE__ */ jsx3("span", { className: "text-xs text-error shrink-0", children: entry.error }),
|
|
331
|
+
/* @__PURE__ */ jsx3(
|
|
332
|
+
"button",
|
|
333
|
+
{
|
|
334
|
+
type: "button",
|
|
335
|
+
"aria-label": `Remove ${entry.file.name}`,
|
|
336
|
+
onClick: () => removeEntry(i),
|
|
337
|
+
className: "shrink-0 hover:opacity-70 transition-opacity focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus rounded",
|
|
338
|
+
children: /* @__PURE__ */ jsx3(FontAwesomeIcon, { icon: faXmark, className: "w-3 h-3" })
|
|
339
|
+
}
|
|
340
|
+
)
|
|
341
|
+
]
|
|
342
|
+
},
|
|
343
|
+
i
|
|
344
|
+
)) }),
|
|
345
|
+
onUpload && entries.length > 0 && /* @__PURE__ */ jsx3("div", { className: "flex justify-end", children: /* @__PURE__ */ jsx3(
|
|
346
|
+
"button",
|
|
347
|
+
{
|
|
348
|
+
type: "button",
|
|
349
|
+
onClick: handleUpload,
|
|
350
|
+
disabled: uploadState === "uploading",
|
|
351
|
+
className: cn(
|
|
352
|
+
"rounded-md px-4 py-2 text-sm font-medium text-primary-fg bg-primary transition-colors",
|
|
353
|
+
"hover:bg-primary-hover active:bg-primary-active",
|
|
354
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus",
|
|
355
|
+
"disabled:opacity-50 disabled:cursor-not-allowed"
|
|
356
|
+
),
|
|
357
|
+
children: uploadState === "uploading" ? "Uploading\u2026" : uploadLabel
|
|
358
|
+
}
|
|
359
|
+
) }),
|
|
360
|
+
showError && /* @__PURE__ */ jsx3("p", { role: "alert", className: "text-sm text-error", children: showError }),
|
|
361
|
+
uploadState === "success" && !showError && /* @__PURE__ */ jsx3("p", { role: "status", className: "text-sm text-success-fg", children: msg.uploadSuccess })
|
|
362
|
+
] });
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// modules/ui/StarRating.tsx
|
|
366
|
+
import { FontAwesomeIcon as FontAwesomeIcon2 } from "@fortawesome/react-fontawesome";
|
|
367
|
+
import { faStar, faStarHalfStroke } from "@fortawesome/free-solid-svg-icons";
|
|
368
|
+
import { faStar as faStarRegular } from "@fortawesome/free-regular-svg-icons";
|
|
369
|
+
import { useState as useState2 } from "react";
|
|
370
|
+
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
371
|
+
var sizeClasses = {
|
|
372
|
+
sm: "w-3.5 h-3.5",
|
|
373
|
+
md: "w-5 h-5",
|
|
374
|
+
lg: "w-7 h-7"
|
|
375
|
+
};
|
|
376
|
+
var gapClasses = {
|
|
377
|
+
sm: "gap-0.5",
|
|
378
|
+
md: "gap-1",
|
|
379
|
+
lg: "gap-1.5"
|
|
380
|
+
};
|
|
381
|
+
var TOTAL_STARS = 5;
|
|
382
|
+
function clampValue(value) {
|
|
383
|
+
if (Number.isNaN(value)) return 0;
|
|
384
|
+
if (value < 0) return 0;
|
|
385
|
+
if (value > TOTAL_STARS) return TOTAL_STARS;
|
|
386
|
+
return value;
|
|
387
|
+
}
|
|
388
|
+
function StarRating({
|
|
389
|
+
value,
|
|
390
|
+
size = "md",
|
|
391
|
+
readonly = true,
|
|
392
|
+
onChange,
|
|
393
|
+
"aria-label": ariaLabel,
|
|
394
|
+
caption,
|
|
395
|
+
className
|
|
396
|
+
}) {
|
|
397
|
+
const safeValue = clampValue(value);
|
|
398
|
+
const [hoverValue, setHoverValue] = useState2(null);
|
|
399
|
+
const isInteractive = !readonly && typeof onChange === "function";
|
|
400
|
+
const displayValue = isInteractive && hoverValue !== null ? hoverValue : safeValue;
|
|
401
|
+
const labelText = ariaLabel != null ? ariaLabel : `${safeValue.toFixed(1)} out of ${TOTAL_STARS} stars`;
|
|
402
|
+
const starClass = sizeClasses[size];
|
|
403
|
+
if (!isInteractive) {
|
|
404
|
+
return /* @__PURE__ */ jsxs3(
|
|
405
|
+
"span",
|
|
406
|
+
{
|
|
407
|
+
className: cn("inline-flex items-center", gapClasses[size], className),
|
|
408
|
+
role: "img",
|
|
409
|
+
"aria-label": labelText,
|
|
410
|
+
children: [
|
|
411
|
+
Array.from({ length: TOTAL_STARS }, (_, i) => {
|
|
412
|
+
const starIndex = i + 1;
|
|
413
|
+
const filled = displayValue >= starIndex;
|
|
414
|
+
const half = !filled && displayValue >= starIndex - 0.5;
|
|
415
|
+
return /* @__PURE__ */ jsx4(
|
|
416
|
+
FontAwesomeIcon2,
|
|
417
|
+
{
|
|
418
|
+
icon: filled ? faStar : half ? faStarHalfStroke : faStarRegular,
|
|
419
|
+
className: cn(starClass, filled || half ? "text-warning" : "text-text-disabled"),
|
|
420
|
+
"aria-hidden": "true"
|
|
421
|
+
},
|
|
422
|
+
starIndex
|
|
423
|
+
);
|
|
424
|
+
}),
|
|
425
|
+
caption && /* @__PURE__ */ jsx4("span", { className: "ml-2 text-sm text-text-secondary", children: caption })
|
|
426
|
+
]
|
|
427
|
+
}
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
return /* @__PURE__ */ jsxs3(
|
|
431
|
+
"span",
|
|
432
|
+
{
|
|
433
|
+
role: "radiogroup",
|
|
434
|
+
"aria-label": ariaLabel != null ? ariaLabel : "Rating",
|
|
435
|
+
className: cn("inline-flex items-center", gapClasses[size], className),
|
|
436
|
+
onMouseLeave: () => setHoverValue(null),
|
|
437
|
+
children: [
|
|
438
|
+
Array.from({ length: TOTAL_STARS }, (_, i) => {
|
|
439
|
+
const starIndex = i + 1;
|
|
440
|
+
const filled = displayValue >= starIndex;
|
|
441
|
+
const checked = safeValue === starIndex;
|
|
442
|
+
return /* @__PURE__ */ jsx4(
|
|
443
|
+
"button",
|
|
444
|
+
{
|
|
445
|
+
type: "button",
|
|
446
|
+
role: "radio",
|
|
447
|
+
"aria-checked": checked,
|
|
448
|
+
"aria-label": `${starIndex} ${starIndex === 1 ? "star" : "stars"}`,
|
|
449
|
+
onClick: () => onChange == null ? void 0 : onChange(starIndex),
|
|
450
|
+
onMouseEnter: () => setHoverValue(starIndex),
|
|
451
|
+
onFocus: () => setHoverValue(starIndex),
|
|
452
|
+
onBlur: () => setHoverValue(null),
|
|
453
|
+
className: cn(
|
|
454
|
+
"rounded-sm transition-colors p-0.5",
|
|
455
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus",
|
|
456
|
+
"disabled:opacity-50 disabled:cursor-not-allowed"
|
|
457
|
+
),
|
|
458
|
+
children: /* @__PURE__ */ jsx4(
|
|
459
|
+
FontAwesomeIcon2,
|
|
460
|
+
{
|
|
461
|
+
icon: filled ? faStar : faStarRegular,
|
|
462
|
+
className: cn(starClass, filled ? "text-warning" : "text-text-disabled"),
|
|
463
|
+
"aria-hidden": "true"
|
|
464
|
+
}
|
|
465
|
+
)
|
|
466
|
+
},
|
|
467
|
+
starIndex
|
|
468
|
+
);
|
|
469
|
+
}),
|
|
470
|
+
caption && /* @__PURE__ */ jsx4("span", { className: "ml-2 text-sm text-text-secondary", children: caption })
|
|
471
|
+
]
|
|
472
|
+
}
|
|
473
|
+
);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// modules/ui/StatCard.tsx
|
|
477
|
+
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
478
|
+
function StatCard({ label, value, accent, className }) {
|
|
479
|
+
return /* @__PURE__ */ jsxs4("div", { className: cn("bg-surface-raised border border-border rounded-xl px-5 py-4 flex flex-col gap-1", className), children: [
|
|
480
|
+
/* @__PURE__ */ jsx5("span", { className: cn("text-2xl font-black tabular-nums", accent != null ? accent : "text-text-primary"), children: value }),
|
|
481
|
+
/* @__PURE__ */ jsx5("span", { className: "text-xs text-text-secondary", children: label })
|
|
482
|
+
] });
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// modules/ui/ButtonGroup.tsx
|
|
486
|
+
import { jsx as jsx6 } from "react/jsx-runtime";
|
|
487
|
+
var variantClasses = {
|
|
488
|
+
primary: {
|
|
489
|
+
base: "text-primary-fg",
|
|
490
|
+
active: "bg-primary",
|
|
491
|
+
inactive: "bg-primary/20 hover:bg-primary/40"
|
|
492
|
+
},
|
|
493
|
+
secondary: {
|
|
494
|
+
base: "text-secondary-fg",
|
|
495
|
+
active: "bg-secondary",
|
|
496
|
+
inactive: "bg-secondary/20 hover:bg-secondary/40"
|
|
497
|
+
},
|
|
498
|
+
outline: {
|
|
499
|
+
base: "border-y border-border text-text-primary",
|
|
500
|
+
active: "bg-surface-overlay font-semibold",
|
|
501
|
+
inactive: "bg-surface-base hover:bg-surface-overlay"
|
|
502
|
+
},
|
|
503
|
+
ghost: {
|
|
504
|
+
base: "text-text-primary",
|
|
505
|
+
active: "bg-surface-overlay font-semibold",
|
|
506
|
+
inactive: "hover:bg-surface-overlay"
|
|
507
|
+
}
|
|
508
|
+
};
|
|
509
|
+
var sizeClasses2 = {
|
|
510
|
+
xs: "px-2 py-1 text-xs",
|
|
511
|
+
sm: "px-3 py-1.5 text-sm",
|
|
512
|
+
md: "px-4 py-2 text-sm",
|
|
513
|
+
lg: "px-5 py-2.5 text-base"
|
|
514
|
+
};
|
|
515
|
+
function ButtonGroup({
|
|
516
|
+
items,
|
|
517
|
+
value,
|
|
518
|
+
onChange,
|
|
519
|
+
variant = "outline",
|
|
520
|
+
size = "md",
|
|
521
|
+
className
|
|
522
|
+
}) {
|
|
523
|
+
const v = variantClasses[variant];
|
|
524
|
+
return /* @__PURE__ */ jsx6(
|
|
525
|
+
"div",
|
|
526
|
+
{
|
|
527
|
+
role: "group",
|
|
528
|
+
className: cn(
|
|
529
|
+
"inline-flex rounded-md overflow-hidden",
|
|
530
|
+
variant === "outline" && "border border-border divide-x divide-border",
|
|
531
|
+
className
|
|
532
|
+
),
|
|
533
|
+
children: items.map((item, i) => {
|
|
534
|
+
const active = item.value === value;
|
|
535
|
+
return /* @__PURE__ */ jsx6(
|
|
536
|
+
"button",
|
|
537
|
+
{
|
|
538
|
+
type: "button",
|
|
539
|
+
disabled: item.disabled,
|
|
540
|
+
"aria-pressed": active,
|
|
541
|
+
onClick: () => onChange(item.value),
|
|
542
|
+
className: cn(
|
|
543
|
+
"font-medium transition-colors",
|
|
544
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-border-focus",
|
|
545
|
+
"disabled:opacity-50 disabled:cursor-not-allowed",
|
|
546
|
+
v.base,
|
|
547
|
+
active ? v.active : v.inactive,
|
|
548
|
+
sizeClasses2[size],
|
|
549
|
+
i === 0 && variant !== "outline" && "rounded-l-md",
|
|
550
|
+
i === items.length - 1 && variant !== "outline" && "rounded-r-md"
|
|
551
|
+
),
|
|
552
|
+
children: item.label
|
|
553
|
+
},
|
|
554
|
+
item.value
|
|
555
|
+
);
|
|
556
|
+
})
|
|
557
|
+
}
|
|
558
|
+
);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// modules/ui/CheckboxGroup.tsx
|
|
562
|
+
import { FontAwesomeIcon as FontAwesomeIcon3 } from "@fortawesome/react-fontawesome";
|
|
563
|
+
import { faCheck } from "@fortawesome/free-solid-svg-icons";
|
|
564
|
+
import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
565
|
+
function CheckboxGroup({
|
|
566
|
+
legend,
|
|
567
|
+
options,
|
|
568
|
+
selected,
|
|
569
|
+
onChange,
|
|
570
|
+
disabled,
|
|
571
|
+
error,
|
|
572
|
+
className
|
|
573
|
+
}) {
|
|
574
|
+
function toggle(value, checked) {
|
|
575
|
+
onChange(checked ? [...selected, value] : selected.filter((s) => s !== value));
|
|
576
|
+
}
|
|
577
|
+
return /* @__PURE__ */ jsxs5("fieldset", { className: cn("space-y-2", className), children: [
|
|
578
|
+
/* @__PURE__ */ jsx7("legend", { className: "text-sm font-medium text-text-primary mb-2", children: legend }),
|
|
579
|
+
/* @__PURE__ */ jsx7("div", { className: "flex flex-wrap gap-2", children: options.map(({ value, label }) => {
|
|
580
|
+
const isSelected = selected.includes(value);
|
|
581
|
+
return /* @__PURE__ */ jsxs5(
|
|
582
|
+
"label",
|
|
583
|
+
{
|
|
584
|
+
className: cn(
|
|
585
|
+
"flex items-center gap-2 px-3 py-1.5 rounded-lg border text-sm transition-colors",
|
|
586
|
+
"focus-within:ring-2 focus-within:ring-border-focus",
|
|
587
|
+
disabled ? "cursor-not-allowed opacity-50" : "cursor-pointer",
|
|
588
|
+
isSelected ? "bg-primary-subtle border-primary text-primary" : "bg-surface-base border-border text-text-primary hover:bg-surface-overlay"
|
|
589
|
+
),
|
|
590
|
+
children: [
|
|
591
|
+
/* @__PURE__ */ jsx7(
|
|
592
|
+
"input",
|
|
593
|
+
{
|
|
594
|
+
type: "checkbox",
|
|
595
|
+
checked: isSelected,
|
|
596
|
+
disabled,
|
|
597
|
+
onChange: (e) => toggle(value, e.target.checked),
|
|
598
|
+
"data-testid": `checkboxgroup-${value}`,
|
|
599
|
+
className: "sr-only"
|
|
600
|
+
}
|
|
601
|
+
),
|
|
602
|
+
isSelected && /* @__PURE__ */ jsx7(FontAwesomeIcon3, { icon: faCheck, className: "w-3 h-3", "aria-hidden": "true" }),
|
|
603
|
+
/* @__PURE__ */ jsx7("span", { children: label })
|
|
604
|
+
]
|
|
605
|
+
},
|
|
606
|
+
value
|
|
607
|
+
);
|
|
608
|
+
}) }),
|
|
609
|
+
error && /* @__PURE__ */ jsx7("p", { className: "text-xs text-error mt-1", role: "alert", children: error })
|
|
610
|
+
] });
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// modules/ui/ComboBox/index.tsx
|
|
614
|
+
import { useEffect as useEffect4, useMemo, useRef as useRef4, useState as useState4 } from "react";
|
|
615
|
+
|
|
616
|
+
// modules/ui/ComboBox/parts/Trigger.tsx
|
|
617
|
+
import { forwardRef } from "react";
|
|
618
|
+
import { FontAwesomeIcon as FontAwesomeIcon4 } from "@fortawesome/react-fontawesome";
|
|
619
|
+
import { faChevronUp, faChevronDown, faXmark as faXmark2 } from "@fortawesome/free-solid-svg-icons";
|
|
620
|
+
import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
621
|
+
var Trigger = forwardRef(function Trigger2({
|
|
622
|
+
id,
|
|
623
|
+
inputId,
|
|
624
|
+
labelId,
|
|
625
|
+
listboxId,
|
|
626
|
+
describedBy,
|
|
627
|
+
value,
|
|
628
|
+
placeholder,
|
|
629
|
+
disabled,
|
|
630
|
+
required,
|
|
631
|
+
error,
|
|
632
|
+
clearable,
|
|
633
|
+
open,
|
|
634
|
+
highlightedIndex,
|
|
635
|
+
showClear,
|
|
636
|
+
onFocus,
|
|
637
|
+
onChange,
|
|
638
|
+
onKeyDown,
|
|
639
|
+
onClick,
|
|
640
|
+
onClear
|
|
641
|
+
}, ref) {
|
|
642
|
+
return /* @__PURE__ */ jsxs6(
|
|
643
|
+
"div",
|
|
644
|
+
{
|
|
645
|
+
role: "combobox",
|
|
646
|
+
"aria-expanded": open,
|
|
647
|
+
"aria-haspopup": "listbox",
|
|
648
|
+
"aria-controls": listboxId,
|
|
649
|
+
"aria-labelledby": labelId,
|
|
650
|
+
"aria-disabled": disabled,
|
|
651
|
+
"aria-invalid": !!error,
|
|
652
|
+
className: cn(
|
|
653
|
+
"flex min-h-10 w-full items-center gap-2 rounded-md border bg-surface-base px-3 py-1.5 transition-colors",
|
|
654
|
+
"focus-within:ring-2 focus-within:ring-border-focus",
|
|
655
|
+
error ? "border-error ring-1 ring-error bg-error-subtle" : "border-border",
|
|
656
|
+
disabled && "cursor-not-allowed bg-surface-sunken opacity-50"
|
|
657
|
+
),
|
|
658
|
+
onClick,
|
|
659
|
+
children: [
|
|
660
|
+
/* @__PURE__ */ jsx8(
|
|
661
|
+
"input",
|
|
662
|
+
{
|
|
663
|
+
ref,
|
|
664
|
+
id: inputId,
|
|
665
|
+
type: "text",
|
|
666
|
+
role: "searchbox",
|
|
667
|
+
disabled,
|
|
668
|
+
required,
|
|
669
|
+
value,
|
|
670
|
+
placeholder,
|
|
671
|
+
"aria-describedby": describedBy,
|
|
672
|
+
"aria-autocomplete": "list",
|
|
673
|
+
"aria-activedescendant": highlightedIndex >= 0 ? `${id}-option-${highlightedIndex}` : void 0,
|
|
674
|
+
autoComplete: "off",
|
|
675
|
+
className: cn(
|
|
676
|
+
"w-full bg-transparent text-sm text-text-primary placeholder:text-text-disabled",
|
|
677
|
+
"outline-none"
|
|
678
|
+
),
|
|
679
|
+
onFocus,
|
|
680
|
+
onChange: (event) => onChange(event.target.value),
|
|
681
|
+
onKeyDown
|
|
682
|
+
}
|
|
683
|
+
),
|
|
684
|
+
clearable && showClear && !disabled && /* @__PURE__ */ jsx8(
|
|
685
|
+
"button",
|
|
686
|
+
{
|
|
687
|
+
type: "button",
|
|
688
|
+
"aria-label": "Clear selection",
|
|
689
|
+
onClick: onClear,
|
|
690
|
+
className: "rounded px-1 text-text-disabled transition-colors hover:text-text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus",
|
|
691
|
+
children: /* @__PURE__ */ jsx8(FontAwesomeIcon4, { icon: faXmark2, className: "h-3 w-3" })
|
|
692
|
+
}
|
|
693
|
+
),
|
|
694
|
+
/* @__PURE__ */ jsx8(
|
|
695
|
+
FontAwesomeIcon4,
|
|
696
|
+
{
|
|
697
|
+
"aria-hidden": "true",
|
|
698
|
+
icon: open ? faChevronUp : faChevronDown,
|
|
699
|
+
className: "h-3 w-3 select-none text-text-disabled"
|
|
700
|
+
}
|
|
701
|
+
)
|
|
702
|
+
]
|
|
703
|
+
}
|
|
704
|
+
);
|
|
705
|
+
});
|
|
706
|
+
|
|
707
|
+
// modules/ui/ComboBox/parts/Listbox.tsx
|
|
708
|
+
import { useEffect as useEffect3, useRef as useRef3, useState as useState3 } from "react";
|
|
709
|
+
import { Fragment as Fragment2, jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
710
|
+
var ROW_HEIGHT = 36;
|
|
711
|
+
var OVERSCAN = 4;
|
|
712
|
+
var DEFAULT_THRESHOLD = 50;
|
|
713
|
+
function Listbox({
|
|
714
|
+
id,
|
|
715
|
+
listboxId,
|
|
716
|
+
options,
|
|
717
|
+
selectedValue,
|
|
718
|
+
highlightedIndex,
|
|
719
|
+
loading,
|
|
720
|
+
loadingMore,
|
|
721
|
+
noResultsText,
|
|
722
|
+
virtualize,
|
|
723
|
+
sentinelRef,
|
|
724
|
+
onHighlight,
|
|
725
|
+
onSelect
|
|
726
|
+
}) {
|
|
727
|
+
const scrollRef = useRef3(null);
|
|
728
|
+
const [scrollTop, setScrollTop] = useState3(0);
|
|
729
|
+
const threshold = typeof virtualize === "number" ? virtualize : virtualize === true ? 0 : DEFAULT_THRESHOLD;
|
|
730
|
+
const windowed = options.length > threshold;
|
|
731
|
+
useEffect3(() => {
|
|
732
|
+
if (highlightedIndex < 0 || !scrollRef.current) return;
|
|
733
|
+
const top = highlightedIndex * ROW_HEIGHT;
|
|
734
|
+
const bottom = top + ROW_HEIGHT;
|
|
735
|
+
const el = scrollRef.current;
|
|
736
|
+
if (top < el.scrollTop) el.scrollTop = top;
|
|
737
|
+
else if (bottom > el.scrollTop + el.clientHeight) el.scrollTop = bottom - el.clientHeight;
|
|
738
|
+
}, [highlightedIndex]);
|
|
739
|
+
let visibleStart = 0;
|
|
740
|
+
let visibleEnd = options.length;
|
|
741
|
+
if (windowed) {
|
|
742
|
+
const containerHeight = 240;
|
|
743
|
+
visibleStart = Math.max(0, Math.floor(scrollTop / ROW_HEIGHT) - OVERSCAN);
|
|
744
|
+
visibleEnd = Math.min(
|
|
745
|
+
options.length,
|
|
746
|
+
Math.ceil((scrollTop + containerHeight) / ROW_HEIGHT) + OVERSCAN
|
|
747
|
+
);
|
|
748
|
+
}
|
|
749
|
+
const topPad = windowed ? visibleStart * ROW_HEIGHT : 0;
|
|
750
|
+
const bottomPad = windowed ? (options.length - visibleEnd) * ROW_HEIGHT : 0;
|
|
751
|
+
return /* @__PURE__ */ jsx9(
|
|
752
|
+
"ul",
|
|
753
|
+
{
|
|
754
|
+
ref: scrollRef,
|
|
755
|
+
id: listboxId,
|
|
756
|
+
role: "listbox",
|
|
757
|
+
"data-combobox-list": true,
|
|
758
|
+
onScroll: windowed ? (e) => setScrollTop(e.target.scrollTop) : void 0,
|
|
759
|
+
className: "z-20 max-h-60 w-full overflow-y-auto rounded-md border border-border bg-surface-raised py-1 shadow-lg",
|
|
760
|
+
children: loading ? (
|
|
761
|
+
// Skeleton rows while async search is pending.
|
|
762
|
+
/* @__PURE__ */ jsx9(Fragment2, { children: Array.from({ length: 3 }).map((_, i) => /* @__PURE__ */ jsx9("li", { className: "px-3 py-2", "aria-hidden": "true", children: /* @__PURE__ */ jsx9("div", { className: "h-3 w-full animate-pulse rounded bg-surface-overlay" }) }, `sk-${i}`)) })
|
|
763
|
+
) : options.length === 0 ? /* @__PURE__ */ jsx9("li", { className: "px-3 py-3 text-sm text-text-secondary", children: noResultsText }) : /* @__PURE__ */ jsxs7(Fragment2, { children: [
|
|
764
|
+
topPad > 0 && /* @__PURE__ */ jsx9("li", { "aria-hidden": "true", style: { height: topPad } }),
|
|
765
|
+
options.slice(visibleStart, visibleEnd).map((option, sliceIdx) => {
|
|
766
|
+
const index = visibleStart + sliceIdx;
|
|
767
|
+
const isSelected = option.value === selectedValue;
|
|
768
|
+
const isHighlighted = index === highlightedIndex;
|
|
769
|
+
return /* @__PURE__ */ jsx9(
|
|
770
|
+
"li",
|
|
771
|
+
{
|
|
772
|
+
id: `${id}-option-${index}`,
|
|
773
|
+
role: "option",
|
|
774
|
+
"aria-selected": isSelected,
|
|
775
|
+
children: /* @__PURE__ */ jsxs7(
|
|
776
|
+
"button",
|
|
777
|
+
{
|
|
778
|
+
type: "button",
|
|
779
|
+
disabled: option.disabled,
|
|
780
|
+
className: cn(
|
|
781
|
+
"flex w-full items-start gap-2 px-3 py-2 text-left text-sm transition-colors",
|
|
782
|
+
"focus-visible:outline-none",
|
|
783
|
+
isHighlighted ? "bg-surface-overlay" : "hover:bg-surface-overlay",
|
|
784
|
+
isSelected && "font-medium text-primary",
|
|
785
|
+
option.disabled && "cursor-not-allowed opacity-50"
|
|
786
|
+
),
|
|
787
|
+
onMouseEnter: () => onHighlight(index),
|
|
788
|
+
onMouseDown: (event) => event.preventDefault(),
|
|
789
|
+
onClick: () => onSelect(option),
|
|
790
|
+
children: [
|
|
791
|
+
option.icon && /* @__PURE__ */ jsx9("span", { className: "mt-0.5 shrink-0", "aria-hidden": "true", children: option.icon }),
|
|
792
|
+
/* @__PURE__ */ jsxs7("span", { className: "min-w-0 flex-1", children: [
|
|
793
|
+
/* @__PURE__ */ jsx9("span", { className: "block truncate", children: option.label }),
|
|
794
|
+
option.description && /* @__PURE__ */ jsx9("span", { className: "block truncate text-xs text-text-secondary", children: option.description })
|
|
795
|
+
] })
|
|
796
|
+
]
|
|
797
|
+
}
|
|
798
|
+
)
|
|
799
|
+
},
|
|
800
|
+
option.value
|
|
801
|
+
);
|
|
802
|
+
}),
|
|
803
|
+
bottomPad > 0 && /* @__PURE__ */ jsx9("li", { "aria-hidden": "true", style: { height: bottomPad } }),
|
|
804
|
+
sentinelRef && /* @__PURE__ */ jsx9("li", { ref: sentinelRef, "aria-hidden": "true", "data-combobox-sentinel": true, className: "h-1" }),
|
|
805
|
+
loadingMore && /* @__PURE__ */ jsx9("li", { className: "px-3 py-2 text-xs text-text-secondary", "aria-live": "polite", children: "Loading more\u2026" })
|
|
806
|
+
] })
|
|
807
|
+
}
|
|
808
|
+
);
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
// modules/ui/ComboBox/index.tsx
|
|
812
|
+
import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
813
|
+
function ComboBox({
|
|
814
|
+
id,
|
|
815
|
+
label,
|
|
816
|
+
options,
|
|
817
|
+
value,
|
|
818
|
+
onChange,
|
|
819
|
+
onSearch,
|
|
820
|
+
onLoadMore,
|
|
821
|
+
placeholder = "Search or select...",
|
|
822
|
+
hint,
|
|
823
|
+
error,
|
|
824
|
+
disabled,
|
|
825
|
+
required,
|
|
826
|
+
clearable = true,
|
|
827
|
+
noResultsText = "No results found.",
|
|
828
|
+
className,
|
|
829
|
+
debounceMs = 300,
|
|
830
|
+
virtualize = false
|
|
831
|
+
}) {
|
|
832
|
+
var _a;
|
|
833
|
+
const rootRef = useRef4(null);
|
|
834
|
+
const inputRef = useRef4(null);
|
|
835
|
+
const sentinelRef = useRef4(null);
|
|
836
|
+
const [open, setOpen] = useState4(false);
|
|
837
|
+
const [query, setQuery] = useState4("");
|
|
838
|
+
const [highlightedIndex, setHighlightedIndex] = useState4(-1);
|
|
839
|
+
const [internalValue, setInternalValue] = useState4(value != null ? value : "");
|
|
840
|
+
const selectedValue = value !== void 0 ? value : internalValue;
|
|
841
|
+
const { results: asyncResults, loading, appendResults } = useAsync(
|
|
842
|
+
open && !!onSearch,
|
|
843
|
+
query,
|
|
844
|
+
onSearch,
|
|
845
|
+
debounceMs
|
|
846
|
+
);
|
|
847
|
+
const loadingMore = useLoadMore(open, sentinelRef, onLoadMore, appendResults);
|
|
848
|
+
const sourceOptions = asyncResults != null ? asyncResults : options;
|
|
849
|
+
const selectedOption = useMemo(
|
|
850
|
+
() => {
|
|
851
|
+
var _a2;
|
|
852
|
+
return (_a2 = sourceOptions.find((opt) => opt.value === selectedValue)) != null ? _a2 : options.find((opt) => opt.value === selectedValue);
|
|
853
|
+
},
|
|
854
|
+
[options, selectedValue, sourceOptions]
|
|
855
|
+
);
|
|
856
|
+
const localFiltered = useFilter(sourceOptions, query);
|
|
857
|
+
const filteredOptions = onSearch ? sourceOptions : localFiltered;
|
|
858
|
+
const hintId = hint ? `${id}-hint` : void 0;
|
|
859
|
+
const errorId = error ? `${id}-error` : void 0;
|
|
860
|
+
const describedBy = [hintId, errorId].filter(Boolean).join(" ") || void 0;
|
|
861
|
+
const listboxId = `${id}-listbox`;
|
|
862
|
+
const labelId = `${id}-label`;
|
|
863
|
+
const inputId = `${id}-input`;
|
|
864
|
+
useEffect4(() => {
|
|
865
|
+
var _a2;
|
|
866
|
+
if (!open) {
|
|
867
|
+
setQuery((_a2 = selectedOption == null ? void 0 : selectedOption.label) != null ? _a2 : "");
|
|
868
|
+
setHighlightedIndex(-1);
|
|
869
|
+
}
|
|
870
|
+
}, [open, selectedOption == null ? void 0 : selectedOption.label]);
|
|
871
|
+
useEffect4(() => {
|
|
872
|
+
function handleOutsideClick(event) {
|
|
873
|
+
if (!rootRef.current || rootRef.current.contains(event.target)) return;
|
|
874
|
+
setOpen(false);
|
|
875
|
+
}
|
|
876
|
+
document.addEventListener("mousedown", handleOutsideClick);
|
|
877
|
+
return () => document.removeEventListener("mousedown", handleOutsideClick);
|
|
878
|
+
}, []);
|
|
879
|
+
function commitValue(next) {
|
|
880
|
+
if (value === void 0) setInternalValue(next);
|
|
881
|
+
onChange == null ? void 0 : onChange(next);
|
|
882
|
+
}
|
|
883
|
+
function handleSelect(option) {
|
|
884
|
+
if (option.disabled) return;
|
|
885
|
+
commitValue(option.value);
|
|
886
|
+
setQuery(option.label);
|
|
887
|
+
setOpen(false);
|
|
888
|
+
setHighlightedIndex(-1);
|
|
889
|
+
}
|
|
890
|
+
function handleClear(event) {
|
|
891
|
+
var _a2;
|
|
892
|
+
event.stopPropagation();
|
|
893
|
+
if (disabled) return;
|
|
894
|
+
commitValue("");
|
|
895
|
+
setQuery("");
|
|
896
|
+
setOpen(false);
|
|
897
|
+
setHighlightedIndex(-1);
|
|
898
|
+
(_a2 = inputRef.current) == null ? void 0 : _a2.focus();
|
|
899
|
+
}
|
|
900
|
+
function moveHighlight(direction) {
|
|
901
|
+
var _a2;
|
|
902
|
+
if (filteredOptions.length === 0) return;
|
|
903
|
+
let idx = highlightedIndex;
|
|
904
|
+
for (let i = 0; i < filteredOptions.length; i += 1) {
|
|
905
|
+
idx = (idx + direction + filteredOptions.length) % filteredOptions.length;
|
|
906
|
+
if (!((_a2 = filteredOptions[idx]) == null ? void 0 : _a2.disabled)) {
|
|
907
|
+
setHighlightedIndex(idx);
|
|
908
|
+
break;
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
function jumpHighlight(target) {
|
|
913
|
+
var _a2, _b;
|
|
914
|
+
if (filteredOptions.length === 0) return;
|
|
915
|
+
if (target === "first") {
|
|
916
|
+
for (let i = 0; i < filteredOptions.length; i += 1) {
|
|
917
|
+
if (!((_a2 = filteredOptions[i]) == null ? void 0 : _a2.disabled)) {
|
|
918
|
+
setHighlightedIndex(i);
|
|
919
|
+
return;
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
} else {
|
|
923
|
+
for (let i = filteredOptions.length - 1; i >= 0; i -= 1) {
|
|
924
|
+
if (!((_b = filteredOptions[i]) == null ? void 0 : _b.disabled)) {
|
|
925
|
+
setHighlightedIndex(i);
|
|
926
|
+
return;
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
function handleKeyDown(event) {
|
|
932
|
+
if (event.key === "ArrowDown") {
|
|
933
|
+
event.preventDefault();
|
|
934
|
+
if (!open) setOpen(true);
|
|
935
|
+
moveHighlight(1);
|
|
936
|
+
return;
|
|
937
|
+
}
|
|
938
|
+
if (event.key === "ArrowUp") {
|
|
939
|
+
event.preventDefault();
|
|
940
|
+
if (!open) setOpen(true);
|
|
941
|
+
moveHighlight(-1);
|
|
942
|
+
return;
|
|
943
|
+
}
|
|
944
|
+
if (event.key === "Home") {
|
|
945
|
+
event.preventDefault();
|
|
946
|
+
if (!open) setOpen(true);
|
|
947
|
+
jumpHighlight("first");
|
|
948
|
+
return;
|
|
949
|
+
}
|
|
950
|
+
if (event.key === "End") {
|
|
951
|
+
event.preventDefault();
|
|
952
|
+
if (!open) setOpen(true);
|
|
953
|
+
jumpHighlight("last");
|
|
954
|
+
return;
|
|
955
|
+
}
|
|
956
|
+
if (event.key === "Enter") {
|
|
957
|
+
if (!open || highlightedIndex < 0) return;
|
|
958
|
+
event.preventDefault();
|
|
959
|
+
const opt = filteredOptions[highlightedIndex];
|
|
960
|
+
if (opt) handleSelect(opt);
|
|
961
|
+
return;
|
|
962
|
+
}
|
|
963
|
+
if (event.key === "Escape") {
|
|
964
|
+
event.preventDefault();
|
|
965
|
+
setOpen(false);
|
|
966
|
+
return;
|
|
967
|
+
}
|
|
968
|
+
if (event.key === "Tab") setOpen(false);
|
|
969
|
+
}
|
|
970
|
+
return /* @__PURE__ */ jsxs8("div", { ref: rootRef, className: cn("space-y-1", className), children: [
|
|
971
|
+
/* @__PURE__ */ jsxs8("label", { id: labelId, htmlFor: inputId, className: "block text-sm font-medium text-text-primary", children: [
|
|
972
|
+
label,
|
|
973
|
+
required && /* @__PURE__ */ jsx10("span", { className: "ml-1 text-error", "aria-hidden": "true", children: "*" })
|
|
974
|
+
] }),
|
|
975
|
+
/* @__PURE__ */ jsx10(
|
|
976
|
+
Trigger,
|
|
977
|
+
{
|
|
978
|
+
ref: inputRef,
|
|
979
|
+
id,
|
|
980
|
+
inputId,
|
|
981
|
+
labelId,
|
|
982
|
+
listboxId,
|
|
983
|
+
describedBy,
|
|
984
|
+
value: open ? query : (_a = selectedOption == null ? void 0 : selectedOption.label) != null ? _a : query,
|
|
985
|
+
placeholder,
|
|
986
|
+
disabled,
|
|
987
|
+
required,
|
|
988
|
+
error,
|
|
989
|
+
clearable,
|
|
990
|
+
open,
|
|
991
|
+
highlightedIndex,
|
|
992
|
+
showClear: !!selectedValue,
|
|
993
|
+
onFocus: () => {
|
|
994
|
+
if (!disabled) setOpen(true);
|
|
995
|
+
},
|
|
996
|
+
onChange: (next) => {
|
|
997
|
+
setQuery(next);
|
|
998
|
+
setOpen(true);
|
|
999
|
+
setHighlightedIndex(-1);
|
|
1000
|
+
},
|
|
1001
|
+
onKeyDown: handleKeyDown,
|
|
1002
|
+
onClick: () => {
|
|
1003
|
+
var _a2;
|
|
1004
|
+
if (disabled) return;
|
|
1005
|
+
(_a2 = inputRef.current) == null ? void 0 : _a2.focus();
|
|
1006
|
+
setOpen(true);
|
|
1007
|
+
},
|
|
1008
|
+
onClear: handleClear
|
|
1009
|
+
}
|
|
1010
|
+
),
|
|
1011
|
+
open && /* @__PURE__ */ jsx10(
|
|
1012
|
+
Listbox,
|
|
1013
|
+
{
|
|
1014
|
+
id,
|
|
1015
|
+
listboxId,
|
|
1016
|
+
options: filteredOptions,
|
|
1017
|
+
selectedValue,
|
|
1018
|
+
highlightedIndex,
|
|
1019
|
+
loading,
|
|
1020
|
+
loadingMore,
|
|
1021
|
+
noResultsText,
|
|
1022
|
+
virtualize,
|
|
1023
|
+
sentinelRef: onLoadMore ? sentinelRef : void 0,
|
|
1024
|
+
onHighlight: setHighlightedIndex,
|
|
1025
|
+
onSelect: handleSelect
|
|
1026
|
+
}
|
|
1027
|
+
),
|
|
1028
|
+
hint && !error && /* @__PURE__ */ jsx10("p", { id: hintId, className: "text-xs text-text-secondary", children: hint }),
|
|
1029
|
+
error && /* @__PURE__ */ jsx10("p", { id: errorId, className: "text-xs text-error", role: "alert", children: error })
|
|
1030
|
+
] });
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
// modules/ui/ContentScoreBar.tsx
|
|
1034
|
+
import { useMemo as useMemo2 } from "react";
|
|
1035
|
+
import { FontAwesomeIcon as FontAwesomeIcon5 } from "@fortawesome/react-fontawesome";
|
|
1036
|
+
import { faCheck as faCheck2 } from "@fortawesome/free-solid-svg-icons";
|
|
1037
|
+
import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1038
|
+
var tierMap = {
|
|
1039
|
+
great: { bar: "bg-success", text: "text-success-fg", bg: "bg-success-subtle", border: "border-success", dot: "bg-success", label: "Good" },
|
|
1040
|
+
ok: { bar: "bg-warning", text: "text-warning-fg", bg: "bg-warning-subtle", border: "border-warning", dot: "bg-warning", label: "Fair" },
|
|
1041
|
+
poor: { bar: "bg-error", text: "text-error-fg", bg: "bg-error-subtle", border: "border-error", dot: "bg-error", label: "Poor" }
|
|
1042
|
+
};
|
|
1043
|
+
function ContentScoreBar({
|
|
1044
|
+
value,
|
|
1045
|
+
rules,
|
|
1046
|
+
label,
|
|
1047
|
+
className
|
|
1048
|
+
}) {
|
|
1049
|
+
const { score, results } = useMemo2(() => {
|
|
1050
|
+
let earned = 0, total = 0;
|
|
1051
|
+
const results2 = rules.map((rule) => {
|
|
1052
|
+
const pass = rule.check(value);
|
|
1053
|
+
if (pass) earned += rule.points;
|
|
1054
|
+
total += rule.points;
|
|
1055
|
+
return { label: rule.label, pass, hint: rule.hint };
|
|
1056
|
+
});
|
|
1057
|
+
return { score: total > 0 ? Math.round(earned / total * 100) : 0, results: results2 };
|
|
1058
|
+
}, [value, rules]);
|
|
1059
|
+
const tier = score >= 70 ? "great" : score >= 40 ? "ok" : "poor";
|
|
1060
|
+
const t = tierMap[tier];
|
|
1061
|
+
const passCount = results.filter((r) => r.pass).length;
|
|
1062
|
+
return /* @__PURE__ */ jsxs9(
|
|
1063
|
+
"div",
|
|
1064
|
+
{
|
|
1065
|
+
className: cn(
|
|
1066
|
+
"rounded-lg border p-3 space-y-2 transition-colors duration-300",
|
|
1067
|
+
t.bg,
|
|
1068
|
+
t.border,
|
|
1069
|
+
className
|
|
1070
|
+
),
|
|
1071
|
+
children: [
|
|
1072
|
+
/* @__PURE__ */ jsxs9("div", { className: "flex items-center gap-2", children: [
|
|
1073
|
+
/* @__PURE__ */ jsx11("span", { className: cn("inline-block h-1.5 w-1.5 rounded-full shrink-0", t.dot), "aria-hidden": "true" }),
|
|
1074
|
+
label && /* @__PURE__ */ jsx11("span", { className: "text-xs font-semibold text-text-secondary uppercase tracking-wider", children: label }),
|
|
1075
|
+
/* @__PURE__ */ jsxs9("div", { className: "ml-auto flex items-center gap-1.5", children: [
|
|
1076
|
+
/* @__PURE__ */ jsx11("span", { className: cn("text-xs font-medium", t.text), children: t.label }),
|
|
1077
|
+
/* @__PURE__ */ jsxs9(
|
|
1078
|
+
"span",
|
|
1079
|
+
{
|
|
1080
|
+
className: cn("text-sm font-bold tabular-nums leading-none", t.text),
|
|
1081
|
+
"aria-label": `${label != null ? label : "Content score"}: ${score}%`,
|
|
1082
|
+
children: [
|
|
1083
|
+
score,
|
|
1084
|
+
"%"
|
|
1085
|
+
]
|
|
1086
|
+
}
|
|
1087
|
+
)
|
|
1088
|
+
] })
|
|
1089
|
+
] }),
|
|
1090
|
+
/* @__PURE__ */ jsx11("div", { className: "h-1.5 w-full rounded-full bg-surface-sunken overflow-hidden", children: /* @__PURE__ */ jsx11(
|
|
1091
|
+
"div",
|
|
1092
|
+
{
|
|
1093
|
+
className: cn("h-full rounded-full transition-all duration-500 ease-out", t.bar),
|
|
1094
|
+
style: { width: `${score}%` }
|
|
1095
|
+
}
|
|
1096
|
+
) }),
|
|
1097
|
+
/* @__PURE__ */ jsx11("div", { className: "flex flex-wrap gap-1", children: results.map((r, i) => /* @__PURE__ */ jsxs9(
|
|
1098
|
+
"span",
|
|
1099
|
+
{
|
|
1100
|
+
title: r.hint,
|
|
1101
|
+
className: cn(
|
|
1102
|
+
"inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-xs font-medium cursor-default select-none transition-colors",
|
|
1103
|
+
r.pass ? cn(t.bg, t.text, "border", t.border) : "bg-surface-sunken text-text-disabled border border-border"
|
|
1104
|
+
),
|
|
1105
|
+
children: [
|
|
1106
|
+
r.pass && /* @__PURE__ */ jsx11(FontAwesomeIcon5, { icon: faCheck2, className: "w-2.5 h-2.5", "aria-hidden": "true" }),
|
|
1107
|
+
r.label
|
|
1108
|
+
]
|
|
1109
|
+
},
|
|
1110
|
+
i
|
|
1111
|
+
)) }),
|
|
1112
|
+
/* @__PURE__ */ jsxs9("p", { className: "text-xs text-text-secondary leading-none", children: [
|
|
1113
|
+
passCount,
|
|
1114
|
+
" / ",
|
|
1115
|
+
results.length,
|
|
1116
|
+
" rules passed"
|
|
1117
|
+
] })
|
|
1118
|
+
]
|
|
1119
|
+
}
|
|
1120
|
+
);
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
// modules/ui/PageHeader.tsx
|
|
1124
|
+
import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1125
|
+
var variantMap = {
|
|
1126
|
+
primary: "bg-primary text-primary-fg hover:bg-primary-hover",
|
|
1127
|
+
secondary: "bg-secondary text-secondary-fg hover:bg-secondary-hover",
|
|
1128
|
+
outline: "border border-border text-text-primary hover:bg-surface-overlay",
|
|
1129
|
+
danger: "bg-error text-text-inverse hover:opacity-90",
|
|
1130
|
+
ghost: "bg-transparent text-text-primary hover:bg-surface-overlay"
|
|
1131
|
+
};
|
|
1132
|
+
function PageHeader({
|
|
1133
|
+
title,
|
|
1134
|
+
subtitle,
|
|
1135
|
+
badge,
|
|
1136
|
+
actions,
|
|
1137
|
+
className
|
|
1138
|
+
}) {
|
|
1139
|
+
return /* @__PURE__ */ jsxs10(
|
|
1140
|
+
"div",
|
|
1141
|
+
{
|
|
1142
|
+
className: cn(
|
|
1143
|
+
"flex items-start justify-between gap-4 pb-5 border-b border-border",
|
|
1144
|
+
className
|
|
1145
|
+
),
|
|
1146
|
+
children: [
|
|
1147
|
+
/* @__PURE__ */ jsxs10("div", { className: "min-w-0", children: [
|
|
1148
|
+
/* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-2 flex-wrap", children: [
|
|
1149
|
+
/* @__PURE__ */ jsx12("h1", { className: "text-2xl font-bold text-text-primary leading-tight", children: title }),
|
|
1150
|
+
badge
|
|
1151
|
+
] }),
|
|
1152
|
+
subtitle && /* @__PURE__ */ jsx12("p", { className: "text-sm text-text-secondary mt-0.5", children: subtitle })
|
|
1153
|
+
] }),
|
|
1154
|
+
actions && actions.length > 0 && /* @__PURE__ */ jsx12("div", { className: "flex items-center gap-2 shrink-0 flex-wrap justify-end", children: actions.map((action, i) => {
|
|
1155
|
+
var _a;
|
|
1156
|
+
const cls = cn(
|
|
1157
|
+
"inline-flex items-center gap-2 px-4 py-2 rounded-md text-sm font-medium transition-colors",
|
|
1158
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus",
|
|
1159
|
+
"disabled:opacity-50 disabled:cursor-not-allowed",
|
|
1160
|
+
variantMap[(_a = action.variant) != null ? _a : "primary"]
|
|
1161
|
+
);
|
|
1162
|
+
if (action.href) {
|
|
1163
|
+
return /* @__PURE__ */ jsx12("a", { href: action.href, className: cls, children: action.label }, i);
|
|
1164
|
+
}
|
|
1165
|
+
return /* @__PURE__ */ jsx12(
|
|
1166
|
+
"button",
|
|
1167
|
+
{
|
|
1168
|
+
type: "button",
|
|
1169
|
+
onClick: action.onClick,
|
|
1170
|
+
disabled: action.disabled,
|
|
1171
|
+
className: cls,
|
|
1172
|
+
children: action.label
|
|
1173
|
+
},
|
|
1174
|
+
i
|
|
1175
|
+
);
|
|
1176
|
+
}) })
|
|
1177
|
+
]
|
|
1178
|
+
}
|
|
1179
|
+
);
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
// modules/ui/Overlays/Popover/index.tsx
|
|
1183
|
+
import { useRef as useRef5, useState as useState5 } from "react";
|
|
1184
|
+
|
|
1185
|
+
// modules/ui/Overlays/shared/useDismiss.ts
|
|
1186
|
+
import { useEffect as useEffect5 } from "react";
|
|
1187
|
+
function useDismiss({
|
|
1188
|
+
active,
|
|
1189
|
+
ref,
|
|
1190
|
+
onDismiss,
|
|
1191
|
+
escape = true,
|
|
1192
|
+
outsidePointer = true
|
|
1193
|
+
}) {
|
|
1194
|
+
useEffect5(() => {
|
|
1195
|
+
if (!active) return;
|
|
1196
|
+
function onKey(e) {
|
|
1197
|
+
if (!escape) return;
|
|
1198
|
+
if (e.key !== "Escape") return;
|
|
1199
|
+
if (!isFocusTrapTopLayer(ref)) return;
|
|
1200
|
+
onDismiss();
|
|
1201
|
+
}
|
|
1202
|
+
function onPointer(e) {
|
|
1203
|
+
if (!outsidePointer) return;
|
|
1204
|
+
const root = ref.current;
|
|
1205
|
+
if (!root) return;
|
|
1206
|
+
if (root.contains(e.target)) return;
|
|
1207
|
+
if (!isFocusTrapTopLayer(ref)) return;
|
|
1208
|
+
onDismiss();
|
|
1209
|
+
}
|
|
1210
|
+
document.addEventListener("keydown", onKey);
|
|
1211
|
+
document.addEventListener("pointerdown", onPointer, true);
|
|
1212
|
+
return () => {
|
|
1213
|
+
document.removeEventListener("keydown", onKey);
|
|
1214
|
+
document.removeEventListener("pointerdown", onPointer, true);
|
|
1215
|
+
};
|
|
1216
|
+
}, [active, ref, onDismiss, escape, outsidePointer]);
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
// modules/ui/Overlays/shared/positioning.ts
|
|
1220
|
+
var placementClasses = {
|
|
1221
|
+
bottom: "top-full left-0 mt-2",
|
|
1222
|
+
top: "bottom-full left-0 mb-2",
|
|
1223
|
+
left: "right-full top-0 mr-2",
|
|
1224
|
+
right: "left-full top-0 ml-2"
|
|
1225
|
+
};
|
|
1226
|
+
|
|
1227
|
+
// modules/ui/Overlays/Popover/index.tsx
|
|
1228
|
+
import { jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1229
|
+
function Popover({
|
|
1230
|
+
trigger,
|
|
1231
|
+
children,
|
|
1232
|
+
placement = "bottom",
|
|
1233
|
+
className,
|
|
1234
|
+
focusTrap = true
|
|
1235
|
+
}) {
|
|
1236
|
+
const [open, setOpen] = useState5(false);
|
|
1237
|
+
const containerRef = useRef5(null);
|
|
1238
|
+
const panelRef = useRef5(null);
|
|
1239
|
+
useDismiss({
|
|
1240
|
+
active: open,
|
|
1241
|
+
ref: containerRef,
|
|
1242
|
+
onDismiss: () => setOpen(false)
|
|
1243
|
+
});
|
|
1244
|
+
useFocusTrap(panelRef, {
|
|
1245
|
+
active: open && focusTrap,
|
|
1246
|
+
onEscape: () => setOpen(false),
|
|
1247
|
+
// useDismiss already handles Escape — avoid double handling.
|
|
1248
|
+
handleEscape: false
|
|
1249
|
+
});
|
|
1250
|
+
return /* @__PURE__ */ jsxs11("div", { ref: containerRef, className: "relative inline-block", children: [
|
|
1251
|
+
/* @__PURE__ */ jsx13("div", { onClick: () => setOpen((o) => !o), children: trigger }),
|
|
1252
|
+
open && /* @__PURE__ */ jsx13(
|
|
1253
|
+
"div",
|
|
1254
|
+
{
|
|
1255
|
+
ref: panelRef,
|
|
1256
|
+
role: "dialog",
|
|
1257
|
+
tabIndex: -1,
|
|
1258
|
+
"data-state": open ? "open" : "closed",
|
|
1259
|
+
className: cn(
|
|
1260
|
+
"absolute z-[70] min-w-[12rem] rounded-lg border border-border bg-surface-raised shadow-xl",
|
|
1261
|
+
"focus-visible:outline-none",
|
|
1262
|
+
placementClasses[placement],
|
|
1263
|
+
className
|
|
1264
|
+
),
|
|
1265
|
+
children
|
|
1266
|
+
}
|
|
1267
|
+
)
|
|
1268
|
+
] });
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
// modules/ui/Slider/index.tsx
|
|
1272
|
+
import { useCallback as useCallback3, useState as useState7 } from "react";
|
|
1273
|
+
|
|
1274
|
+
// modules/ui/Slider/parts/Track.tsx
|
|
1275
|
+
import { jsx as jsx14 } from "react/jsx-runtime";
|
|
1276
|
+
function Track({
|
|
1277
|
+
current,
|
|
1278
|
+
isDragging,
|
|
1279
|
+
offsetPx,
|
|
1280
|
+
pointerHandlers,
|
|
1281
|
+
children
|
|
1282
|
+
}) {
|
|
1283
|
+
const baseTransform = `translateX(-${current * 100}%)`;
|
|
1284
|
+
const transform = offsetPx != null ? `${baseTransform} translateX(${offsetPx}px)` : baseTransform;
|
|
1285
|
+
return /* @__PURE__ */ jsx14(
|
|
1286
|
+
"div",
|
|
1287
|
+
__spreadProps(__spreadValues({
|
|
1288
|
+
className: cn(
|
|
1289
|
+
"flex ease-in-out will-change-transform",
|
|
1290
|
+
// While dragging we want zero animation; otherwise use the standard 350 ms snap.
|
|
1291
|
+
isDragging ? "transition-none" : "transition-transform duration-350",
|
|
1292
|
+
// `touch-pan-y` lets vertical page scrolls pass through but reserves
|
|
1293
|
+
// horizontal gestures for the slider drag handler.
|
|
1294
|
+
"touch-pan-y select-none",
|
|
1295
|
+
isDragging && "cursor-grabbing"
|
|
1296
|
+
),
|
|
1297
|
+
style: { transform }
|
|
1298
|
+
}, pointerHandlers), {
|
|
1299
|
+
children
|
|
1300
|
+
})
|
|
1301
|
+
);
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
// modules/ui/Slider/parts/Slide.tsx
|
|
1305
|
+
import { jsx as jsx15 } from "react/jsx-runtime";
|
|
1306
|
+
function Slide({ index, total, isActive, className, children }) {
|
|
1307
|
+
return /* @__PURE__ */ jsx15(
|
|
1308
|
+
"div",
|
|
1309
|
+
{
|
|
1310
|
+
role: "group",
|
|
1311
|
+
"aria-roledescription": "slide",
|
|
1312
|
+
"aria-label": `Slide ${index + 1} of ${total}`,
|
|
1313
|
+
"aria-hidden": !isActive,
|
|
1314
|
+
className: cn("w-full shrink-0", className),
|
|
1315
|
+
children
|
|
1316
|
+
}
|
|
1317
|
+
);
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
// modules/ui/Slider/parts/Arrows.tsx
|
|
1321
|
+
import { FontAwesomeIcon as FontAwesomeIcon6 } from "@fortawesome/react-fontawesome";
|
|
1322
|
+
import { faChevronLeft, faChevronRight } from "@fortawesome/free-solid-svg-icons";
|
|
1323
|
+
import { Fragment as Fragment3, jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
1324
|
+
var ARROW_BTN = cn(
|
|
1325
|
+
"absolute top-1/2 -translate-y-1/2 z-10 w-9 h-9 rounded-full",
|
|
1326
|
+
"bg-black/40 hover:bg-black/60 text-white",
|
|
1327
|
+
"flex items-center justify-center transition-colors",
|
|
1328
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white"
|
|
1329
|
+
);
|
|
1330
|
+
function Arrows({ canPrev, canNext, onPrev, onNext }) {
|
|
1331
|
+
return /* @__PURE__ */ jsxs12(Fragment3, { children: [
|
|
1332
|
+
canPrev && /* @__PURE__ */ jsx16(
|
|
1333
|
+
"button",
|
|
1334
|
+
{
|
|
1335
|
+
type: "button",
|
|
1336
|
+
onClick: onPrev,
|
|
1337
|
+
"aria-label": "Previous slide",
|
|
1338
|
+
className: cn(ARROW_BTN, "left-3"),
|
|
1339
|
+
children: /* @__PURE__ */ jsx16(FontAwesomeIcon6, { icon: faChevronLeft, className: "w-3 h-3", "aria-hidden": "true" })
|
|
1340
|
+
}
|
|
1341
|
+
),
|
|
1342
|
+
canNext && /* @__PURE__ */ jsx16(
|
|
1343
|
+
"button",
|
|
1344
|
+
{
|
|
1345
|
+
type: "button",
|
|
1346
|
+
onClick: onNext,
|
|
1347
|
+
"aria-label": "Next slide",
|
|
1348
|
+
className: cn(ARROW_BTN, "right-3"),
|
|
1349
|
+
children: /* @__PURE__ */ jsx16(FontAwesomeIcon6, { icon: faChevronRight, className: "w-3 h-3", "aria-hidden": "true" })
|
|
1350
|
+
}
|
|
1351
|
+
)
|
|
1352
|
+
] });
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
// modules/ui/Slider/parts/Dots.tsx
|
|
1356
|
+
import { jsx as jsx17 } from "react/jsx-runtime";
|
|
1357
|
+
function Dots({ total, current, onSelect }) {
|
|
1358
|
+
return /* @__PURE__ */ jsx17(
|
|
1359
|
+
"div",
|
|
1360
|
+
{
|
|
1361
|
+
className: "absolute bottom-3 left-1/2 -translate-x-1/2 flex gap-1.5 z-10",
|
|
1362
|
+
role: "tablist",
|
|
1363
|
+
"aria-label": "Slide indicators",
|
|
1364
|
+
children: Array.from({ length: total }).map((_, i) => /* @__PURE__ */ jsx17(
|
|
1365
|
+
"button",
|
|
1366
|
+
{
|
|
1367
|
+
type: "button",
|
|
1368
|
+
role: "tab",
|
|
1369
|
+
"aria-selected": i === current,
|
|
1370
|
+
"aria-label": `Go to slide ${i + 1}`,
|
|
1371
|
+
onClick: () => onSelect(i),
|
|
1372
|
+
className: cn(
|
|
1373
|
+
"h-2 rounded-full transition-all duration-300",
|
|
1374
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white",
|
|
1375
|
+
i === current ? "w-5 bg-white" : "w-2 bg-white/40 hover:bg-white/70"
|
|
1376
|
+
)
|
|
1377
|
+
},
|
|
1378
|
+
i
|
|
1379
|
+
))
|
|
1380
|
+
}
|
|
1381
|
+
);
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
// modules/ui/Slider/hooks/useDrag.ts
|
|
1385
|
+
import { useCallback as useCallback2, useRef as useRef6, useState as useState6 } from "react";
|
|
1386
|
+
var VELOCITY_PER_EXTRA_SLIDE = 0.5;
|
|
1387
|
+
var EDGE_RESISTANCE = 0.4;
|
|
1388
|
+
var VELOCITY_SAMPLE_WINDOW_MS = 100;
|
|
1389
|
+
function useDrag({
|
|
1390
|
+
current,
|
|
1391
|
+
total,
|
|
1392
|
+
loop,
|
|
1393
|
+
dragThreshold,
|
|
1394
|
+
goTo
|
|
1395
|
+
}) {
|
|
1396
|
+
const [dragState, setDragState] = useState6({
|
|
1397
|
+
offsetPx: null,
|
|
1398
|
+
trackWidth: 0,
|
|
1399
|
+
isDragging: false
|
|
1400
|
+
});
|
|
1401
|
+
const startXRef = useRef6(0);
|
|
1402
|
+
const startTimeRef = useRef6(0);
|
|
1403
|
+
const trackWidthRef = useRef6(0);
|
|
1404
|
+
const samplesRef = useRef6([]);
|
|
1405
|
+
const activePointerRef = useRef6(null);
|
|
1406
|
+
const onPointerDown = useCallback2(
|
|
1407
|
+
(e) => {
|
|
1408
|
+
if (e.pointerType === "mouse" && e.button !== 0) return;
|
|
1409
|
+
if (total <= 1) return;
|
|
1410
|
+
const target = e.currentTarget;
|
|
1411
|
+
const rect = target.getBoundingClientRect();
|
|
1412
|
+
trackWidthRef.current = rect.width;
|
|
1413
|
+
startXRef.current = e.clientX;
|
|
1414
|
+
startTimeRef.current = e.timeStamp;
|
|
1415
|
+
samplesRef.current = [{ x: e.clientX, t: e.timeStamp }];
|
|
1416
|
+
activePointerRef.current = e.pointerId;
|
|
1417
|
+
try {
|
|
1418
|
+
target.setPointerCapture(e.pointerId);
|
|
1419
|
+
} catch (e2) {
|
|
1420
|
+
}
|
|
1421
|
+
setDragState({
|
|
1422
|
+
offsetPx: 0,
|
|
1423
|
+
trackWidth: rect.width,
|
|
1424
|
+
isDragging: true
|
|
1425
|
+
});
|
|
1426
|
+
},
|
|
1427
|
+
[total]
|
|
1428
|
+
);
|
|
1429
|
+
const onPointerMove = useCallback2(
|
|
1430
|
+
(e) => {
|
|
1431
|
+
if (activePointerRef.current !== e.pointerId) return;
|
|
1432
|
+
let delta = e.clientX - startXRef.current;
|
|
1433
|
+
if (!loop) {
|
|
1434
|
+
const atFirst = current === 0 && delta > 0;
|
|
1435
|
+
const atLast = current === total - 1 && delta < 0;
|
|
1436
|
+
if (atFirst || atLast) delta *= EDGE_RESISTANCE;
|
|
1437
|
+
}
|
|
1438
|
+
const now = e.timeStamp;
|
|
1439
|
+
samplesRef.current.push({ x: e.clientX, t: now });
|
|
1440
|
+
while (samplesRef.current.length > 1 && now - samplesRef.current[0].t > VELOCITY_SAMPLE_WINDOW_MS) {
|
|
1441
|
+
samplesRef.current.shift();
|
|
1442
|
+
}
|
|
1443
|
+
setDragState((s) => __spreadProps(__spreadValues({}, s), { offsetPx: delta }));
|
|
1444
|
+
},
|
|
1445
|
+
[current, loop, total]
|
|
1446
|
+
);
|
|
1447
|
+
const endDrag = useCallback2(
|
|
1448
|
+
(e) => {
|
|
1449
|
+
var _a, _b;
|
|
1450
|
+
if (activePointerRef.current !== e.pointerId) return;
|
|
1451
|
+
activePointerRef.current = null;
|
|
1452
|
+
try {
|
|
1453
|
+
e.currentTarget.releasePointerCapture(e.pointerId);
|
|
1454
|
+
} catch (e2) {
|
|
1455
|
+
}
|
|
1456
|
+
const samples = samplesRef.current;
|
|
1457
|
+
const last = (_a = samples[samples.length - 1]) != null ? _a : { x: e.clientX, t: e.timeStamp };
|
|
1458
|
+
const first = (_b = samples[0]) != null ? _b : last;
|
|
1459
|
+
const totalDelta = e.clientX - startXRef.current;
|
|
1460
|
+
const dt = Math.max(1, last.t - first.t);
|
|
1461
|
+
const velocity = (last.x - first.x) / dt;
|
|
1462
|
+
const distance = Math.abs(totalDelta);
|
|
1463
|
+
const direction = totalDelta < 0 ? 1 : -1;
|
|
1464
|
+
setDragState({ offsetPx: null, trackWidth: 0, isDragging: false });
|
|
1465
|
+
samplesRef.current = [];
|
|
1466
|
+
if (distance < dragThreshold && Math.abs(velocity) < VELOCITY_PER_EXTRA_SLIDE) {
|
|
1467
|
+
return;
|
|
1468
|
+
}
|
|
1469
|
+
let step = distance >= dragThreshold ? 1 : 0;
|
|
1470
|
+
const flickDir = velocity < 0 ? 1 : -1;
|
|
1471
|
+
const flickStep = Math.floor(Math.abs(velocity) / VELOCITY_PER_EXTRA_SLIDE);
|
|
1472
|
+
let signedStep = direction * step;
|
|
1473
|
+
if (flickStep > 0 && flickDir === direction) {
|
|
1474
|
+
signedStep = direction * (step + flickStep);
|
|
1475
|
+
} else if (flickStep > 0 && step === 0) {
|
|
1476
|
+
signedStep = flickDir * flickStep;
|
|
1477
|
+
}
|
|
1478
|
+
if (signedStep === 0) return;
|
|
1479
|
+
goTo(current + signedStep);
|
|
1480
|
+
},
|
|
1481
|
+
[current, dragThreshold, goTo]
|
|
1482
|
+
);
|
|
1483
|
+
return {
|
|
1484
|
+
dragState,
|
|
1485
|
+
handlers: {
|
|
1486
|
+
onPointerDown,
|
|
1487
|
+
onPointerMove,
|
|
1488
|
+
onPointerUp: endDrag,
|
|
1489
|
+
onPointerCancel: endDrag
|
|
1490
|
+
}
|
|
1491
|
+
};
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
// modules/ui/Slider/hooks/useAutoPlay.ts
|
|
1495
|
+
import { useEffect as useEffect6, useRef as useRef7 } from "react";
|
|
1496
|
+
function useAutoPlay({ enabled, interval, total, onTick }) {
|
|
1497
|
+
const onTickRef = useRef7(onTick);
|
|
1498
|
+
useEffect6(() => {
|
|
1499
|
+
onTickRef.current = onTick;
|
|
1500
|
+
}, [onTick]);
|
|
1501
|
+
useEffect6(() => {
|
|
1502
|
+
if (!enabled || total <= 1) return;
|
|
1503
|
+
const id = setInterval(() => onTickRef.current(), interval);
|
|
1504
|
+
return () => clearInterval(id);
|
|
1505
|
+
}, [enabled, interval, total]);
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
// modules/ui/Slider/index.tsx
|
|
1509
|
+
import { jsx as jsx18, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
1510
|
+
var TRANSITION_MS = 350;
|
|
1511
|
+
function Slider({
|
|
1512
|
+
slides,
|
|
1513
|
+
autoPlay = false,
|
|
1514
|
+
autoPlayInterval = 4e3,
|
|
1515
|
+
showDots = true,
|
|
1516
|
+
showArrows = true,
|
|
1517
|
+
loop = true,
|
|
1518
|
+
dragThreshold = 50,
|
|
1519
|
+
className,
|
|
1520
|
+
slideClassName,
|
|
1521
|
+
ariaLabel = "Content slider"
|
|
1522
|
+
}) {
|
|
1523
|
+
const [current, setCurrent] = useState7(0);
|
|
1524
|
+
const [isTransitioning, setIsTransitioning] = useState7(false);
|
|
1525
|
+
const total = slides.length;
|
|
1526
|
+
const goTo = useCallback3(
|
|
1527
|
+
(index) => {
|
|
1528
|
+
if (isTransitioning) return;
|
|
1529
|
+
const target = loop ? (index % total + total) % total : Math.max(0, Math.min(index, total - 1));
|
|
1530
|
+
if (target === current) return;
|
|
1531
|
+
setIsTransitioning(true);
|
|
1532
|
+
setCurrent(target);
|
|
1533
|
+
setTimeout(() => setIsTransitioning(false), TRANSITION_MS);
|
|
1534
|
+
},
|
|
1535
|
+
[current, isTransitioning, loop, total]
|
|
1536
|
+
);
|
|
1537
|
+
const prev = useCallback3(() => goTo(current - 1), [current, goTo]);
|
|
1538
|
+
const next = useCallback3(() => goTo(current + 1), [current, goTo]);
|
|
1539
|
+
const { dragState, handlers: dragHandlers } = useDrag({
|
|
1540
|
+
current,
|
|
1541
|
+
total,
|
|
1542
|
+
loop,
|
|
1543
|
+
dragThreshold,
|
|
1544
|
+
goTo
|
|
1545
|
+
});
|
|
1546
|
+
useAutoPlay({
|
|
1547
|
+
enabled: autoPlay,
|
|
1548
|
+
interval: autoPlayInterval,
|
|
1549
|
+
total,
|
|
1550
|
+
onTick: useCallback3(() => {
|
|
1551
|
+
if (dragState.isDragging) return;
|
|
1552
|
+
setCurrent((c) => (c + 1) % total);
|
|
1553
|
+
}, [dragState.isDragging, total])
|
|
1554
|
+
});
|
|
1555
|
+
if (total === 0) return null;
|
|
1556
|
+
const canPrev = loop || current > 0;
|
|
1557
|
+
const canNext = loop || current < total - 1;
|
|
1558
|
+
return /* @__PURE__ */ jsxs13(
|
|
1559
|
+
"div",
|
|
1560
|
+
{
|
|
1561
|
+
className: cn("relative overflow-hidden rounded-xl", className),
|
|
1562
|
+
role: "region",
|
|
1563
|
+
"aria-label": ariaLabel,
|
|
1564
|
+
"aria-roledescription": "carousel",
|
|
1565
|
+
children: [
|
|
1566
|
+
/* @__PURE__ */ jsx18(
|
|
1567
|
+
Track,
|
|
1568
|
+
{
|
|
1569
|
+
current,
|
|
1570
|
+
isDragging: dragState.isDragging,
|
|
1571
|
+
offsetPx: dragState.offsetPx,
|
|
1572
|
+
pointerHandlers: dragHandlers,
|
|
1573
|
+
children: slides.map((slide, i) => {
|
|
1574
|
+
const isObject = slide !== null && typeof slide === "object" && "content" in slide;
|
|
1575
|
+
const key = isObject ? slide.id : i;
|
|
1576
|
+
const content = isObject ? slide.content : slide;
|
|
1577
|
+
return /* @__PURE__ */ jsx18(
|
|
1578
|
+
Slide,
|
|
1579
|
+
{
|
|
1580
|
+
index: i,
|
|
1581
|
+
total,
|
|
1582
|
+
isActive: i === current,
|
|
1583
|
+
className: slideClassName,
|
|
1584
|
+
children: content
|
|
1585
|
+
},
|
|
1586
|
+
key
|
|
1587
|
+
);
|
|
1588
|
+
})
|
|
1589
|
+
}
|
|
1590
|
+
),
|
|
1591
|
+
showArrows && total > 1 && /* @__PURE__ */ jsx18(Arrows, { canPrev, canNext, onPrev: prev, onNext: next }),
|
|
1592
|
+
showDots && total > 1 && /* @__PURE__ */ jsx18(Dots, { total, current, onSelect: goTo })
|
|
1593
|
+
]
|
|
1594
|
+
}
|
|
1595
|
+
);
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
// modules/ui/TabButton.tsx
|
|
1599
|
+
import { jsx as jsx19, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
1600
|
+
function TabButton({ active, onClick, children, count, className }) {
|
|
1601
|
+
return /* @__PURE__ */ jsxs14(
|
|
1602
|
+
"button",
|
|
1603
|
+
{
|
|
1604
|
+
onClick,
|
|
1605
|
+
className: cn(
|
|
1606
|
+
"flex items-center gap-1.5 px-4 py-2 rounded-lg text-sm font-semibold transition-colors",
|
|
1607
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus",
|
|
1608
|
+
active ? "bg-primary text-primary-fg shadow-sm" : "text-text-secondary hover:text-text-primary hover:bg-surface-overlay",
|
|
1609
|
+
className
|
|
1610
|
+
),
|
|
1611
|
+
children: [
|
|
1612
|
+
children,
|
|
1613
|
+
count !== void 0 && /* @__PURE__ */ jsx19(
|
|
1614
|
+
"span",
|
|
1615
|
+
{
|
|
1616
|
+
className: cn(
|
|
1617
|
+
"text-[10px] font-bold px-1.5 py-0.5 rounded-full leading-none",
|
|
1618
|
+
active ? "bg-primary-fg/20 text-primary-fg" : "bg-surface-sunken text-text-disabled"
|
|
1619
|
+
),
|
|
1620
|
+
children: count
|
|
1621
|
+
}
|
|
1622
|
+
)
|
|
1623
|
+
]
|
|
1624
|
+
}
|
|
1625
|
+
);
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
// modules/ui/TabGroup.tsx
|
|
1629
|
+
import { useRef as useRef8, useState as useState8 } from "react";
|
|
1630
|
+
import { jsx as jsx20, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
1631
|
+
function TabGroup({
|
|
1632
|
+
tabs,
|
|
1633
|
+
defaultTab,
|
|
1634
|
+
label = "Tabs",
|
|
1635
|
+
lazy = false,
|
|
1636
|
+
className
|
|
1637
|
+
}) {
|
|
1638
|
+
var _a, _b, _c, _d;
|
|
1639
|
+
const [active, setActive] = useState8((_b = defaultTab != null ? defaultTab : (_a = tabs[0]) == null ? void 0 : _a.id) != null ? _b : "");
|
|
1640
|
+
const activated = useRef8(/* @__PURE__ */ new Set([(_d = defaultTab != null ? defaultTab : (_c = tabs[0]) == null ? void 0 : _c.id) != null ? _d : ""]));
|
|
1641
|
+
function activate(id) {
|
|
1642
|
+
setActive(id);
|
|
1643
|
+
activated.current.add(id);
|
|
1644
|
+
}
|
|
1645
|
+
function handleKeyDown(e, index) {
|
|
1646
|
+
let nextIdx = null;
|
|
1647
|
+
if (e.key === "ArrowRight") {
|
|
1648
|
+
nextIdx = (index + 1) % tabs.length;
|
|
1649
|
+
while (tabs[nextIdx].disabled && nextIdx !== index) nextIdx = (nextIdx + 1) % tabs.length;
|
|
1650
|
+
} else if (e.key === "ArrowLeft") {
|
|
1651
|
+
nextIdx = (index - 1 + tabs.length) % tabs.length;
|
|
1652
|
+
while (tabs[nextIdx].disabled && nextIdx !== index) nextIdx = (nextIdx - 1 + tabs.length) % tabs.length;
|
|
1653
|
+
} else if (e.key === "Home") {
|
|
1654
|
+
nextIdx = tabs.findIndex((t) => !t.disabled);
|
|
1655
|
+
} else if (e.key === "End") {
|
|
1656
|
+
nextIdx = tabs.length - 1 - [...tabs].reverse().findIndex((t) => !t.disabled);
|
|
1657
|
+
}
|
|
1658
|
+
if (nextIdx !== null && !tabs[nextIdx].disabled) {
|
|
1659
|
+
activate(tabs[nextIdx].id);
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
return /* @__PURE__ */ jsxs15("div", { className: cn("w-full", className), children: [
|
|
1663
|
+
/* @__PURE__ */ jsx20("div", { role: "tablist", "aria-label": label, className: "flex border-b border-border pb-3", children: tabs.map((tab, i) => {
|
|
1664
|
+
const isActive = tab.id === active;
|
|
1665
|
+
return /* @__PURE__ */ jsxs15(
|
|
1666
|
+
"button",
|
|
1667
|
+
{
|
|
1668
|
+
role: "tab",
|
|
1669
|
+
id: `tab-btn-${tab.id}`,
|
|
1670
|
+
"aria-selected": isActive,
|
|
1671
|
+
"aria-controls": `tabpanel-${tab.id}`,
|
|
1672
|
+
"aria-disabled": tab.disabled,
|
|
1673
|
+
tabIndex: isActive ? 0 : -1,
|
|
1674
|
+
onClick: () => !tab.disabled && activate(tab.id),
|
|
1675
|
+
onKeyDown: (e) => handleKeyDown(e, i),
|
|
1676
|
+
className: cn(
|
|
1677
|
+
"inline-flex items-center gap-1.5 px-4 py-2.5 text-sm font-medium border-b-2 transition-colors",
|
|
1678
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus",
|
|
1679
|
+
isActive ? "border-primary text-primary" : "border-transparent text-text-secondary hover:text-text-primary hover:border-border",
|
|
1680
|
+
tab.disabled && "opacity-40 cursor-not-allowed pointer-events-none"
|
|
1681
|
+
),
|
|
1682
|
+
children: [
|
|
1683
|
+
tab.icon && /* @__PURE__ */ jsx20("span", { "aria-hidden": "true", className: "shrink-0", children: tab.icon }),
|
|
1684
|
+
tab.label,
|
|
1685
|
+
tab.badge && /* @__PURE__ */ jsx20("span", { className: "shrink-0", children: tab.badge })
|
|
1686
|
+
]
|
|
1687
|
+
},
|
|
1688
|
+
tab.id
|
|
1689
|
+
);
|
|
1690
|
+
}) }),
|
|
1691
|
+
tabs.map((tab) => {
|
|
1692
|
+
const isActive = tab.id === active;
|
|
1693
|
+
const everActivated = activated.current.has(tab.id);
|
|
1694
|
+
const shouldRender = !lazy || everActivated;
|
|
1695
|
+
return /* @__PURE__ */ jsx20(
|
|
1696
|
+
"div",
|
|
1697
|
+
{
|
|
1698
|
+
id: `tabpanel-${tab.id}`,
|
|
1699
|
+
role: "tabpanel",
|
|
1700
|
+
"aria-labelledby": `tab-btn-${tab.id}`,
|
|
1701
|
+
tabIndex: 0,
|
|
1702
|
+
hidden: !isActive,
|
|
1703
|
+
className: "py-4 focus-visible:outline-none",
|
|
1704
|
+
children: shouldRender ? tab.content : null
|
|
1705
|
+
},
|
|
1706
|
+
tab.id
|
|
1707
|
+
);
|
|
1708
|
+
})
|
|
1709
|
+
] });
|
|
1710
|
+
}
|
|
1711
|
+
|
|
1712
|
+
// modules/ui/TreeView/index.tsx
|
|
1713
|
+
import { FontAwesomeIcon as FontAwesomeIcon8 } from "@fortawesome/react-fontawesome";
|
|
1714
|
+
import {
|
|
1715
|
+
faAngleDoubleDown,
|
|
1716
|
+
faAngleDoubleUp
|
|
1717
|
+
} from "@fortawesome/free-solid-svg-icons";
|
|
1718
|
+
import { useMemo as useMemo4 } from "react";
|
|
1719
|
+
|
|
1720
|
+
// modules/ui/TreeView/parts/Node.tsx
|
|
1721
|
+
import { FontAwesomeIcon as FontAwesomeIcon7 } from "@fortawesome/react-fontawesome";
|
|
1722
|
+
import { faChevronDown as faChevronDown2, faChevronRight as faChevronRight2 } from "@fortawesome/free-solid-svg-icons";
|
|
1723
|
+
import { jsx as jsx21, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
1724
|
+
function TreeNodeRow({
|
|
1725
|
+
row,
|
|
1726
|
+
isSelected,
|
|
1727
|
+
isFocused,
|
|
1728
|
+
onActivate,
|
|
1729
|
+
onToggle,
|
|
1730
|
+
onFocus
|
|
1731
|
+
}) {
|
|
1732
|
+
const { node, depth, hasChildren, expanded, level, posInSet, setSize } = row;
|
|
1733
|
+
return /* @__PURE__ */ jsx21(
|
|
1734
|
+
"li",
|
|
1735
|
+
{
|
|
1736
|
+
role: "treeitem",
|
|
1737
|
+
"aria-expanded": hasChildren ? expanded : void 0,
|
|
1738
|
+
"aria-selected": isSelected,
|
|
1739
|
+
"aria-level": level,
|
|
1740
|
+
"aria-posinset": posInSet,
|
|
1741
|
+
"aria-setsize": setSize,
|
|
1742
|
+
"data-tree-node-id": node.id,
|
|
1743
|
+
"data-has-children": hasChildren ? "true" : "false",
|
|
1744
|
+
children: /* @__PURE__ */ jsxs16(
|
|
1745
|
+
"div",
|
|
1746
|
+
{
|
|
1747
|
+
tabIndex: isFocused ? 0 : -1,
|
|
1748
|
+
"data-tree-row": true,
|
|
1749
|
+
onClick: (e) => {
|
|
1750
|
+
onFocus(node.id);
|
|
1751
|
+
onActivate(e, node.id);
|
|
1752
|
+
},
|
|
1753
|
+
onFocus: () => onFocus(node.id),
|
|
1754
|
+
style: { paddingLeft: `${depth * 1.25}rem` },
|
|
1755
|
+
className: cn(
|
|
1756
|
+
"flex items-center gap-1.5 px-2 py-1.5 text-sm rounded-md cursor-pointer select-none",
|
|
1757
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus",
|
|
1758
|
+
"hover:bg-surface-overlay transition-colors",
|
|
1759
|
+
isSelected && "bg-primary-subtle text-primary font-medium"
|
|
1760
|
+
),
|
|
1761
|
+
children: [
|
|
1762
|
+
hasChildren ? /* @__PURE__ */ jsx21(
|
|
1763
|
+
"span",
|
|
1764
|
+
{
|
|
1765
|
+
"aria-hidden": "true",
|
|
1766
|
+
"data-tree-chevron": true,
|
|
1767
|
+
onClick: (e) => {
|
|
1768
|
+
e.stopPropagation();
|
|
1769
|
+
onFocus(node.id);
|
|
1770
|
+
onToggle(node.id);
|
|
1771
|
+
},
|
|
1772
|
+
className: "text-text-disabled w-3 shrink-0 flex items-center justify-center",
|
|
1773
|
+
children: /* @__PURE__ */ jsx21(
|
|
1774
|
+
FontAwesomeIcon7,
|
|
1775
|
+
{
|
|
1776
|
+
icon: expanded ? faChevronDown2 : faChevronRight2,
|
|
1777
|
+
className: "w-2.5 h-2.5"
|
|
1778
|
+
}
|
|
1779
|
+
)
|
|
1780
|
+
}
|
|
1781
|
+
) : /* @__PURE__ */ jsx21("span", { className: "w-3 shrink-0", "aria-hidden": "true" }),
|
|
1782
|
+
/* @__PURE__ */ jsx21("span", { children: node.label })
|
|
1783
|
+
]
|
|
1784
|
+
}
|
|
1785
|
+
)
|
|
1786
|
+
}
|
|
1787
|
+
);
|
|
1788
|
+
}
|
|
1789
|
+
|
|
1790
|
+
// modules/ui/TreeView/hooks/useTreeState.ts
|
|
1791
|
+
import { useCallback as useCallback4, useMemo as useMemo3, useState as useState9 } from "react";
|
|
1792
|
+
function collectAllIds(nodes, out = /* @__PURE__ */ new Set()) {
|
|
1793
|
+
for (const n of nodes) {
|
|
1794
|
+
out.add(n.id);
|
|
1795
|
+
if (n.children && n.children.length) collectAllIds(n.children, out);
|
|
1796
|
+
}
|
|
1797
|
+
return out;
|
|
1798
|
+
}
|
|
1799
|
+
function collectExpandableIds(nodes, out = /* @__PURE__ */ new Set()) {
|
|
1800
|
+
for (const n of nodes) {
|
|
1801
|
+
if (n.children && n.children.length) {
|
|
1802
|
+
out.add(n.id);
|
|
1803
|
+
collectExpandableIds(n.children, out);
|
|
1804
|
+
}
|
|
1805
|
+
}
|
|
1806
|
+
return out;
|
|
1807
|
+
}
|
|
1808
|
+
function flattenVisible(nodes, expanded, depth, parentId, out) {
|
|
1809
|
+
const setSize = nodes.length;
|
|
1810
|
+
nodes.forEach((node, idx) => {
|
|
1811
|
+
const hasChildren = !!(node.children && node.children.length);
|
|
1812
|
+
const isExpanded = hasChildren && expanded.has(node.id);
|
|
1813
|
+
out.push({
|
|
1814
|
+
node,
|
|
1815
|
+
depth,
|
|
1816
|
+
parentId,
|
|
1817
|
+
hasChildren,
|
|
1818
|
+
expanded: isExpanded,
|
|
1819
|
+
level: depth + 1,
|
|
1820
|
+
posInSet: idx + 1,
|
|
1821
|
+
setSize
|
|
1822
|
+
});
|
|
1823
|
+
if (isExpanded) {
|
|
1824
|
+
flattenVisible(node.children, expanded, depth + 1, node.id, out);
|
|
1825
|
+
}
|
|
1826
|
+
});
|
|
1827
|
+
return out;
|
|
1828
|
+
}
|
|
1829
|
+
function useTreeState({
|
|
1830
|
+
nodes,
|
|
1831
|
+
selectionMode,
|
|
1832
|
+
selectedIds: controlledSelectedIds,
|
|
1833
|
+
selectedId: controlledSelectedId,
|
|
1834
|
+
expandedIds: controlledExpandedIds,
|
|
1835
|
+
defaultExpandedIds,
|
|
1836
|
+
initialFocusId,
|
|
1837
|
+
onSelectionChange,
|
|
1838
|
+
onSelect,
|
|
1839
|
+
onExpand
|
|
1840
|
+
}) {
|
|
1841
|
+
const [uncontrolledExpanded, setUncontrolledExpanded] = useState9(() => {
|
|
1842
|
+
if (defaultExpandedIds) return new Set(defaultExpandedIds);
|
|
1843
|
+
return collectExpandableIds(nodes);
|
|
1844
|
+
});
|
|
1845
|
+
const expanded = useMemo3(() => {
|
|
1846
|
+
if (controlledExpandedIds) return new Set(controlledExpandedIds);
|
|
1847
|
+
return uncontrolledExpanded;
|
|
1848
|
+
}, [controlledExpandedIds, uncontrolledExpanded]);
|
|
1849
|
+
const [uncontrolledSelected, setUncontrolledSelected] = useState9(() => {
|
|
1850
|
+
if (controlledSelectedIds && controlledSelectedIds.length) return new Set(controlledSelectedIds);
|
|
1851
|
+
if (controlledSelectedId) return /* @__PURE__ */ new Set([controlledSelectedId]);
|
|
1852
|
+
return /* @__PURE__ */ new Set();
|
|
1853
|
+
});
|
|
1854
|
+
const selected = useMemo3(() => {
|
|
1855
|
+
if (controlledSelectedIds) return new Set(controlledSelectedIds);
|
|
1856
|
+
if (controlledSelectedId !== void 0) return /* @__PURE__ */ new Set([controlledSelectedId]);
|
|
1857
|
+
return uncontrolledSelected;
|
|
1858
|
+
}, [controlledSelectedIds, controlledSelectedId, uncontrolledSelected]);
|
|
1859
|
+
const visibleRows = useMemo3(
|
|
1860
|
+
() => flattenVisible(nodes, expanded, 0, null, []),
|
|
1861
|
+
[nodes, expanded]
|
|
1862
|
+
);
|
|
1863
|
+
const [focusId, setFocusId] = useState9(
|
|
1864
|
+
() => {
|
|
1865
|
+
var _a;
|
|
1866
|
+
return initialFocusId != null ? initialFocusId : (_a = visibleRows[0]) == null ? void 0 : _a.node.id;
|
|
1867
|
+
}
|
|
1868
|
+
);
|
|
1869
|
+
const setExpanded = useCallback4(
|
|
1870
|
+
(id, next) => {
|
|
1871
|
+
onExpand == null ? void 0 : onExpand(id, next);
|
|
1872
|
+
if (!controlledExpandedIds) {
|
|
1873
|
+
setUncontrolledExpanded((prev) => {
|
|
1874
|
+
const out = new Set(prev);
|
|
1875
|
+
if (next) out.add(id);
|
|
1876
|
+
else out.delete(id);
|
|
1877
|
+
return out;
|
|
1878
|
+
});
|
|
1879
|
+
}
|
|
1880
|
+
},
|
|
1881
|
+
[controlledExpandedIds, onExpand]
|
|
1882
|
+
);
|
|
1883
|
+
const toggleExpanded = useCallback4(
|
|
1884
|
+
(id) => {
|
|
1885
|
+
const isOpen = expanded.has(id);
|
|
1886
|
+
setExpanded(id, !isOpen);
|
|
1887
|
+
},
|
|
1888
|
+
[expanded, setExpanded]
|
|
1889
|
+
);
|
|
1890
|
+
const expandAll = useCallback4(() => {
|
|
1891
|
+
const all = collectExpandableIds(nodes);
|
|
1892
|
+
if (!controlledExpandedIds) setUncontrolledExpanded(all);
|
|
1893
|
+
all.forEach((id) => {
|
|
1894
|
+
if (!expanded.has(id)) onExpand == null ? void 0 : onExpand(id, true);
|
|
1895
|
+
});
|
|
1896
|
+
}, [nodes, controlledExpandedIds, expanded, onExpand]);
|
|
1897
|
+
const collapseAll = useCallback4(() => {
|
|
1898
|
+
if (!controlledExpandedIds) setUncontrolledExpanded(/* @__PURE__ */ new Set());
|
|
1899
|
+
expanded.forEach((id) => onExpand == null ? void 0 : onExpand(id, false));
|
|
1900
|
+
}, [controlledExpandedIds, expanded, onExpand]);
|
|
1901
|
+
const commitSelection = useCallback4(
|
|
1902
|
+
(next) => {
|
|
1903
|
+
if (!controlledSelectedIds && controlledSelectedId === void 0) {
|
|
1904
|
+
setUncontrolledSelected(next);
|
|
1905
|
+
}
|
|
1906
|
+
const arr = Array.from(next);
|
|
1907
|
+
onSelectionChange == null ? void 0 : onSelectionChange(arr);
|
|
1908
|
+
if (arr.length) onSelect == null ? void 0 : onSelect(arr[arr.length - 1]);
|
|
1909
|
+
else if (selectionMode === "single") onSelect == null ? void 0 : onSelect("");
|
|
1910
|
+
},
|
|
1911
|
+
[controlledSelectedIds, controlledSelectedId, onSelectionChange, onSelect, selectionMode]
|
|
1912
|
+
);
|
|
1913
|
+
const selectSingle = useCallback4(
|
|
1914
|
+
(id) => {
|
|
1915
|
+
commitSelection(/* @__PURE__ */ new Set([id]));
|
|
1916
|
+
},
|
|
1917
|
+
[commitSelection]
|
|
1918
|
+
);
|
|
1919
|
+
const toggleSelection = useCallback4(
|
|
1920
|
+
(id) => {
|
|
1921
|
+
if (selectionMode === "single") {
|
|
1922
|
+
commitSelection(/* @__PURE__ */ new Set([id]));
|
|
1923
|
+
return;
|
|
1924
|
+
}
|
|
1925
|
+
const next = new Set(selected);
|
|
1926
|
+
if (next.has(id)) next.delete(id);
|
|
1927
|
+
else next.add(id);
|
|
1928
|
+
commitSelection(next);
|
|
1929
|
+
},
|
|
1930
|
+
[selectionMode, selected, commitSelection]
|
|
1931
|
+
);
|
|
1932
|
+
const selectRange = useCallback4(
|
|
1933
|
+
(anchor, target) => {
|
|
1934
|
+
if (selectionMode === "single") {
|
|
1935
|
+
commitSelection(/* @__PURE__ */ new Set([target]));
|
|
1936
|
+
return;
|
|
1937
|
+
}
|
|
1938
|
+
const order = visibleRows.map((r) => r.node.id);
|
|
1939
|
+
const a = order.indexOf(anchor);
|
|
1940
|
+
const b = order.indexOf(target);
|
|
1941
|
+
if (a === -1 || b === -1) {
|
|
1942
|
+
commitSelection(/* @__PURE__ */ new Set([target]));
|
|
1943
|
+
return;
|
|
1944
|
+
}
|
|
1945
|
+
const [lo, hi] = a < b ? [a, b] : [b, a];
|
|
1946
|
+
const next = /* @__PURE__ */ new Set();
|
|
1947
|
+
for (let i = lo; i <= hi; i++) next.add(order[i]);
|
|
1948
|
+
commitSelection(next);
|
|
1949
|
+
},
|
|
1950
|
+
[selectionMode, visibleRows, commitSelection]
|
|
1951
|
+
);
|
|
1952
|
+
const moveFocus = useCallback4(
|
|
1953
|
+
(delta) => {
|
|
1954
|
+
if (!visibleRows.length) return;
|
|
1955
|
+
const idx = visibleRows.findIndex((r) => r.node.id === focusId);
|
|
1956
|
+
const safeIdx = idx === -1 ? 0 : idx;
|
|
1957
|
+
const next = Math.max(0, Math.min(visibleRows.length - 1, safeIdx + delta));
|
|
1958
|
+
setFocusId(visibleRows[next].node.id);
|
|
1959
|
+
},
|
|
1960
|
+
[visibleRows, focusId]
|
|
1961
|
+
);
|
|
1962
|
+
const focusFirst = useCallback4(() => {
|
|
1963
|
+
if (visibleRows.length) setFocusId(visibleRows[0].node.id);
|
|
1964
|
+
}, [visibleRows]);
|
|
1965
|
+
const focusLast = useCallback4(() => {
|
|
1966
|
+
if (visibleRows.length) setFocusId(visibleRows[visibleRows.length - 1].node.id);
|
|
1967
|
+
}, [visibleRows]);
|
|
1968
|
+
const findRow = useCallback4(
|
|
1969
|
+
(id) => visibleRows.find((r) => r.node.id === id),
|
|
1970
|
+
[visibleRows]
|
|
1971
|
+
);
|
|
1972
|
+
const selectAllVisible = useCallback4(() => {
|
|
1973
|
+
if (selectionMode !== "multi") return;
|
|
1974
|
+
commitSelection(new Set(visibleRows.map((r) => r.node.id)));
|
|
1975
|
+
}, [selectionMode, visibleRows, commitSelection]);
|
|
1976
|
+
return {
|
|
1977
|
+
expanded,
|
|
1978
|
+
selected,
|
|
1979
|
+
focusId,
|
|
1980
|
+
setFocusId,
|
|
1981
|
+
visibleRows,
|
|
1982
|
+
setExpanded,
|
|
1983
|
+
toggleExpanded,
|
|
1984
|
+
expandAll,
|
|
1985
|
+
collapseAll,
|
|
1986
|
+
selectSingle,
|
|
1987
|
+
toggleSelection,
|
|
1988
|
+
selectRange,
|
|
1989
|
+
selectAllVisible,
|
|
1990
|
+
moveFocus,
|
|
1991
|
+
focusFirst,
|
|
1992
|
+
focusLast,
|
|
1993
|
+
findRow,
|
|
1994
|
+
/** Util for unrelated callers (e.g. devtools). */
|
|
1995
|
+
collectAllIds: () => collectAllIds(nodes)
|
|
1996
|
+
};
|
|
1997
|
+
}
|
|
1998
|
+
|
|
1999
|
+
// modules/ui/TreeView/hooks/useKeyboardNav.ts
|
|
2000
|
+
import { useCallback as useCallback5, useRef as useRef9 } from "react";
|
|
2001
|
+
var TYPE_AHEAD_RESET_MS = 500;
|
|
2002
|
+
function useKeyboardNav({ state, onActivate }) {
|
|
2003
|
+
const {
|
|
2004
|
+
visibleRows,
|
|
2005
|
+
focusId,
|
|
2006
|
+
setFocusId,
|
|
2007
|
+
setExpanded,
|
|
2008
|
+
selected,
|
|
2009
|
+
selectSingle,
|
|
2010
|
+
toggleSelection,
|
|
2011
|
+
selectRange,
|
|
2012
|
+
selectAllVisible,
|
|
2013
|
+
moveFocus,
|
|
2014
|
+
focusFirst,
|
|
2015
|
+
focusLast,
|
|
2016
|
+
findRow
|
|
2017
|
+
} = state;
|
|
2018
|
+
const anchorRef = useRef9(focusId);
|
|
2019
|
+
const typeAheadRef = useRef9({
|
|
2020
|
+
buffer: "",
|
|
2021
|
+
lastAt: 0
|
|
2022
|
+
});
|
|
2023
|
+
const updateAnchorOnFocus = useCallback5(
|
|
2024
|
+
(id) => {
|
|
2025
|
+
anchorRef.current = id;
|
|
2026
|
+
},
|
|
2027
|
+
[]
|
|
2028
|
+
);
|
|
2029
|
+
const jumpTypeAhead = useCallback5(
|
|
2030
|
+
(char) => {
|
|
2031
|
+
var _a, _b;
|
|
2032
|
+
const now = Date.now();
|
|
2033
|
+
const prev = typeAheadRef.current;
|
|
2034
|
+
const buffer = now - prev.lastAt > TYPE_AHEAD_RESET_MS ? char : prev.buffer + char;
|
|
2035
|
+
typeAheadRef.current = { buffer, lastAt: now };
|
|
2036
|
+
if (!visibleRows.length) return;
|
|
2037
|
+
const order = visibleRows;
|
|
2038
|
+
const currentIdx = Math.max(
|
|
2039
|
+
0,
|
|
2040
|
+
order.findIndex((r) => r.node.id === focusId)
|
|
2041
|
+
);
|
|
2042
|
+
const startOffset = buffer.length === 1 ? 1 : 0;
|
|
2043
|
+
const total = order.length;
|
|
2044
|
+
const needle = buffer.toLowerCase();
|
|
2045
|
+
for (let i = 0; i < total; i++) {
|
|
2046
|
+
const probe = order[(currentIdx + startOffset + i) % total];
|
|
2047
|
+
const label = (_b = (_a = probe.node.label) == null ? void 0 : _a.toLowerCase()) != null ? _b : "";
|
|
2048
|
+
if (label.startsWith(needle)) {
|
|
2049
|
+
setFocusId(probe.node.id);
|
|
2050
|
+
return;
|
|
2051
|
+
}
|
|
2052
|
+
}
|
|
2053
|
+
},
|
|
2054
|
+
[visibleRows, focusId, setFocusId]
|
|
2055
|
+
);
|
|
2056
|
+
const onKeyDown = useCallback5(
|
|
2057
|
+
(e) => {
|
|
2058
|
+
var _a, _b, _c, _d, _e;
|
|
2059
|
+
if (!focusId) return;
|
|
2060
|
+
const row = findRow(focusId);
|
|
2061
|
+
if (!row) return;
|
|
2062
|
+
if (e.key.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey && // Space is special — handled below for selection toggle.
|
|
2063
|
+
e.key !== " ") {
|
|
2064
|
+
e.preventDefault();
|
|
2065
|
+
jumpTypeAhead(e.key);
|
|
2066
|
+
return;
|
|
2067
|
+
}
|
|
2068
|
+
switch (e.key) {
|
|
2069
|
+
case "ArrowDown": {
|
|
2070
|
+
e.preventDefault();
|
|
2071
|
+
moveFocus(1);
|
|
2072
|
+
if (e.shiftKey) {
|
|
2073
|
+
const nextIdx = Math.min(visibleRows.length - 1, indexOf(visibleRows, focusId) + 1);
|
|
2074
|
+
const target = (_a = visibleRows[nextIdx]) == null ? void 0 : _a.node.id;
|
|
2075
|
+
if (target) selectRange((_b = anchorRef.current) != null ? _b : focusId, target);
|
|
2076
|
+
}
|
|
2077
|
+
break;
|
|
2078
|
+
}
|
|
2079
|
+
case "ArrowUp": {
|
|
2080
|
+
e.preventDefault();
|
|
2081
|
+
moveFocus(-1);
|
|
2082
|
+
if (e.shiftKey) {
|
|
2083
|
+
const prevIdx = Math.max(0, indexOf(visibleRows, focusId) - 1);
|
|
2084
|
+
const target = (_c = visibleRows[prevIdx]) == null ? void 0 : _c.node.id;
|
|
2085
|
+
if (target) selectRange((_d = anchorRef.current) != null ? _d : focusId, target);
|
|
2086
|
+
}
|
|
2087
|
+
break;
|
|
2088
|
+
}
|
|
2089
|
+
case "ArrowRight": {
|
|
2090
|
+
e.preventDefault();
|
|
2091
|
+
if (row.hasChildren && !row.expanded) {
|
|
2092
|
+
setExpanded(row.node.id, true);
|
|
2093
|
+
} else if (row.hasChildren && row.expanded) {
|
|
2094
|
+
const idx = indexOf(visibleRows, focusId);
|
|
2095
|
+
const child = visibleRows[idx + 1];
|
|
2096
|
+
if (child && child.depth > row.depth) setFocusId(child.node.id);
|
|
2097
|
+
}
|
|
2098
|
+
break;
|
|
2099
|
+
}
|
|
2100
|
+
case "ArrowLeft": {
|
|
2101
|
+
e.preventDefault();
|
|
2102
|
+
if (row.hasChildren && row.expanded) {
|
|
2103
|
+
setExpanded(row.node.id, false);
|
|
2104
|
+
} else if (row.parentId) {
|
|
2105
|
+
setFocusId(row.parentId);
|
|
2106
|
+
}
|
|
2107
|
+
break;
|
|
2108
|
+
}
|
|
2109
|
+
case "Home": {
|
|
2110
|
+
e.preventDefault();
|
|
2111
|
+
focusFirst();
|
|
2112
|
+
break;
|
|
2113
|
+
}
|
|
2114
|
+
case "End": {
|
|
2115
|
+
e.preventDefault();
|
|
2116
|
+
focusLast();
|
|
2117
|
+
break;
|
|
2118
|
+
}
|
|
2119
|
+
case " ": {
|
|
2120
|
+
e.preventDefault();
|
|
2121
|
+
if (e.shiftKey) {
|
|
2122
|
+
selectRange((_e = anchorRef.current) != null ? _e : focusId, focusId);
|
|
2123
|
+
} else if (e.ctrlKey || e.metaKey) {
|
|
2124
|
+
toggleSelection(focusId);
|
|
2125
|
+
anchorRef.current = focusId;
|
|
2126
|
+
} else {
|
|
2127
|
+
toggleSelection(focusId);
|
|
2128
|
+
anchorRef.current = focusId;
|
|
2129
|
+
}
|
|
2130
|
+
break;
|
|
2131
|
+
}
|
|
2132
|
+
case "Enter": {
|
|
2133
|
+
e.preventDefault();
|
|
2134
|
+
if (!selected.has(focusId)) selectSingle(focusId);
|
|
2135
|
+
anchorRef.current = focusId;
|
|
2136
|
+
onActivate == null ? void 0 : onActivate(focusId);
|
|
2137
|
+
break;
|
|
2138
|
+
}
|
|
2139
|
+
case "*": {
|
|
2140
|
+
e.preventDefault();
|
|
2141
|
+
visibleRows.forEach((r) => {
|
|
2142
|
+
if (r.parentId === row.parentId && r.hasChildren && !r.expanded) {
|
|
2143
|
+
setExpanded(r.node.id, true);
|
|
2144
|
+
}
|
|
2145
|
+
});
|
|
2146
|
+
break;
|
|
2147
|
+
}
|
|
2148
|
+
default: {
|
|
2149
|
+
if ((e.ctrlKey || e.metaKey) && (e.key === "a" || e.key === "A")) {
|
|
2150
|
+
e.preventDefault();
|
|
2151
|
+
selectAllVisible();
|
|
2152
|
+
}
|
|
2153
|
+
}
|
|
2154
|
+
}
|
|
2155
|
+
},
|
|
2156
|
+
[
|
|
2157
|
+
focusId,
|
|
2158
|
+
findRow,
|
|
2159
|
+
jumpTypeAhead,
|
|
2160
|
+
moveFocus,
|
|
2161
|
+
visibleRows,
|
|
2162
|
+
selectRange,
|
|
2163
|
+
setExpanded,
|
|
2164
|
+
setFocusId,
|
|
2165
|
+
focusFirst,
|
|
2166
|
+
focusLast,
|
|
2167
|
+
toggleSelection,
|
|
2168
|
+
selectSingle,
|
|
2169
|
+
selectAllVisible,
|
|
2170
|
+
selected,
|
|
2171
|
+
onActivate
|
|
2172
|
+
]
|
|
2173
|
+
);
|
|
2174
|
+
return { onKeyDown, updateAnchorOnFocus, anchorRef };
|
|
2175
|
+
}
|
|
2176
|
+
function indexOf(rows, id) {
|
|
2177
|
+
if (!id) return -1;
|
|
2178
|
+
return rows.findIndex((r) => r.node.id === id);
|
|
2179
|
+
}
|
|
2180
|
+
|
|
2181
|
+
// modules/ui/TreeView/types.ts
|
|
2182
|
+
var DEFAULT_TREE_MESSAGES = {
|
|
2183
|
+
tree: "Tree",
|
|
2184
|
+
expandAll: "Expand all",
|
|
2185
|
+
collapseAll: "Collapse all"
|
|
2186
|
+
};
|
|
2187
|
+
|
|
2188
|
+
// modules/ui/TreeView/index.tsx
|
|
2189
|
+
import { jsx as jsx22, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
2190
|
+
function TreeView({
|
|
2191
|
+
nodes,
|
|
2192
|
+
selectedId,
|
|
2193
|
+
selectedIds,
|
|
2194
|
+
expandedIds,
|
|
2195
|
+
defaultExpandedIds,
|
|
2196
|
+
focusId: initialFocusId,
|
|
2197
|
+
selectionMode = "single",
|
|
2198
|
+
onSelect,
|
|
2199
|
+
onSelectionChange,
|
|
2200
|
+
onExpand,
|
|
2201
|
+
onActivate,
|
|
2202
|
+
label,
|
|
2203
|
+
className,
|
|
2204
|
+
hideToolbar = false,
|
|
2205
|
+
messages
|
|
2206
|
+
}) {
|
|
2207
|
+
const msgs = useMemo4(
|
|
2208
|
+
() => __spreadValues(__spreadValues({}, DEFAULT_TREE_MESSAGES), messages),
|
|
2209
|
+
[messages]
|
|
2210
|
+
);
|
|
2211
|
+
const state = useTreeState({
|
|
2212
|
+
nodes,
|
|
2213
|
+
selectionMode,
|
|
2214
|
+
selectedIds,
|
|
2215
|
+
selectedId,
|
|
2216
|
+
expandedIds,
|
|
2217
|
+
defaultExpandedIds,
|
|
2218
|
+
initialFocusId,
|
|
2219
|
+
onSelectionChange,
|
|
2220
|
+
onSelect,
|
|
2221
|
+
onExpand
|
|
2222
|
+
});
|
|
2223
|
+
const {
|
|
2224
|
+
focusId,
|
|
2225
|
+
setFocusId,
|
|
2226
|
+
visibleRows,
|
|
2227
|
+
selected,
|
|
2228
|
+
toggleExpanded,
|
|
2229
|
+
expandAll,
|
|
2230
|
+
collapseAll,
|
|
2231
|
+
selectSingle,
|
|
2232
|
+
toggleSelection,
|
|
2233
|
+
selectRange
|
|
2234
|
+
} = state;
|
|
2235
|
+
const { onKeyDown, anchorRef } = useKeyboardNav({ state, onActivate });
|
|
2236
|
+
const handleActivate = (e, id) => {
|
|
2237
|
+
var _a;
|
|
2238
|
+
const row = state.findRow(id);
|
|
2239
|
+
if (!row) return;
|
|
2240
|
+
if (e.shiftKey) {
|
|
2241
|
+
selectRange((_a = anchorRef.current) != null ? _a : id, id);
|
|
2242
|
+
return;
|
|
2243
|
+
}
|
|
2244
|
+
if (e.metaKey || e.ctrlKey) {
|
|
2245
|
+
toggleSelection(id);
|
|
2246
|
+
anchorRef.current = id;
|
|
2247
|
+
return;
|
|
2248
|
+
}
|
|
2249
|
+
if (row.hasChildren) {
|
|
2250
|
+
toggleExpanded(id);
|
|
2251
|
+
if (selectionMode === "single") selectSingle(id);
|
|
2252
|
+
else if (!selected.has(id)) toggleSelection(id);
|
|
2253
|
+
anchorRef.current = id;
|
|
2254
|
+
} else {
|
|
2255
|
+
selectSingle(id);
|
|
2256
|
+
anchorRef.current = id;
|
|
2257
|
+
}
|
|
2258
|
+
};
|
|
2259
|
+
const showToolbar = !hideToolbar && visibleRows.some((r) => r.hasChildren);
|
|
2260
|
+
return /* @__PURE__ */ jsxs17("div", { className: cn("flex flex-col gap-1", className), children: [
|
|
2261
|
+
showToolbar && /* @__PURE__ */ jsxs17(
|
|
2262
|
+
"div",
|
|
2263
|
+
{
|
|
2264
|
+
"data-tree-toolbar": true,
|
|
2265
|
+
className: "flex items-center gap-1 px-1 pb-1 text-xs text-text-secondary",
|
|
2266
|
+
children: [
|
|
2267
|
+
/* @__PURE__ */ jsxs17(
|
|
2268
|
+
"button",
|
|
2269
|
+
{
|
|
2270
|
+
type: "button",
|
|
2271
|
+
"data-tree-action": "expand-all",
|
|
2272
|
+
onClick: () => expandAll(),
|
|
2273
|
+
className: cn(
|
|
2274
|
+
"inline-flex items-center gap-1 px-2 py-1 rounded-md",
|
|
2275
|
+
"hover:bg-surface-overlay focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus",
|
|
2276
|
+
"transition-colors"
|
|
2277
|
+
),
|
|
2278
|
+
children: [
|
|
2279
|
+
/* @__PURE__ */ jsx22(FontAwesomeIcon8, { icon: faAngleDoubleDown, className: "w-3 h-3", "aria-hidden": "true" }),
|
|
2280
|
+
/* @__PURE__ */ jsx22("span", { children: msgs.expandAll })
|
|
2281
|
+
]
|
|
2282
|
+
}
|
|
2283
|
+
),
|
|
2284
|
+
/* @__PURE__ */ jsxs17(
|
|
2285
|
+
"button",
|
|
2286
|
+
{
|
|
2287
|
+
type: "button",
|
|
2288
|
+
"data-tree-action": "collapse-all",
|
|
2289
|
+
onClick: () => collapseAll(),
|
|
2290
|
+
className: cn(
|
|
2291
|
+
"inline-flex items-center gap-1 px-2 py-1 rounded-md",
|
|
2292
|
+
"hover:bg-surface-overlay focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus",
|
|
2293
|
+
"transition-colors"
|
|
2294
|
+
),
|
|
2295
|
+
children: [
|
|
2296
|
+
/* @__PURE__ */ jsx22(FontAwesomeIcon8, { icon: faAngleDoubleUp, className: "w-3 h-3", "aria-hidden": "true" }),
|
|
2297
|
+
/* @__PURE__ */ jsx22("span", { children: msgs.collapseAll })
|
|
2298
|
+
]
|
|
2299
|
+
}
|
|
2300
|
+
)
|
|
2301
|
+
]
|
|
2302
|
+
}
|
|
2303
|
+
),
|
|
2304
|
+
/* @__PURE__ */ jsx22(
|
|
2305
|
+
"ul",
|
|
2306
|
+
{
|
|
2307
|
+
role: "tree",
|
|
2308
|
+
"aria-label": label != null ? label : msgs.tree,
|
|
2309
|
+
"aria-multiselectable": selectionMode === "multi" ? true : void 0,
|
|
2310
|
+
onKeyDown,
|
|
2311
|
+
className: cn("space-y-0.5"),
|
|
2312
|
+
children: visibleRows.map((row) => /* @__PURE__ */ jsx22(
|
|
2313
|
+
TreeNodeRow,
|
|
2314
|
+
{
|
|
2315
|
+
row,
|
|
2316
|
+
isSelected: selected.has(row.node.id),
|
|
2317
|
+
isFocused: focusId === row.node.id,
|
|
2318
|
+
onActivate: handleActivate,
|
|
2319
|
+
onToggle: toggleExpanded,
|
|
2320
|
+
onFocus: setFocusId
|
|
2321
|
+
},
|
|
2322
|
+
row.node.id
|
|
2323
|
+
))
|
|
2324
|
+
}
|
|
2325
|
+
)
|
|
2326
|
+
] });
|
|
2327
|
+
}
|
|
2328
|
+
|
|
2329
|
+
// modules/ui/ViewToggle.tsx
|
|
2330
|
+
import { FontAwesomeIcon as FontAwesomeIcon9 } from "@fortawesome/react-fontawesome";
|
|
2331
|
+
import { faTableList, faTableCells } from "@fortawesome/free-solid-svg-icons";
|
|
2332
|
+
import { jsx as jsx23, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
2333
|
+
function ViewToggle({ value, onChange, labels, ariaLabel, className }) {
|
|
2334
|
+
var _a, _b;
|
|
2335
|
+
const hLabel = (_a = labels == null ? void 0 : labels.horizontal) != null ? _a : "Horizontal";
|
|
2336
|
+
const vLabel = (_b = labels == null ? void 0 : labels.vertical) != null ? _b : "Vertical";
|
|
2337
|
+
return /* @__PURE__ */ jsx23(
|
|
2338
|
+
"div",
|
|
2339
|
+
{
|
|
2340
|
+
className: cn("flex items-center gap-0.5 rounded-lg p-0.5 border border-border bg-surface-raised", className),
|
|
2341
|
+
role: "group",
|
|
2342
|
+
"aria-label": ariaLabel != null ? ariaLabel : "View options",
|
|
2343
|
+
children: ["horizontal", "vertical"].map((opt) => /* @__PURE__ */ jsx23(
|
|
2344
|
+
"button",
|
|
2345
|
+
{
|
|
2346
|
+
onClick: () => onChange(opt),
|
|
2347
|
+
"aria-pressed": value === opt,
|
|
2348
|
+
className: cn(
|
|
2349
|
+
"px-3 py-1.5 rounded-md text-xs font-semibold transition-colors",
|
|
2350
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus",
|
|
2351
|
+
value === opt ? "bg-primary text-primary-fg shadow-sm" : "text-text-secondary hover:text-text-primary"
|
|
2352
|
+
),
|
|
2353
|
+
children: /* @__PURE__ */ jsxs18("span", { className: "flex items-center gap-1.5", children: [
|
|
2354
|
+
/* @__PURE__ */ jsx23(
|
|
2355
|
+
FontAwesomeIcon9,
|
|
2356
|
+
{
|
|
2357
|
+
icon: opt === "horizontal" ? faTableList : faTableCells,
|
|
2358
|
+
className: "w-3.5 h-3.5",
|
|
2359
|
+
"aria-hidden": "true"
|
|
2360
|
+
}
|
|
2361
|
+
),
|
|
2362
|
+
opt === "horizontal" ? hLabel : vLabel
|
|
2363
|
+
] })
|
|
2364
|
+
},
|
|
2365
|
+
opt
|
|
2366
|
+
))
|
|
2367
|
+
}
|
|
2368
|
+
);
|
|
2369
|
+
}
|
|
2370
|
+
|
|
2371
|
+
// modules/ui/lazy.tsx
|
|
2372
|
+
import dynamic from "next/dynamic";
|
|
2373
|
+
import { jsx as jsx24 } from "react/jsx-runtime";
|
|
2374
|
+
var LazyDataTable = dynamic(
|
|
2375
|
+
() => import("./DataTable-2G27T4E6.mjs").then((m) => m.DataTable),
|
|
2376
|
+
{ loading: () => /* @__PURE__ */ jsx24(SkeletonTableRow, { cols: 4 }), ssr: false }
|
|
2377
|
+
);
|
|
2378
|
+
var LazyAdvancedDataTable = dynamic(
|
|
2379
|
+
() => import("./AdvancedDataTable-F3DNXDKX.mjs").then((m) => m.AdvancedDataTable),
|
|
2380
|
+
{ loading: () => /* @__PURE__ */ jsx24(SkeletonTableRow, { cols: 4 }), ssr: false }
|
|
2381
|
+
);
|
|
2382
|
+
var LazyServerDataTable = dynamic(
|
|
2383
|
+
() => import("./ServerDataTable-RZV3K6KQ.mjs").then((m) => m.ServerDataTable),
|
|
2384
|
+
{ loading: () => /* @__PURE__ */ jsx24(SkeletonTableRow, { cols: 4 }), ssr: false }
|
|
2385
|
+
);
|
|
2386
|
+
var LazyDateRangePicker = dynamic(
|
|
2387
|
+
() => import("./DateRangePicker-AL32QB6L.mjs").then((m) => m.DateRangePicker),
|
|
2388
|
+
{ loading: () => /* @__PURE__ */ jsx24(SkeletonCard, {}), ssr: false }
|
|
2389
|
+
);
|
|
2390
|
+
var LazyMapView = dynamic(
|
|
2391
|
+
() => import("./MapView-FERKPCDB.mjs").then((m) => m.MapView),
|
|
2392
|
+
{ loading: () => /* @__PURE__ */ jsx24(SkeletonCard, {}), ssr: false }
|
|
2393
|
+
);
|
|
2394
|
+
var LazyVideoPlayer = dynamic(
|
|
2395
|
+
() => import("./VideoPlayer-P3I6ESXJ.mjs").then((m) => m.VideoPlayer),
|
|
2396
|
+
{ loading: () => /* @__PURE__ */ jsx24(SkeletonCard, {}), ssr: false }
|
|
2397
|
+
);
|
|
2398
|
+
|
|
2399
|
+
export {
|
|
2400
|
+
BrandLogo,
|
|
2401
|
+
Checkbox,
|
|
2402
|
+
FileInput,
|
|
2403
|
+
StarRating,
|
|
2404
|
+
StatCard,
|
|
2405
|
+
ButtonGroup,
|
|
2406
|
+
CheckboxGroup,
|
|
2407
|
+
ComboBox,
|
|
2408
|
+
ContentScoreBar,
|
|
2409
|
+
PageHeader,
|
|
2410
|
+
Popover,
|
|
2411
|
+
Slider,
|
|
2412
|
+
TabButton,
|
|
2413
|
+
TabGroup,
|
|
2414
|
+
TreeView,
|
|
2415
|
+
ViewToggle,
|
|
2416
|
+
LazyDataTable,
|
|
2417
|
+
LazyAdvancedDataTable,
|
|
2418
|
+
LazyServerDataTable,
|
|
2419
|
+
LazyDateRangePicker,
|
|
2420
|
+
LazyMapView,
|
|
2421
|
+
LazyVideoPlayer
|
|
2422
|
+
};
|