@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,726 @@
1
+ "use client";
2
+ import {
3
+ __objRest,
4
+ __spreadProps,
5
+ __spreadValues,
6
+ cn
7
+ } from "./chunk-RBDK7MWQ.mjs";
8
+
9
+ // modules/ui/Avatar.tsx
10
+ import { jsx, jsxs } from "react/jsx-runtime";
11
+ var sizeMap = {
12
+ xs: "h-6 w-6 text-xs",
13
+ sm: "h-8 w-8 text-xs",
14
+ md: "h-10 w-10 text-sm",
15
+ lg: "h-12 w-12 text-base",
16
+ xl: "h-16 w-16 text-lg"
17
+ };
18
+ var statusColorMap = {
19
+ online: "bg-success",
20
+ offline: "bg-text-disabled",
21
+ away: "bg-warning",
22
+ busy: "bg-error"
23
+ };
24
+ var statusDotSizeMap = {
25
+ xs: "h-1.5 w-1.5",
26
+ sm: "h-2 w-2",
27
+ md: "h-2.5 w-2.5",
28
+ lg: "h-3 w-3",
29
+ xl: "h-4 w-4"
30
+ };
31
+ function getInitials(name) {
32
+ return name.trim().split(/\s+/).map((w) => w[0]).slice(0, 2).join("").toUpperCase() || "?";
33
+ }
34
+ function Avatar({
35
+ src,
36
+ name,
37
+ size = "md",
38
+ status,
39
+ className
40
+ }) {
41
+ const sizeClass = sizeMap[size];
42
+ const inner = src ? (
43
+ // eslint-disable-next-line @next/next/no-img-element
44
+ /* @__PURE__ */ jsx(
45
+ "img",
46
+ {
47
+ src,
48
+ alt: name,
49
+ className: cn(sizeClass, "rounded-full object-cover border border-border shrink-0", className)
50
+ }
51
+ )
52
+ ) : /* @__PURE__ */ jsx(
53
+ "span",
54
+ {
55
+ "aria-label": name,
56
+ className: cn(
57
+ sizeClass,
58
+ "rounded-full bg-primary-subtle text-primary font-semibold",
59
+ "flex items-center justify-center shrink-0 border border-primary-subtle select-none",
60
+ className
61
+ ),
62
+ children: getInitials(name)
63
+ }
64
+ );
65
+ if (!status) return inner;
66
+ return /* @__PURE__ */ jsxs("span", { className: "relative inline-flex shrink-0", children: [
67
+ inner,
68
+ /* @__PURE__ */ jsx(
69
+ "span",
70
+ {
71
+ "aria-label": status,
72
+ className: cn(
73
+ "absolute bottom-0 right-0 rounded-full border-2 border-surface-base",
74
+ statusColorMap[status],
75
+ statusDotSizeMap[size]
76
+ )
77
+ }
78
+ )
79
+ ] });
80
+ }
81
+ function AvatarGroup({
82
+ avatars,
83
+ max = 4,
84
+ size = "md"
85
+ }) {
86
+ const visible = avatars.slice(0, max);
87
+ const overflow = avatars.length - max;
88
+ return /* @__PURE__ */ jsxs("div", { className: "flex -space-x-2", "aria-label": `${avatars.length} users`, children: [
89
+ visible.map((a, i) => /* @__PURE__ */ jsx(Avatar, __spreadProps(__spreadValues({}, a), { size, className: "ring-2 ring-surface-base" }), i)),
90
+ overflow > 0 && /* @__PURE__ */ jsxs(
91
+ "span",
92
+ {
93
+ className: cn(
94
+ sizeMap[size],
95
+ "rounded-full bg-surface-sunken text-text-secondary font-semibold text-xs",
96
+ "flex items-center justify-center shrink-0 ring-2 ring-surface-base border border-border select-none"
97
+ ),
98
+ "aria-label": `${overflow} more`,
99
+ children: [
100
+ "+",
101
+ overflow
102
+ ]
103
+ }
104
+ )
105
+ ] });
106
+ }
107
+
108
+ // modules/ui/Badge.tsx
109
+ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
110
+ import { faXmark } from "@fortawesome/free-solid-svg-icons";
111
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
112
+ var variantMap = {
113
+ success: "bg-success-subtle text-success-fg",
114
+ error: "bg-error-subtle text-error-fg",
115
+ warning: "bg-warning-subtle text-warning-fg",
116
+ info: "bg-info-subtle text-info-fg",
117
+ neutral: "bg-surface-sunken text-text-secondary",
118
+ primary: "bg-primary-subtle text-primary"
119
+ };
120
+ var sizeMap2 = {
121
+ sm: "px-1.5 py-0 text-[10px]",
122
+ md: "px-2 py-0.5 text-xs",
123
+ lg: "px-3 py-1 text-sm"
124
+ };
125
+ var dotColorMap = {
126
+ success: "bg-success",
127
+ error: "bg-error",
128
+ warning: "bg-warning",
129
+ info: "bg-info",
130
+ neutral: "bg-text-disabled",
131
+ primary: "bg-primary"
132
+ };
133
+ function Badge(_a) {
134
+ var _b = _a, {
135
+ as,
136
+ children,
137
+ variant = "neutral",
138
+ size = "md",
139
+ dot = false,
140
+ dismissible = false,
141
+ onDismiss,
142
+ className
143
+ } = _b, rest = __objRest(_b, [
144
+ "as",
145
+ "children",
146
+ "variant",
147
+ "size",
148
+ "dot",
149
+ "dismissible",
150
+ "onDismiss",
151
+ "className"
152
+ ]);
153
+ const Tag = as != null ? as : "span";
154
+ return /* @__PURE__ */ jsxs2(
155
+ Tag,
156
+ __spreadProps(__spreadValues({
157
+ className: cn(
158
+ "inline-flex items-center gap-1 rounded-full font-medium",
159
+ variantMap[variant],
160
+ sizeMap2[size],
161
+ className
162
+ )
163
+ }, rest), {
164
+ children: [
165
+ dot && /* @__PURE__ */ jsx2(
166
+ "span",
167
+ {
168
+ className: cn("h-1.5 w-1.5 rounded-full shrink-0", dotColorMap[variant]),
169
+ "aria-hidden": "true"
170
+ }
171
+ ),
172
+ children,
173
+ dismissible && /* @__PURE__ */ jsx2(
174
+ "button",
175
+ {
176
+ type: "button",
177
+ "aria-label": "Remove",
178
+ onClick: onDismiss,
179
+ className: "ml-0.5 leading-none hover:opacity-70 transition-opacity focus-visible:outline-none rounded-full",
180
+ children: /* @__PURE__ */ jsx2(FontAwesomeIcon, { icon: faXmark, className: "w-2.5 h-2.5" })
181
+ }
182
+ )
183
+ ]
184
+ })
185
+ );
186
+ }
187
+
188
+ // modules/ui/Select.tsx
189
+ import { forwardRef, useEffect, useRef, useState } from "react";
190
+ import { FontAwesomeIcon as FontAwesomeIcon2 } from "@fortawesome/react-fontawesome";
191
+ import { faChevronUp, faChevronDown, faCheck } from "@fortawesome/free-solid-svg-icons";
192
+ import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
193
+ function CustomSelect({
194
+ id,
195
+ label,
196
+ options,
197
+ placeholder,
198
+ hint,
199
+ error,
200
+ disabled,
201
+ required,
202
+ searchable,
203
+ className,
204
+ value,
205
+ onChange
206
+ }) {
207
+ const [open, setOpen] = useState(false);
208
+ const [search, setSearch] = useState("");
209
+ const containerRef = useRef(null);
210
+ const searchRef = useRef(null);
211
+ const hintId = hint ? `${id}-hint` : void 0;
212
+ const errorId = error ? `${id}-error` : void 0;
213
+ const describedBy = [hintId, errorId].filter(Boolean).join(" ") || void 0;
214
+ const selected = options.find((o) => o.value === value);
215
+ const filtered = searchable && search ? options.filter((o) => o.label.toLowerCase().includes(search.toLowerCase())) : options;
216
+ useEffect(() => {
217
+ if (!open) {
218
+ setSearch("");
219
+ return;
220
+ }
221
+ if (searchable) setTimeout(() => {
222
+ var _a;
223
+ return (_a = searchRef.current) == null ? void 0 : _a.focus();
224
+ }, 30);
225
+ function onOutside(e) {
226
+ if (containerRef.current && !containerRef.current.contains(e.target)) setOpen(false);
227
+ }
228
+ document.addEventListener("mousedown", onOutside);
229
+ return () => document.removeEventListener("mousedown", onOutside);
230
+ }, [open, searchable]);
231
+ function select(v) {
232
+ onChange == null ? void 0 : onChange(v);
233
+ setOpen(false);
234
+ }
235
+ function onKeyDown(e) {
236
+ if (e.key === "Escape") setOpen(false);
237
+ if (e.key === "Enter" || e.key === " ") {
238
+ e.preventDefault();
239
+ setOpen((o) => !o);
240
+ }
241
+ }
242
+ return /* @__PURE__ */ jsxs3("div", { ref: containerRef, className: cn("space-y-1", className), children: [
243
+ /* @__PURE__ */ jsxs3("label", { id: `${id}-label`, className: "block text-sm font-medium text-text-primary", children: [
244
+ label,
245
+ required && /* @__PURE__ */ jsxs3(Fragment, { children: [
246
+ /* @__PURE__ */ jsx3("span", { className: "text-error ml-1", "aria-hidden": "true", children: "*" }),
247
+ /* @__PURE__ */ jsx3("span", { className: "sr-only", children: "(required)" })
248
+ ] })
249
+ ] }),
250
+ /* @__PURE__ */ jsxs3(
251
+ "div",
252
+ {
253
+ role: "combobox",
254
+ tabIndex: disabled ? -1 : 0,
255
+ "aria-haspopup": "listbox",
256
+ "aria-expanded": open,
257
+ "aria-labelledby": `${id}-label`,
258
+ "aria-describedby": describedBy,
259
+ "aria-disabled": disabled,
260
+ "aria-required": required,
261
+ id,
262
+ onClick: () => !disabled && setOpen((o) => !o),
263
+ onKeyDown,
264
+ className: cn(
265
+ "flex items-center gap-2 w-full rounded-md border px-3 py-2 text-sm transition-colors cursor-pointer",
266
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus",
267
+ error ? "border-error ring-1 ring-error bg-error-subtle" : "border-border bg-surface-base",
268
+ disabled && "opacity-50 cursor-not-allowed bg-surface-sunken"
269
+ ),
270
+ children: [
271
+ (selected == null ? void 0 : selected.icon) && /* @__PURE__ */ jsx3("span", { className: "shrink-0", children: selected.icon }),
272
+ /* @__PURE__ */ jsx3("span", { className: cn("flex-1", !selected && "text-text-disabled"), children: selected ? selected.label : placeholder != null ? placeholder : "Select\u2026" }),
273
+ /* @__PURE__ */ jsx3(FontAwesomeIcon2, { icon: open ? faChevronUp : faChevronDown, className: "w-3 h-3 text-text-disabled", "aria-hidden": "true" })
274
+ ]
275
+ }
276
+ ),
277
+ open && /* @__PURE__ */ jsxs3("div", { className: "z-20 w-full rounded-md border border-border bg-surface-raised shadow-lg overflow-hidden", children: [
278
+ searchable && /* @__PURE__ */ jsx3("div", { className: "p-2 border-b border-border", children: /* @__PURE__ */ jsx3(
279
+ "input",
280
+ {
281
+ ref: searchRef,
282
+ type: "text",
283
+ value: search,
284
+ onChange: (e) => setSearch(e.target.value),
285
+ placeholder: "Search\u2026",
286
+ className: cn(
287
+ "block w-full rounded-md border border-border bg-surface-base px-3 py-1.5 text-sm",
288
+ "text-text-primary placeholder:text-text-disabled",
289
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus"
290
+ )
291
+ }
292
+ ) }),
293
+ /* @__PURE__ */ jsxs3("ul", { role: "listbox", "aria-labelledby": `${id}-label`, className: "py-1 max-h-48 overflow-y-auto", children: [
294
+ placeholder && !search && /* @__PURE__ */ jsx3(
295
+ "li",
296
+ {
297
+ role: "option",
298
+ "aria-selected": !value,
299
+ onClick: () => select(""),
300
+ tabIndex: 0,
301
+ onKeyDown: (e) => {
302
+ if (e.key === "Enter" || e.key === " ") {
303
+ e.preventDefault();
304
+ select("");
305
+ }
306
+ },
307
+ className: cn(
308
+ "flex items-center gap-2 px-3 py-2 text-sm cursor-pointer text-text-disabled select-none",
309
+ "hover:bg-surface-overlay focus-visible:outline-none focus-visible:bg-surface-overlay"
310
+ ),
311
+ children: placeholder
312
+ }
313
+ ),
314
+ filtered.length === 0 ? /* @__PURE__ */ jsx3("li", { className: "px-3 py-4 text-sm text-center text-text-secondary", children: "No results found." }) : filtered.map((opt) => {
315
+ const active = opt.value === value;
316
+ return /* @__PURE__ */ jsxs3(
317
+ "li",
318
+ {
319
+ role: "option",
320
+ "aria-selected": active,
321
+ onClick: () => select(opt.value),
322
+ tabIndex: 0,
323
+ onKeyDown: (e) => {
324
+ if (e.key === "Enter" || e.key === " ") {
325
+ e.preventDefault();
326
+ select(opt.value);
327
+ }
328
+ },
329
+ className: cn(
330
+ "flex items-center gap-2 px-3 py-2 text-sm cursor-pointer select-none",
331
+ "hover:bg-surface-overlay transition-colors",
332
+ "focus-visible:outline-none focus-visible:bg-surface-overlay",
333
+ active && "text-primary font-medium"
334
+ ),
335
+ children: [
336
+ opt.icon && /* @__PURE__ */ jsx3("span", { className: "shrink-0", "aria-hidden": "true", children: opt.icon }),
337
+ opt.label,
338
+ active && /* @__PURE__ */ jsx3(FontAwesomeIcon2, { icon: faCheck, className: "ml-auto w-3 h-3 text-primary", "aria-hidden": "true" })
339
+ ]
340
+ },
341
+ opt.value
342
+ );
343
+ })
344
+ ] })
345
+ ] }),
346
+ hint && !error && /* @__PURE__ */ jsx3("p", { id: hintId, className: "text-xs text-text-secondary", children: hint }),
347
+ error && /* @__PURE__ */ jsx3("p", { id: errorId, className: "text-xs text-error", role: "alert", children: error })
348
+ ] });
349
+ }
350
+ var Select = forwardRef(function Select2(_a, ref) {
351
+ var _b = _a, { id, label, options, placeholder, hint, error, disabled, required, searchable, className } = _b, props = __objRest(_b, ["id", "label", "options", "placeholder", "hint", "error", "disabled", "required", "searchable", "className"]);
352
+ const hasIcons = options.some((o) => o.icon);
353
+ if (hasIcons || searchable) {
354
+ const _a2 = props, { value, onChange } = _a2, rest = __objRest(_a2, ["value", "onChange"]);
355
+ return /* @__PURE__ */ jsx3(
356
+ CustomSelect,
357
+ __spreadValues({
358
+ id,
359
+ label,
360
+ options,
361
+ placeholder,
362
+ hint,
363
+ error,
364
+ disabled,
365
+ required,
366
+ searchable,
367
+ className,
368
+ value,
369
+ onChange: (v) => onChange == null ? void 0 : onChange({ target: { value: v } })
370
+ }, rest)
371
+ );
372
+ }
373
+ const hintId = hint ? `${id}-hint` : void 0;
374
+ const errorId = error ? `${id}-error` : void 0;
375
+ const describedBy = [hintId, errorId].filter(Boolean).join(" ") || void 0;
376
+ return /* @__PURE__ */ jsxs3("div", { className: cn("space-y-1", className), children: [
377
+ /* @__PURE__ */ jsxs3("label", { htmlFor: id, className: "block text-sm font-medium text-text-primary", children: [
378
+ label,
379
+ required && /* @__PURE__ */ jsxs3(Fragment, { children: [
380
+ /* @__PURE__ */ jsx3("span", { className: "text-error ml-1", "aria-hidden": "true", children: "*" }),
381
+ /* @__PURE__ */ jsx3("span", { className: "sr-only", children: "(required)" })
382
+ ] })
383
+ ] }),
384
+ /* @__PURE__ */ jsxs3(
385
+ "select",
386
+ __spreadProps(__spreadValues({
387
+ ref,
388
+ id,
389
+ disabled,
390
+ required,
391
+ "aria-describedby": describedBy,
392
+ "aria-invalid": !!error,
393
+ "data-testid": `select-${id}`,
394
+ className: cn(
395
+ "block w-full rounded-md border px-3 py-2 text-sm transition-colors appearance-none",
396
+ "bg-surface-base text-text-primary",
397
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus focus-visible:border-border-focus",
398
+ "disabled:opacity-50 disabled:cursor-not-allowed disabled:bg-surface-sunken",
399
+ error ? "border-error ring-1 ring-error bg-error-subtle" : "border-border"
400
+ )
401
+ }, props), {
402
+ children: [
403
+ placeholder && /* @__PURE__ */ jsx3("option", { value: "", children: placeholder }),
404
+ options.map((opt) => /* @__PURE__ */ jsx3("option", { value: opt.value, children: opt.label }, opt.value))
405
+ ]
406
+ })
407
+ ),
408
+ hint && !error && /* @__PURE__ */ jsx3("p", { id: hintId, className: "text-xs text-text-secondary", children: hint }),
409
+ error && /* @__PURE__ */ jsx3("p", { id: errorId, className: "text-xs text-error", role: "alert", children: error })
410
+ ] });
411
+ });
412
+
413
+ // modules/ui/AlertBanner.tsx
414
+ import { useState as useState2 } from "react";
415
+ import { FontAwesomeIcon as FontAwesomeIcon3 } from "@fortawesome/react-fontawesome";
416
+ import { faCircleCheck, faTriangleExclamation, faCircleXmark, faCircleInfo, faXmark as faXmark2 } from "@fortawesome/free-solid-svg-icons";
417
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
418
+ var variantMap2 = {
419
+ success: { container: "bg-success-subtle border-success text-success-fg", defaultIcon: /* @__PURE__ */ jsx4(FontAwesomeIcon3, { icon: faCircleCheck, className: "w-4 h-4" }) },
420
+ warning: { container: "bg-warning-subtle border-warning text-warning-fg", defaultIcon: /* @__PURE__ */ jsx4(FontAwesomeIcon3, { icon: faTriangleExclamation, className: "w-4 h-4" }) },
421
+ error: { container: "bg-error-subtle border-error text-error-fg", defaultIcon: /* @__PURE__ */ jsx4(FontAwesomeIcon3, { icon: faCircleXmark, className: "w-4 h-4" }) },
422
+ info: { container: "bg-info-subtle border-info text-info-fg", defaultIcon: /* @__PURE__ */ jsx4(FontAwesomeIcon3, { icon: faCircleInfo, className: "w-4 h-4" }) }
423
+ };
424
+ function AlertBanner({
425
+ variant = "info",
426
+ title,
427
+ message,
428
+ dismissible = false,
429
+ action,
430
+ icon,
431
+ className
432
+ }) {
433
+ const [dismissed, setDismissed] = useState2(false);
434
+ if (dismissed) return null;
435
+ const { container, defaultIcon } = variantMap2[variant];
436
+ return /* @__PURE__ */ jsxs4(
437
+ "div",
438
+ {
439
+ role: "alert",
440
+ className: cn(
441
+ "flex items-start gap-3 rounded-lg border p-4",
442
+ container,
443
+ className
444
+ ),
445
+ children: [
446
+ /* @__PURE__ */ jsx4("span", { "aria-hidden": "true", className: "mt-0.5 shrink-0 font-bold", children: icon != null ? icon : defaultIcon }),
447
+ /* @__PURE__ */ jsxs4("div", { className: "flex-1 text-sm min-w-0", children: [
448
+ title && /* @__PURE__ */ jsx4("p", { className: "font-semibold", children: title }),
449
+ /* @__PURE__ */ jsx4("p", { className: cn(title && "mt-0.5"), children: message }),
450
+ action && /* @__PURE__ */ jsx4("div", { className: "mt-2", children: action.href ? /* @__PURE__ */ jsx4(
451
+ "a",
452
+ {
453
+ href: action.href,
454
+ className: "text-xs font-semibold underline underline-offset-2 hover:opacity-70 transition-opacity focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus rounded",
455
+ children: action.label
456
+ }
457
+ ) : /* @__PURE__ */ jsx4(
458
+ "button",
459
+ {
460
+ type: "button",
461
+ onClick: action.onClick,
462
+ className: "text-xs font-semibold underline underline-offset-2 hover:opacity-70 transition-opacity focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus rounded",
463
+ children: action.label
464
+ }
465
+ ) })
466
+ ] }),
467
+ dismissible && /* @__PURE__ */ jsx4(
468
+ "button",
469
+ {
470
+ type: "button",
471
+ "aria-label": "Dismiss",
472
+ onClick: () => setDismissed(true),
473
+ className: "shrink-0 hover:opacity-70 transition-opacity focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus rounded",
474
+ children: /* @__PURE__ */ jsx4(FontAwesomeIcon3, { icon: faXmark2, className: "w-4 h-4" })
475
+ }
476
+ )
477
+ ]
478
+ }
479
+ );
480
+ }
481
+
482
+ // modules/ui/DropdownMenu.tsx
483
+ import { useEffect as useEffect2, useRef as useRef2, useState as useState3 } from "react";
484
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
485
+ function DropdownMenu({
486
+ trigger,
487
+ items,
488
+ header,
489
+ align = "left",
490
+ className
491
+ }) {
492
+ const [open, setOpen] = useState3(false);
493
+ const containerRef = useRef2(null);
494
+ useEffect2(() => {
495
+ if (!open) return;
496
+ function onOutside(e) {
497
+ var _a;
498
+ if (!((_a = containerRef.current) == null ? void 0 : _a.contains(e.target))) setOpen(false);
499
+ }
500
+ function onKey(e) {
501
+ if (e.key === "Escape") setOpen(false);
502
+ }
503
+ document.addEventListener("mousedown", onOutside);
504
+ document.addEventListener("keydown", onKey);
505
+ return () => {
506
+ document.removeEventListener("mousedown", onOutside);
507
+ document.removeEventListener("keydown", onKey);
508
+ };
509
+ }, [open]);
510
+ return /* @__PURE__ */ jsxs5("div", { ref: containerRef, className: cn("relative inline-block", className), children: [
511
+ /* @__PURE__ */ jsx5(
512
+ "div",
513
+ {
514
+ onClick: () => setOpen((p) => !p),
515
+ "aria-haspopup": "menu",
516
+ "aria-expanded": open,
517
+ children: trigger
518
+ }
519
+ ),
520
+ open && /* @__PURE__ */ jsxs5(
521
+ "div",
522
+ {
523
+ role: "menu",
524
+ className: cn(
525
+ "absolute z-[60] mt-1 min-w-[10rem] rounded-lg border border-border bg-surface-raised shadow-lg py-1",
526
+ align === "right" ? "right-0" : "left-0"
527
+ ),
528
+ children: [
529
+ header && /* @__PURE__ */ jsx5("div", { className: "border-b border-border mb-1", children: header }),
530
+ items.map((item, i) => {
531
+ if (item.type === "separator") {
532
+ return /* @__PURE__ */ jsx5("div", { role: "separator", className: "my-1 border-t border-border" }, i);
533
+ }
534
+ return /* @__PURE__ */ jsxs5(
535
+ "button",
536
+ {
537
+ role: "menuitem",
538
+ type: "button",
539
+ disabled: item.disabled,
540
+ onClick: () => {
541
+ var _a;
542
+ (_a = item.onClick) == null ? void 0 : _a.call(item);
543
+ setOpen(false);
544
+ },
545
+ className: cn(
546
+ "flex w-full items-center gap-2 px-3 py-2 text-sm text-left transition-colors",
547
+ "focus-visible:outline-none focus-visible:bg-surface-overlay",
548
+ item.danger ? "text-error hover:bg-error-subtle" : "text-text-primary hover:bg-surface-overlay",
549
+ item.disabled && "opacity-50 cursor-not-allowed"
550
+ ),
551
+ children: [
552
+ item.icon && /* @__PURE__ */ jsx5("span", { "aria-hidden": "true", children: item.icon }),
553
+ item.label
554
+ ]
555
+ },
556
+ i
557
+ );
558
+ })
559
+ ]
560
+ }
561
+ )
562
+ ] });
563
+ }
564
+
565
+ // modules/ui/TagInput.tsx
566
+ import { useRef as useRef3, useState as useState4 } from "react";
567
+ import { FontAwesomeIcon as FontAwesomeIcon4 } from "@fortawesome/react-fontawesome";
568
+ import { faXmark as faXmark3 } from "@fortawesome/free-solid-svg-icons";
569
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
570
+ function TagInput({
571
+ id,
572
+ label,
573
+ hint,
574
+ error,
575
+ value,
576
+ onChange,
577
+ placeholder = "Type and press Enter or comma\u2026",
578
+ disabled,
579
+ className
580
+ }) {
581
+ const [input, setInput] = useState4("");
582
+ const [editingIdx, setEditingIdx] = useState4(null);
583
+ const [editValue, setEditValue] = useState4("");
584
+ const inputRef = useRef3(null);
585
+ const hintId = hint ? `${id}-hint` : void 0;
586
+ const errorId = error ? `${id}-error` : void 0;
587
+ const describedBy = [hintId, errorId].filter(Boolean).join(" ") || void 0;
588
+ function addTags(raw) {
589
+ const tags = raw.split(",").map((t) => t.trim()).filter(Boolean);
590
+ onChange([.../* @__PURE__ */ new Set([...value, ...tags])]);
591
+ setInput("");
592
+ }
593
+ function removeTag(idx) {
594
+ onChange(value.filter((_, i) => i !== idx));
595
+ }
596
+ function handleInputChange(v) {
597
+ if (v.includes(",")) {
598
+ const parts = v.split(",");
599
+ parts.slice(0, -1).forEach((p) => p.trim() && addTags(p));
600
+ setInput(parts[parts.length - 1]);
601
+ } else {
602
+ setInput(v);
603
+ }
604
+ }
605
+ function handleKeyDown(e) {
606
+ if ((e.key === "Enter" || e.key === ",") && input.trim()) {
607
+ e.preventDefault();
608
+ addTags(input);
609
+ } else if (e.key === "Backspace" && !input && value.length) {
610
+ removeTag(value.length - 1);
611
+ }
612
+ }
613
+ function finishEdit() {
614
+ if (editingIdx === null) return;
615
+ const trimmed = editValue.trim();
616
+ if (trimmed) {
617
+ const next = [...value];
618
+ next[editingIdx] = trimmed;
619
+ onChange([...new Set(next)]);
620
+ }
621
+ setEditingIdx(null);
622
+ setEditValue("");
623
+ }
624
+ return /* @__PURE__ */ jsxs6("div", { className: cn("space-y-1", className), children: [
625
+ /* @__PURE__ */ jsx6("label", { htmlFor: id, className: "block text-sm font-medium text-text-primary", children: label }),
626
+ /* @__PURE__ */ jsxs6(
627
+ "div",
628
+ {
629
+ onClick: () => {
630
+ var _a;
631
+ return (_a = inputRef.current) == null ? void 0 : _a.focus();
632
+ },
633
+ className: cn(
634
+ "flex flex-wrap gap-1.5 min-h-10 w-full rounded-md border px-3 py-2 transition-colors cursor-text",
635
+ "focus-within:ring-2 focus-within:ring-border-focus focus-within:border-border-focus",
636
+ disabled ? "opacity-50 cursor-not-allowed bg-surface-sunken border-border" : "bg-surface-base border-border",
637
+ error && "border-error ring-1 ring-error bg-error-subtle"
638
+ ),
639
+ children: [
640
+ value.map(
641
+ (tag, i) => editingIdx === i ? /* @__PURE__ */ jsx6(
642
+ "input",
643
+ {
644
+ type: "text",
645
+ value: editValue,
646
+ onChange: (e) => setEditValue(e.target.value),
647
+ onBlur: finishEdit,
648
+ onKeyDown: (e) => {
649
+ if (e.key === "Enter") {
650
+ e.preventDefault();
651
+ finishEdit();
652
+ }
653
+ if (e.key === "Escape") {
654
+ setEditingIdx(null);
655
+ setEditValue("");
656
+ }
657
+ },
658
+ autoFocus: true,
659
+ className: "inline-block w-24 rounded border border-border-focus bg-surface-base px-1.5 py-0.5 text-xs text-text-primary outline-none"
660
+ },
661
+ i
662
+ ) : /* @__PURE__ */ jsxs6(
663
+ "span",
664
+ {
665
+ className: "inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs font-medium bg-primary-subtle text-primary",
666
+ onDoubleClick: () => {
667
+ if (!disabled) {
668
+ setEditingIdx(i);
669
+ setEditValue(tag);
670
+ }
671
+ },
672
+ title: "Double-click to edit",
673
+ children: [
674
+ tag,
675
+ !disabled && /* @__PURE__ */ jsx6(
676
+ "button",
677
+ {
678
+ type: "button",
679
+ onClick: (e) => {
680
+ e.stopPropagation();
681
+ removeTag(i);
682
+ },
683
+ "aria-label": `Remove ${tag}`,
684
+ className: "hover:opacity-70 focus-visible:outline-none rounded-full",
685
+ children: /* @__PURE__ */ jsx6(FontAwesomeIcon4, { icon: faXmark3, className: "w-2.5 h-2.5" })
686
+ }
687
+ )
688
+ ]
689
+ },
690
+ i
691
+ )
692
+ ),
693
+ !disabled && /* @__PURE__ */ jsx6(
694
+ "input",
695
+ {
696
+ ref: inputRef,
697
+ id,
698
+ value: input,
699
+ onChange: (e) => handleInputChange(e.target.value),
700
+ onKeyDown: handleKeyDown,
701
+ onBlur: () => {
702
+ if (input.trim()) addTags(input);
703
+ },
704
+ placeholder: value.length === 0 ? placeholder : void 0,
705
+ "aria-describedby": describedBy,
706
+ className: "flex-1 min-w-24 bg-transparent text-sm text-text-primary placeholder:text-text-disabled outline-none"
707
+ }
708
+ )
709
+ ]
710
+ }
711
+ ),
712
+ hint && !error && /* @__PURE__ */ jsx6("p", { id: hintId, className: "text-xs text-text-secondary", children: hint }),
713
+ !hint && !error && value.length > 0 && /* @__PURE__ */ jsx6("p", { className: "text-xs text-text-disabled", children: "Double-click a tag to edit it" }),
714
+ error && /* @__PURE__ */ jsx6("p", { id: errorId, className: "text-xs text-error", role: "alert", children: error })
715
+ ] });
716
+ }
717
+
718
+ export {
719
+ Avatar,
720
+ AvatarGroup,
721
+ Badge,
722
+ Select,
723
+ AlertBanner,
724
+ DropdownMenu,
725
+ TagInput
726
+ };