@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.
Files changed (47) hide show
  1. package/LICENSE +17 -0
  2. package/README.md +168 -0
  3. package/dist/AdvancedDataTable-F3DNXDKX.mjs +11 -0
  4. package/dist/DataTable-2G27T4E6.mjs +11 -0
  5. package/dist/DateRangePicker-AL32QB6L.mjs +11 -0
  6. package/dist/DropdownMenu-f5yV9dzM.d.mts +22 -0
  7. package/dist/DropdownMenu-f5yV9dzM.d.ts +22 -0
  8. package/dist/MapView-FERKPCDB.mjs +10 -0
  9. package/dist/ServerDataTable-RZV3K6KQ.mjs +11 -0
  10. package/dist/Tooltip-Bof5GvOc.d.mts +248 -0
  11. package/dist/Tooltip-Bof5GvOc.d.ts +248 -0
  12. package/dist/VideoPlayer-P3I6ESXJ.mjs +9 -0
  13. package/dist/app.d.mts +620 -0
  14. package/dist/app.d.ts +620 -0
  15. package/dist/app.js +7061 -0
  16. package/dist/app.mjs +100 -0
  17. package/dist/chunk-24BCQSLI.mjs +1 -0
  18. package/dist/chunk-45I3EDB2.mjs +90 -0
  19. package/dist/chunk-4IWCD7ID.mjs +1450 -0
  20. package/dist/chunk-5E2HXWFI.mjs +105 -0
  21. package/dist/chunk-C7AYI4XM.mjs +402 -0
  22. package/dist/chunk-J4D44TUA.mjs +1267 -0
  23. package/dist/chunk-KTEWZKNE.mjs +1020 -0
  24. package/dist/chunk-LMUQHL4Z.mjs +3829 -0
  25. package/dist/chunk-MD5OQ4J2.mjs +527 -0
  26. package/dist/chunk-MPJRPYIZ.mjs +1 -0
  27. package/dist/chunk-MPWUEQ7J.mjs +2422 -0
  28. package/dist/chunk-MTT5TKAJ.mjs +93 -0
  29. package/dist/chunk-RBDK7MWQ.mjs +46 -0
  30. package/dist/chunk-SVFQZPNZ.mjs +3648 -0
  31. package/dist/chunk-TZWBBMSG.mjs +1 -0
  32. package/dist/chunk-XA7J6PVJ.mjs +1488 -0
  33. package/dist/chunk-ZLYBRYWQ.mjs +726 -0
  34. package/dist/common.d.mts +921 -0
  35. package/dist/common.d.ts +921 -0
  36. package/dist/common.js +4991 -0
  37. package/dist/common.mjs +172 -0
  38. package/dist/index.d.mts +10 -0
  39. package/dist/index.d.ts +10 -0
  40. package/dist/index.js +17563 -0
  41. package/dist/index.mjs +349 -0
  42. package/dist/ui.d.mts +937 -0
  43. package/dist/ui.d.ts +937 -0
  44. package/dist/ui.js +10095 -0
  45. package/dist/ui.mjs +163 -0
  46. package/package.json +114 -0
  47. 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
+ };