@geomak/ui 5.0.3 → 5.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -3,6 +3,7 @@ export { colors_default as COLORS, PALETTE as palette, semanticTokens, vars } fr
3
3
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
4
  import React8, { createContext, useState, useEffect, useMemo, useCallback, useContext, useRef, useId, useLayoutEffect } from 'react';
5
5
  import { createPortal } from 'react-dom';
6
+ import * as AvatarPrimitive from '@radix-ui/react-avatar';
6
7
  import * as Dialog from '@radix-ui/react-dialog';
7
8
  import { useReducedMotion, AnimatePresence, motion } from 'framer-motion';
8
9
  import * as TooltipPrimitive from '@radix-ui/react-tooltip';
@@ -185,6 +186,417 @@ function Portal({ children, target }) {
185
186
  }, [target]);
186
187
  return resolved ? createPortal(children, resolved) : null;
187
188
  }
189
+ var SPACING_MAP = {
190
+ "none": "0",
191
+ "xs": "1",
192
+ "sm": "2",
193
+ "md": "4",
194
+ "lg": "6",
195
+ "xl": "8",
196
+ "2xl": "12"
197
+ };
198
+ var padding = (s, axis = "p") => s == null ? "" : `${axis}-${SPACING_MAP[s]}`;
199
+ var margin = (s, axis = "m") => s == null ? "" : `${axis}-${SPACING_MAP[s]}`;
200
+ var BG_MAP = {
201
+ "none": "",
202
+ "background": "bg-background",
203
+ "surface": "bg-surface",
204
+ "surface-raised": "bg-surface-raised",
205
+ "accent": "bg-accent text-accent-fg"
206
+ };
207
+ var BORDER_MAP = {
208
+ "none": "",
209
+ "border": "border border-border",
210
+ "border-strong": "border border-border-strong",
211
+ "accent": "border border-accent",
212
+ "status-error": "border border-status-error"
213
+ };
214
+ var RADIUS_MAP = {
215
+ "none": "rounded-none",
216
+ "sm": "rounded-sm",
217
+ "md": "rounded-md",
218
+ "lg": "rounded-lg",
219
+ "xl": "rounded-xl",
220
+ "2xl": "rounded-2xl",
221
+ "full": "rounded-full"
222
+ };
223
+ var SHADOW_MAP = {
224
+ "none": "",
225
+ "sm": "shadow-sm",
226
+ "md": "shadow-md",
227
+ "lg": "shadow-lg",
228
+ "xl": "shadow-xl"
229
+ };
230
+ function Box({
231
+ as,
232
+ p,
233
+ px: px2,
234
+ py,
235
+ pt,
236
+ pr,
237
+ pb,
238
+ pl,
239
+ m,
240
+ mx,
241
+ my,
242
+ mt,
243
+ mr,
244
+ mb,
245
+ ml,
246
+ background = "none",
247
+ border = "none",
248
+ radius,
249
+ shadow = "none",
250
+ width,
251
+ height,
252
+ onClick,
253
+ className = "",
254
+ style,
255
+ children
256
+ }) {
257
+ const Element = as ?? "div";
258
+ return /* @__PURE__ */ jsx(
259
+ Element,
260
+ {
261
+ onClick,
262
+ className: [
263
+ padding(p, "p"),
264
+ padding(px2, "px"),
265
+ padding(py, "py"),
266
+ padding(pt, "pt"),
267
+ padding(pr, "pr"),
268
+ padding(pb, "pb"),
269
+ padding(pl, "pl"),
270
+ margin(m, "m"),
271
+ margin(mx, "mx"),
272
+ margin(my, "my"),
273
+ margin(mt, "mt"),
274
+ margin(mr, "mr"),
275
+ margin(mb, "mb"),
276
+ margin(ml, "ml"),
277
+ BG_MAP[background],
278
+ BORDER_MAP[border],
279
+ radius ? RADIUS_MAP[radius] : "",
280
+ SHADOW_MAP[shadow],
281
+ className
282
+ ].filter(Boolean).join(" "),
283
+ style: {
284
+ width: typeof width === "number" ? `${width}px` : width,
285
+ height: typeof height === "number" ? `${height}px` : height,
286
+ ...style
287
+ },
288
+ children
289
+ }
290
+ );
291
+ }
292
+ var GAP_MAP = {
293
+ "none": "gap-0",
294
+ "xs": "gap-1",
295
+ "sm": "gap-2",
296
+ "md": "gap-4",
297
+ "lg": "gap-6",
298
+ "xl": "gap-8",
299
+ "2xl": "gap-12"
300
+ };
301
+ var DIRECTION_CLASS = {
302
+ "row": "flex-row",
303
+ "row-reverse": "flex-row-reverse",
304
+ "col": "flex-col",
305
+ "col-reverse": "flex-col-reverse"
306
+ };
307
+ var ALIGN_CLASS = {
308
+ start: "items-start",
309
+ center: "items-center",
310
+ end: "items-end",
311
+ stretch: "items-stretch",
312
+ baseline: "items-baseline"
313
+ };
314
+ var JUSTIFY_CLASS = {
315
+ start: "justify-start",
316
+ center: "justify-center",
317
+ end: "justify-end",
318
+ between: "justify-between",
319
+ around: "justify-around",
320
+ evenly: "justify-evenly"
321
+ };
322
+ var WRAP_CLASS = {
323
+ "nowrap": "flex-nowrap",
324
+ "wrap": "flex-wrap",
325
+ "wrap-reverse": "flex-wrap-reverse"
326
+ };
327
+ function Flex({
328
+ direction = "row",
329
+ align,
330
+ justify,
331
+ wrap,
332
+ gap,
333
+ inline,
334
+ className = "",
335
+ ...boxProps
336
+ }) {
337
+ return /* @__PURE__ */ jsx(
338
+ Box,
339
+ {
340
+ ...boxProps,
341
+ className: [
342
+ inline ? "inline-flex" : "flex",
343
+ DIRECTION_CLASS[direction],
344
+ align ? ALIGN_CLASS[align] : "",
345
+ justify ? JUSTIFY_CLASS[justify] : "",
346
+ wrap ? WRAP_CLASS[wrap] : "",
347
+ gap ? GAP_MAP[gap] : "",
348
+ className
349
+ ].filter(Boolean).join(" ")
350
+ }
351
+ );
352
+ }
353
+ var GAP_MAP2 = {
354
+ "none": "gap-0",
355
+ "xs": "gap-1",
356
+ "sm": "gap-2",
357
+ "md": "gap-4",
358
+ "lg": "gap-6",
359
+ "xl": "gap-8",
360
+ "2xl": "gap-12"
361
+ };
362
+ var COL_MAP = {
363
+ 1: "grid-cols-1",
364
+ 2: "grid-cols-2",
365
+ 3: "grid-cols-3",
366
+ 4: "grid-cols-4",
367
+ 5: "grid-cols-5",
368
+ 6: "grid-cols-6",
369
+ 7: "grid-cols-7",
370
+ 8: "grid-cols-8",
371
+ 9: "grid-cols-9",
372
+ 10: "grid-cols-10",
373
+ 11: "grid-cols-11",
374
+ 12: "grid-cols-12"
375
+ };
376
+ var ROW_MAP = {
377
+ 1: "grid-rows-1",
378
+ 2: "grid-rows-2",
379
+ 3: "grid-rows-3",
380
+ 4: "grid-rows-4",
381
+ 5: "grid-rows-5",
382
+ 6: "grid-rows-6"
383
+ };
384
+ var ALIGN_CLASS2 = {
385
+ start: "items-start",
386
+ center: "items-center",
387
+ end: "items-end",
388
+ stretch: "items-stretch"
389
+ };
390
+ var JUSTIFY_CLASS2 = {
391
+ start: "justify-items-start",
392
+ center: "justify-items-center",
393
+ end: "justify-items-end",
394
+ stretch: "justify-items-stretch"
395
+ };
396
+ function Grid2({
397
+ cols,
398
+ rows,
399
+ gap,
400
+ gapX,
401
+ gapY,
402
+ align,
403
+ justify,
404
+ className = "",
405
+ style,
406
+ ...boxProps
407
+ }) {
408
+ const colClass = typeof cols === "number" ? COL_MAP[cols] ?? "" : "";
409
+ const rowClass = typeof rows === "number" ? ROW_MAP[rows] ?? "" : "";
410
+ const inlineCols = typeof cols === "string" ? cols : void 0;
411
+ const inlineRows = typeof rows === "string" ? rows : void 0;
412
+ return /* @__PURE__ */ jsx(
413
+ Box,
414
+ {
415
+ ...boxProps,
416
+ className: [
417
+ "grid",
418
+ colClass,
419
+ rowClass,
420
+ gap ? GAP_MAP2[gap] : "",
421
+ gapX ? GAP_MAP2[gapX].replace("gap-", "gap-x-") : "",
422
+ gapY ? GAP_MAP2[gapY].replace("gap-", "gap-y-") : "",
423
+ align ? ALIGN_CLASS2[align] : "",
424
+ justify ? JUSTIFY_CLASS2[justify] : "",
425
+ className
426
+ ].filter(Boolean).join(" "),
427
+ style: {
428
+ gridTemplateColumns: inlineCols,
429
+ gridTemplateRows: inlineRows,
430
+ ...style
431
+ }
432
+ }
433
+ );
434
+ }
435
+ var SIZE_PX = {
436
+ xs: 20,
437
+ sm: 28,
438
+ md: 36,
439
+ lg: 48,
440
+ xl: 64
441
+ };
442
+ var TEXT_CLASS = {
443
+ xs: "text-[10px]",
444
+ sm: "text-xs",
445
+ md: "text-sm",
446
+ lg: "text-base",
447
+ xl: "text-lg"
448
+ };
449
+ var STATUS_CLASS = {
450
+ online: "bg-status-success",
451
+ offline: "bg-foreground-muted",
452
+ away: "bg-status-warning",
453
+ busy: "bg-status-error"
454
+ };
455
+ function Avatar({
456
+ src,
457
+ alt,
458
+ fallback,
459
+ size = "md",
460
+ shape = "circle",
461
+ status,
462
+ className = ""
463
+ }) {
464
+ const px2 = SIZE_PX[size];
465
+ const initialsFallback = (() => {
466
+ if (fallback) return fallback;
467
+ if (alt) {
468
+ const parts = alt.trim().split(/\s+/).slice(0, 2);
469
+ const initials = parts.map((p) => p[0]?.toUpperCase() ?? "").join("");
470
+ if (initials) return initials;
471
+ }
472
+ return /* @__PURE__ */ jsx(PersonSilhouette, {});
473
+ })();
474
+ return /* @__PURE__ */ jsxs(
475
+ "span",
476
+ {
477
+ className: `relative inline-block flex-shrink-0 ${className}`,
478
+ style: { width: px2, height: px2 },
479
+ children: [
480
+ /* @__PURE__ */ jsxs(
481
+ AvatarPrimitive.Root,
482
+ {
483
+ className: `flex w-full h-full items-center justify-center overflow-hidden bg-surface-raised text-foreground-secondary select-none ${shape === "circle" ? "rounded-full" : "rounded-md"}`,
484
+ children: [
485
+ src && /* @__PURE__ */ jsx(
486
+ AvatarPrimitive.Image,
487
+ {
488
+ src,
489
+ alt: alt ?? "",
490
+ className: "h-full w-full object-cover"
491
+ }
492
+ ),
493
+ /* @__PURE__ */ jsx(
494
+ AvatarPrimitive.Fallback,
495
+ {
496
+ delayMs: src ? 300 : 0,
497
+ className: `flex h-full w-full items-center justify-center font-semibold ${TEXT_CLASS[size]}`,
498
+ children: initialsFallback
499
+ }
500
+ )
501
+ ]
502
+ }
503
+ ),
504
+ status && /* @__PURE__ */ jsx(
505
+ "span",
506
+ {
507
+ className: `absolute bottom-0 right-0 block rounded-full ring-2 ring-background ${STATUS_CLASS[status]}`,
508
+ style: {
509
+ width: Math.max(6, Math.round(px2 / 4)),
510
+ height: Math.max(6, Math.round(px2 / 4))
511
+ },
512
+ "aria-label": `Status: ${status}`,
513
+ role: "status"
514
+ }
515
+ )
516
+ ]
517
+ }
518
+ );
519
+ }
520
+ function PersonSilhouette() {
521
+ return /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "currentColor", className: "w-[60%] h-[60%]", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M12 12a4 4 0 100-8 4 4 0 000 8zM4 20a8 8 0 1116 0H4z" }) });
522
+ }
523
+ var VARIANT_CLASS = {
524
+ display: "text-3xl font-bold leading-tight tracking-tight",
525
+ h1: "text-2xl font-bold leading-tight tracking-tight",
526
+ h2: "text-xl font-semibold leading-snug tracking-tight",
527
+ h3: "text-lg font-semibold leading-snug",
528
+ h4: "text-base font-semibold leading-snug",
529
+ subtitle: "text-sm font-medium leading-snug",
530
+ body: "text-sm leading-normal",
531
+ caption: "text-xs leading-normal",
532
+ overline: "text-[10px] font-semibold leading-normal uppercase tracking-wider",
533
+ code: "text-xs leading-normal font-mono bg-surface-raised text-foreground rounded px-1 py-0.5"
534
+ };
535
+ var DEFAULT_ELEMENT = {
536
+ display: "h1",
537
+ h1: "h1",
538
+ h2: "h2",
539
+ h3: "h3",
540
+ h4: "h4",
541
+ subtitle: "p",
542
+ body: "p",
543
+ caption: "span",
544
+ overline: "span",
545
+ code: "code"
546
+ };
547
+ var COLOR_CLASS = {
548
+ "foreground": "text-foreground",
549
+ "foreground-secondary": "text-foreground-secondary",
550
+ "foreground-muted": "text-foreground-muted",
551
+ "accent": "text-accent",
552
+ "status-error": "text-status-error",
553
+ "status-warning": "text-status-warning",
554
+ "status-success": "text-status-success",
555
+ "status-info": "text-status-info",
556
+ "inherit": ""
557
+ };
558
+ var WEIGHT_CLASS = {
559
+ normal: "font-normal",
560
+ medium: "font-medium",
561
+ semibold: "font-semibold",
562
+ bold: "font-bold"
563
+ };
564
+ var ALIGN_CLASS3 = {
565
+ left: "text-left",
566
+ center: "text-center",
567
+ right: "text-right",
568
+ justify: "text-justify"
569
+ };
570
+ function Typography({
571
+ variant = "body",
572
+ as,
573
+ color = "inherit",
574
+ weight,
575
+ align,
576
+ truncate,
577
+ muted,
578
+ className = "",
579
+ style,
580
+ children
581
+ }) {
582
+ const Element = as ?? DEFAULT_ELEMENT[variant];
583
+ return /* @__PURE__ */ jsx(
584
+ Element,
585
+ {
586
+ className: [
587
+ VARIANT_CLASS[variant],
588
+ COLOR_CLASS[color],
589
+ weight ? WEIGHT_CLASS[weight] : "",
590
+ align ? ALIGN_CLASS3[align] : "",
591
+ truncate ? "truncate" : "",
592
+ muted ? "opacity-60" : "",
593
+ className
594
+ ].filter(Boolean).join(" "),
595
+ style,
596
+ children
597
+ }
598
+ );
599
+ }
188
600
  function IconButton({
189
601
  icon,
190
602
  onClick,
@@ -1105,30 +1517,53 @@ function FadingBase({
1105
1517
  )
1106
1518
  );
1107
1519
  }
1108
- function List2({ items, onItemClick, activeKey }) {
1109
- return /* @__PURE__ */ jsx("div", { role: "listbox", children: items.map((item) => (
1110
- // tabIndex + Enter/Space onKeyDown makes each option
1111
- // keyboard-activatable. Previously the items were only mouse-
1112
- // clickable — keyboard-only users couldn't select anything.
1113
- /* @__PURE__ */ jsx(
1520
+ var DENSITY_PADDING = {
1521
+ "compact": "py-1.5 px-2",
1522
+ "comfortable": "py-2.5 px-3",
1523
+ "spacious": "py-3.5 px-4"
1524
+ };
1525
+ function List2({
1526
+ items,
1527
+ onItemClick,
1528
+ activeKey,
1529
+ density = "comfortable"
1530
+ }) {
1531
+ return /* @__PURE__ */ jsx("div", { role: "listbox", className: "flex flex-col", children: items.map((item) => {
1532
+ const isActive = activeKey === item.key;
1533
+ const isDisabled = !!item.disabled;
1534
+ return /* @__PURE__ */ jsxs(
1114
1535
  "div",
1115
1536
  {
1116
1537
  role: "option",
1117
- "aria-selected": activeKey === item.key,
1118
- tabIndex: 0,
1119
- className: `hover:bg-surface-raised cursor-pointer p-3 border-b border-border transition-colors duration-150 focus:outline-none focus-visible:ring-2 focus-visible:ring-accent ${activeKey === item.key ? "bg-surface-raised text-foreground" : "text-foreground-secondary"}`,
1120
- onClick: () => onItemClick(item),
1538
+ "aria-selected": isActive,
1539
+ "aria-disabled": isDisabled || void 0,
1540
+ tabIndex: isDisabled ? -1 : 0,
1541
+ onClick: () => !isDisabled && onItemClick(item),
1121
1542
  onKeyDown: (e) => {
1543
+ if (isDisabled) return;
1122
1544
  if (e.key === "Enter" || e.key === " ") {
1123
1545
  e.preventDefault();
1124
1546
  onItemClick(item);
1125
1547
  }
1126
1548
  },
1127
- children: item.label
1549
+ className: [
1550
+ "flex items-center gap-3 cursor-pointer border-b border-border transition-colors duration-150",
1551
+ DENSITY_PADDING[density],
1552
+ "focus:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-inset",
1553
+ isDisabled ? "opacity-50 cursor-not-allowed" : isActive ? "bg-surface-raised text-foreground" : "text-foreground-secondary hover:bg-surface-raised hover:text-foreground"
1554
+ ].join(" "),
1555
+ children: [
1556
+ item.avatar && /* @__PURE__ */ jsx("span", { className: "flex-shrink-0", children: item.avatar }),
1557
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
1558
+ /* @__PURE__ */ jsx("div", { className: "text-sm font-medium text-foreground truncate", children: item.label }),
1559
+ item.description && /* @__PURE__ */ jsx("div", { className: "text-xs text-foreground-secondary mt-0.5 truncate", children: item.description })
1560
+ ] }),
1561
+ item.trailing && /* @__PURE__ */ jsx("span", { className: "flex-shrink-0 text-foreground-muted", children: item.trailing })
1562
+ ]
1128
1563
  },
1129
1564
  item.key
1130
- )
1131
- )) });
1565
+ );
1566
+ }) });
1132
1567
  }
1133
1568
  var TOGGLE_POSITION_CLASS = {
1134
1569
  "top-left": "top-2 left-2",
@@ -1567,21 +2002,50 @@ function Wizard({
1567
2002
  const handlePrev = () => {
1568
2003
  if (activeIndex > 0) setActiveIndex((i) => i - 1);
1569
2004
  };
2005
+ const SPOT_PAD = 6;
1570
2006
  const highlightStyle = bbox ? {
1571
- left: bbox.left - 4,
1572
- top: bbox.top - 4,
1573
- width: bbox.width + 8,
1574
- height: bbox.height + 8
2007
+ left: bbox.left - SPOT_PAD,
2008
+ top: bbox.top - SPOT_PAD,
2009
+ width: bbox.width + SPOT_PAD * 2,
2010
+ height: bbox.height + SPOT_PAD * 2
2011
+ } : { display: "none" };
2012
+ const backdropTop = bbox ? { left: 0, top: 0, right: 0, height: Math.max(0, bbox.top - SPOT_PAD) } : { display: "none" };
2013
+ const backdropBottom = bbox ? { left: 0, top: bbox.bottom + SPOT_PAD, right: 0, bottom: 0 } : { display: "none" };
2014
+ const backdropLeft = bbox ? {
2015
+ left: 0,
2016
+ top: bbox.top - SPOT_PAD,
2017
+ width: Math.max(0, bbox.left - SPOT_PAD),
2018
+ height: bbox.height + SPOT_PAD * 2
2019
+ } : { display: "none" };
2020
+ const backdropRight = bbox ? {
2021
+ left: bbox.right + SPOT_PAD,
2022
+ top: bbox.top - SPOT_PAD,
2023
+ right: 0,
2024
+ height: bbox.height + SPOT_PAD * 2
1575
2025
  } : { display: "none" };
1576
2026
  const tooltipStyle = bbox ? tooltipStyleFor(bbox, step?.placement) : { display: "none" };
1577
2027
  const isLast = activeIndex === steps.length - 1;
1578
2028
  return /* @__PURE__ */ jsxs(Fragment, { children: [
1579
2029
  children,
1580
2030
  /* @__PURE__ */ jsx(AnimatePresence, { children: open && step && /* @__PURE__ */ jsxs(Portal, { children: [
2031
+ ["top", "bottom", "left", "right"].map((side) => /* @__PURE__ */ jsx(
2032
+ motion.div,
2033
+ {
2034
+ className: "fixed z-[7000000] bg-foreground/40 backdrop-blur-[2px] pointer-events-auto",
2035
+ style: side === "top" ? backdropTop : side === "bottom" ? backdropBottom : side === "left" ? backdropLeft : backdropRight,
2036
+ initial: { opacity: 0 },
2037
+ animate: { opacity: 1 },
2038
+ exit: { opacity: 0 },
2039
+ transition: { duration: reduced ? 0 : 0.18, ease: "easeOut" },
2040
+ "aria-hidden": "true"
2041
+ },
2042
+ side
2043
+ )),
1581
2044
  /* @__PURE__ */ jsx(
1582
2045
  motion.div,
1583
2046
  {
1584
- className: "fixed inset-0 z-[7000000] bg-foreground/40 backdrop-blur-[1px] pointer-events-auto",
2047
+ className: "fixed z-[7000001] pointer-events-auto",
2048
+ style: highlightStyle,
1585
2049
  initial: { opacity: 0 },
1586
2050
  animate: { opacity: 1 },
1587
2051
  exit: { opacity: 0 },
@@ -1592,7 +2056,7 @@ function Wizard({
1592
2056
  /* @__PURE__ */ jsx(
1593
2057
  motion.div,
1594
2058
  {
1595
- className: "fixed z-[7000001] pointer-events-none rounded-md ring-2 ring-accent ring-offset-2 ring-offset-background",
2059
+ className: "fixed z-[7000002] pointer-events-none rounded-md ring-2 ring-accent",
1596
2060
  style: highlightStyle,
1597
2061
  initial: { opacity: 0, scale: 1.08 },
1598
2062
  animate: { opacity: 1, scale: 1 },
@@ -1613,7 +2077,7 @@ function Wizard({
1613
2077
  "aria-modal": "true",
1614
2078
  "aria-labelledby": step.title ? tooltipTitleId : void 0,
1615
2079
  "aria-describedby": tooltipBodyId,
1616
- className: "fixed z-[7000002] rounded-lg bg-surface text-foreground border border-border shadow-xl p-4 pointer-events-auto",
2080
+ className: "fixed z-[7000003] rounded-lg bg-surface text-foreground border border-border shadow-xl p-4 pointer-events-auto",
1617
2081
  style: tooltipStyle,
1618
2082
  initial: { opacity: 0, scale: 0.96, y: 6 },
1619
2083
  animate: { opacity: 1, scale: 1, y: 0 },
@@ -1900,9 +2364,105 @@ function Dropdown({
1900
2364
  ]
1901
2365
  }
1902
2366
  ),
1903
- hasError && /* @__PURE__ */ jsx("div", { id: errorId, className: "text-center text-status-error text-xs mt-1", children: errorMessage })
2367
+ hasError && /* @__PURE__ */ jsx("div", { id: errorId, className: "text-status-error text-xs mt-1", children: errorMessage })
1904
2368
  ] });
1905
2369
  }
2370
+ var SHIMMER = [
2371
+ "relative overflow-hidden rounded-sm bg-surface-raised",
2372
+ 'before:absolute before:inset-0 before:content-[""]',
2373
+ "before:bg-gradient-to-r before:from-transparent before:via-white/30 before:to-transparent",
2374
+ "before:animate-[shimmer_1.6s_linear_infinite]",
2375
+ // Respect prefers-reduced-motion — the resting bg-surface-raised is still
2376
+ // a perfectly legible placeholder for users who have animations off.
2377
+ "motion-reduce:before:hidden"
2378
+ ].join(" ");
2379
+ function SkeletonBox({ width, height = 16, radius, className = "", style }) {
2380
+ return /* @__PURE__ */ jsx(
2381
+ "span",
2382
+ {
2383
+ role: "presentation",
2384
+ "aria-hidden": "true",
2385
+ className: `block ${SHIMMER} ${className}`,
2386
+ style: {
2387
+ width: width ?? "100%",
2388
+ height,
2389
+ borderRadius: radius ?? "var(--radius-md)",
2390
+ ...style
2391
+ }
2392
+ }
2393
+ );
2394
+ }
2395
+ function SkeletonText({
2396
+ lines = 3,
2397
+ lastLineWidth = 60,
2398
+ lineHeight = 14,
2399
+ gap = 8,
2400
+ className = "",
2401
+ style
2402
+ }) {
2403
+ return /* @__PURE__ */ jsx(
2404
+ "div",
2405
+ {
2406
+ role: "presentation",
2407
+ "aria-hidden": "true",
2408
+ className: `flex flex-col ${className}`,
2409
+ style: { gap, ...style },
2410
+ children: Array.from({ length: lines }).map((_, i) => {
2411
+ const isLast = i === lines - 1;
2412
+ const width = isLast && lines > 1 ? `${lastLineWidth}%` : "100%";
2413
+ return /* @__PURE__ */ jsx(
2414
+ "span",
2415
+ {
2416
+ className: `block ${SHIMMER}`,
2417
+ style: { height: lineHeight, width, borderRadius: "var(--radius-sm)" }
2418
+ },
2419
+ i
2420
+ );
2421
+ })
2422
+ }
2423
+ );
2424
+ }
2425
+ function SkeletonCircle({ size = 40, className = "", style }) {
2426
+ return /* @__PURE__ */ jsx(
2427
+ "span",
2428
+ {
2429
+ role: "presentation",
2430
+ "aria-hidden": "true",
2431
+ className: `block flex-shrink-0 ${SHIMMER} ${className}`,
2432
+ style: {
2433
+ width: size,
2434
+ height: size,
2435
+ borderRadius: "50%",
2436
+ ...style
2437
+ }
2438
+ }
2439
+ );
2440
+ }
2441
+ function SkeletonCard({ hasAvatar = true, lines = 3, className = "", style }) {
2442
+ return /* @__PURE__ */ jsxs(
2443
+ "div",
2444
+ {
2445
+ role: "presentation",
2446
+ "aria-hidden": "true",
2447
+ className: `rounded-lg border border-border bg-surface p-4 ${className}`,
2448
+ style,
2449
+ children: [
2450
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 mb-4", children: [
2451
+ hasAvatar && /* @__PURE__ */ jsx(SkeletonCircle, { size: 36 }),
2452
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 flex flex-col gap-2", children: [
2453
+ /* @__PURE__ */ jsx(SkeletonBox, { height: 12, width: "55%" }),
2454
+ /* @__PURE__ */ jsx(SkeletonBox, { height: 10, width: "35%" })
2455
+ ] })
2456
+ ] }),
2457
+ /* @__PURE__ */ jsx(SkeletonText, { lines, lastLineWidth: 55 }),
2458
+ /* @__PURE__ */ jsxs("div", { className: "mt-4 flex gap-2", children: [
2459
+ /* @__PURE__ */ jsx(SkeletonBox, { height: 28, width: 72 }),
2460
+ /* @__PURE__ */ jsx(SkeletonBox, { height: 28, width: 56 })
2461
+ ] })
2462
+ ]
2463
+ }
2464
+ );
2465
+ }
1906
2466
  var DEFAULT_PICKER = [
1907
2467
  { key: 1, value: 5, label: 5 },
1908
2468
  { key: 2, value: 10, label: 10 },
@@ -2088,7 +2648,9 @@ function Table({
2088
2648
  expandRow = DEFAULT_EXPAND,
2089
2649
  hasSearch = true,
2090
2650
  footer = null,
2091
- header = null
2651
+ header = null,
2652
+ loading = false,
2653
+ loadingRowCount = 8
2092
2654
  }) {
2093
2655
  const searchRef = useRef(null);
2094
2656
  const [searchTerm, setSearchTerm] = useState("");
@@ -2172,9 +2734,16 @@ function Table({
2172
2734
  )
2173
2735
  ] }),
2174
2736
  /* @__PURE__ */ jsx("div", { children: header }),
2175
- /* @__PURE__ */ jsx("div", { className: "overflow-x-auto rounded-lg", children: /* @__PURE__ */ jsxs("table", { className: "w-full border-collapse", children: [
2737
+ /* @__PURE__ */ jsx("div", { className: "overflow-x-auto rounded-lg", children: /* @__PURE__ */ jsxs("table", { className: "w-full border-collapse", "aria-busy": loading || void 0, children: [
2176
2738
  /* @__PURE__ */ jsx(TableHeader, { columns, hasExpand: !!expandRow.enabled }),
2177
- /* @__PURE__ */ jsx(
2739
+ loading ? /* @__PURE__ */ jsx(
2740
+ TableSkeletonBody,
2741
+ {
2742
+ columns,
2743
+ rowCount: loadingRowCount,
2744
+ hasExpand: !!expandRow.enabled
2745
+ }
2746
+ ) : /* @__PURE__ */ jsx(
2178
2747
  TableBody,
2179
2748
  {
2180
2749
  columns,
@@ -2187,6 +2756,23 @@ function Table({
2187
2756
  /* @__PURE__ */ jsx("div", { children: footer })
2188
2757
  ] });
2189
2758
  }
2759
+ function TableSkeletonBody({
2760
+ columns,
2761
+ rowCount,
2762
+ hasExpand
2763
+ }) {
2764
+ return /* @__PURE__ */ jsx("tbody", { "aria-hidden": "true", children: Array.from({ length: rowCount }).map((_, i) => /* @__PURE__ */ jsxs(
2765
+ "tr",
2766
+ {
2767
+ className: `border-b border-border ${i % 2 === 0 ? "bg-surface" : "bg-surface-raised"}`,
2768
+ children: [
2769
+ hasExpand && /* @__PURE__ */ jsx("td", { className: "p-0 align-middle w-9" }),
2770
+ columns.map((col) => /* @__PURE__ */ jsx("td", { className: "py-3 px-3 align-middle", children: /* @__PURE__ */ jsx(SkeletonBox, { height: 12, width: `${50 + i % 4 * 12}%` }) }, col.key))
2771
+ ]
2772
+ },
2773
+ i
2774
+ )) });
2775
+ }
2190
2776
  function ThemeSwitch({ checked, onChange, label = "Toggle dark mode" }) {
2191
2777
  const id = useId();
2192
2778
  return /* @__PURE__ */ jsx("label", { htmlFor: id, className: "flex items-center gap-2 cursor-pointer select-none", children: /* @__PURE__ */ jsx(
@@ -2619,102 +3205,6 @@ function ThemeProvider({
2619
3205
  )
2620
3206
  ] });
2621
3207
  }
2622
- var SHIMMER = [
2623
- "relative overflow-hidden rounded-sm bg-surface-raised",
2624
- 'before:absolute before:inset-0 before:content-[""]',
2625
- "before:bg-gradient-to-r before:from-transparent before:via-white/30 before:to-transparent",
2626
- "before:animate-[shimmer_1.6s_linear_infinite]",
2627
- // Respect prefers-reduced-motion — the resting bg-surface-raised is still
2628
- // a perfectly legible placeholder for users who have animations off.
2629
- "motion-reduce:before:hidden"
2630
- ].join(" ");
2631
- function SkeletonBox({ width, height = 16, radius, className = "", style }) {
2632
- return /* @__PURE__ */ jsx(
2633
- "span",
2634
- {
2635
- role: "presentation",
2636
- "aria-hidden": "true",
2637
- className: `block ${SHIMMER} ${className}`,
2638
- style: {
2639
- width: width ?? "100%",
2640
- height,
2641
- borderRadius: radius ?? "var(--radius-md)",
2642
- ...style
2643
- }
2644
- }
2645
- );
2646
- }
2647
- function SkeletonText({
2648
- lines = 3,
2649
- lastLineWidth = 60,
2650
- lineHeight = 14,
2651
- gap = 8,
2652
- className = "",
2653
- style
2654
- }) {
2655
- return /* @__PURE__ */ jsx(
2656
- "div",
2657
- {
2658
- role: "presentation",
2659
- "aria-hidden": "true",
2660
- className: `flex flex-col ${className}`,
2661
- style: { gap, ...style },
2662
- children: Array.from({ length: lines }).map((_, i) => {
2663
- const isLast = i === lines - 1;
2664
- const width = isLast && lines > 1 ? `${lastLineWidth}%` : "100%";
2665
- return /* @__PURE__ */ jsx(
2666
- "span",
2667
- {
2668
- className: `block ${SHIMMER}`,
2669
- style: { height: lineHeight, width, borderRadius: "var(--radius-sm)" }
2670
- },
2671
- i
2672
- );
2673
- })
2674
- }
2675
- );
2676
- }
2677
- function SkeletonCircle({ size = 40, className = "", style }) {
2678
- return /* @__PURE__ */ jsx(
2679
- "span",
2680
- {
2681
- role: "presentation",
2682
- "aria-hidden": "true",
2683
- className: `block flex-shrink-0 ${SHIMMER} ${className}`,
2684
- style: {
2685
- width: size,
2686
- height: size,
2687
- borderRadius: "50%",
2688
- ...style
2689
- }
2690
- }
2691
- );
2692
- }
2693
- function SkeletonCard({ hasAvatar = true, lines = 3, className = "", style }) {
2694
- return /* @__PURE__ */ jsxs(
2695
- "div",
2696
- {
2697
- role: "presentation",
2698
- "aria-hidden": "true",
2699
- className: `rounded-lg border border-border bg-surface p-4 ${className}`,
2700
- style,
2701
- children: [
2702
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 mb-4", children: [
2703
- hasAvatar && /* @__PURE__ */ jsx(SkeletonCircle, { size: 36 }),
2704
- /* @__PURE__ */ jsxs("div", { className: "flex-1 flex flex-col gap-2", children: [
2705
- /* @__PURE__ */ jsx(SkeletonBox, { height: 12, width: "55%" }),
2706
- /* @__PURE__ */ jsx(SkeletonBox, { height: 10, width: "35%" })
2707
- ] })
2708
- ] }),
2709
- /* @__PURE__ */ jsx(SkeletonText, { lines, lastLineWidth: 55 }),
2710
- /* @__PURE__ */ jsxs("div", { className: "mt-4 flex gap-2", children: [
2711
- /* @__PURE__ */ jsx(SkeletonBox, { height: 28, width: 72 }),
2712
- /* @__PURE__ */ jsx(SkeletonBox, { height: 28, width: 56 })
2713
- ] })
2714
- ]
2715
- }
2716
- );
2717
- }
2718
3208
  function TextInput({
2719
3209
  value,
2720
3210
  onChange,
@@ -2732,48 +3222,50 @@ function TextInput({
2732
3222
  }) {
2733
3223
  const errorId = useId();
2734
3224
  const hasError = errorMessage != null;
2735
- return /* @__PURE__ */ jsxs("div", { className: "relative flex flex-col items-center justify-center", children: [
3225
+ return (
3226
+ // In horizontal mode the row layout is [label, input-with-error-column].
3227
+ // The error sits under the input ONLY, not spanning the label too.
3228
+ // In vertical mode the whole thing is a column.
2736
3229
  /* @__PURE__ */ jsxs(
2737
3230
  "div",
2738
3231
  {
2739
- className: `flex ${layout === "vertical" ? "flex-col" : "flex-row items-center gap-2"}`,
3232
+ className: `flex ${layout === "vertical" ? "flex-col gap-1" : "flex-row items-start gap-2"}`,
2740
3233
  style: style ?? {},
2741
3234
  children: [
2742
- label && // Render <label> only when a label is provided. An empty
2743
- // <label htmlFor=…> announces as an unlabeled control in
2744
- // some screen readers.
2745
- /* @__PURE__ */ jsx(
3235
+ label && /* @__PURE__ */ jsx(
2746
3236
  "label",
2747
3237
  {
2748
3238
  style: { color: labelColor || void 0 },
2749
- className: `text-sm font-medium ml-1 max-content ${!labelColor && "text-foreground"}`,
3239
+ className: `text-sm font-medium ${layout === "horizontal" ? "mt-2" : ""} max-content ${!labelColor && "text-foreground"}`,
2750
3240
  htmlFor,
2751
3241
  children: label
2752
3242
  }
2753
3243
  ),
2754
- /* @__PURE__ */ jsx(
2755
- "input",
2756
- {
2757
- autoComplete: "off",
2758
- disabled,
2759
- value,
2760
- onChange,
2761
- onBlur,
2762
- type: "text",
2763
- name,
2764
- id: htmlFor,
2765
- "aria-invalid": hasError || void 0,
2766
- "aria-describedby": hasError ? errorId : void 0,
2767
- className: `${hasError ? "border border-status-error" : "border border-border"} bg-surface text-foreground p-2 h-9 w-60 mt-1 rounded-lg disabled:bg-surface-raised disabled:text-foreground-muted disabled:cursor-not-allowed focus:outline-none focus:border-transparent focus:ring-2 focus:ring-accent transition-colors`,
2768
- style: inputStyle ?? {},
2769
- placeholder: placeholder ?? ""
2770
- }
2771
- )
3244
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col", children: [
3245
+ /* @__PURE__ */ jsx(
3246
+ "input",
3247
+ {
3248
+ autoComplete: "off",
3249
+ disabled,
3250
+ value,
3251
+ onChange,
3252
+ onBlur,
3253
+ type: "text",
3254
+ name,
3255
+ id: htmlFor,
3256
+ "aria-invalid": hasError || void 0,
3257
+ "aria-describedby": hasError ? errorId : void 0,
3258
+ className: `${hasError ? "border border-status-error" : "border border-border"} bg-surface text-foreground p-2 h-9 w-60 rounded-lg disabled:bg-surface-raised disabled:text-foreground-muted disabled:cursor-not-allowed focus:outline-none focus:border-transparent focus:ring-2 focus:ring-accent transition-colors`,
3259
+ style: inputStyle ?? {},
3260
+ placeholder: placeholder ?? ""
3261
+ }
3262
+ ),
3263
+ hasError && /* @__PURE__ */ jsx("div", { id: errorId, className: "text-status-error text-xs mt-1", children: errorMessage })
3264
+ ] })
2772
3265
  ]
2773
3266
  }
2774
- ),
2775
- hasError && /* @__PURE__ */ jsx("div", { id: errorId, className: "text-center text-status-error text-xs mt-1", children: errorMessage })
2776
- ] });
3267
+ )
3268
+ );
2777
3269
  }
2778
3270
  function NumberInput({
2779
3271
  step = 1,
@@ -2915,64 +3407,71 @@ function Password({
2915
3407
  const [passwordVisible, setPasswordVisible] = useState(false);
2916
3408
  const errorId = useId();
2917
3409
  const hasError = errorMessage != null;
2918
- return /* @__PURE__ */ jsxs("div", { className: "relative flex flex-col items-center justify-center", style: style ?? {}, children: [
2919
- /* @__PURE__ */ jsxs("div", { className: `flex ${layout === "vertical" ? "flex-col" : "flex-row items-center gap-2"}`, children: [
2920
- label && /* @__PURE__ */ jsx(
2921
- "label",
2922
- {
2923
- style: { color: labelColor || void 0 },
2924
- className: `text-sm font-medium ml-1 max-content ${!labelColor && "text-foreground"}`,
2925
- htmlFor,
2926
- children: label
2927
- }
2928
- ),
2929
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
2930
- /* @__PURE__ */ jsx(
2931
- "input",
3410
+ return /* @__PURE__ */ jsxs(
3411
+ "div",
3412
+ {
3413
+ className: `flex ${layout === "vertical" ? "flex-col gap-1" : "flex-row items-start gap-2"}`,
3414
+ style: style ?? {},
3415
+ children: [
3416
+ label && /* @__PURE__ */ jsx(
3417
+ "label",
2932
3418
  {
2933
- autoComplete: "off",
2934
- disabled,
2935
- value,
2936
- onChange,
2937
- onBlur,
2938
- type: passwordVisible ? "text" : "password",
2939
- name,
2940
- id: htmlFor,
2941
- "aria-invalid": hasError || void 0,
2942
- "aria-describedby": hasError ? errorId : void 0,
2943
- className: `${hasError ? "border border-status-error" : "border border-border"} bg-surface text-foreground p-2 h-9 w-52 mt-1 rounded-lg disabled:bg-surface-raised disabled:text-foreground-muted disabled:cursor-not-allowed focus:outline-none focus:border-transparent focus:ring-2 focus:ring-accent transition-colors`,
2944
- style: inputStyle ?? {},
2945
- placeholder: placeholder ?? ""
3419
+ style: { color: labelColor || void 0 },
3420
+ className: `text-sm font-medium ${layout === "horizontal" ? "mt-2" : ""} max-content ${!labelColor && "text-foreground"}`,
3421
+ htmlFor,
3422
+ children: label
2946
3423
  }
2947
3424
  ),
2948
- /* @__PURE__ */ jsx(
2949
- "button",
2950
- {
2951
- type: "button",
2952
- className: "cursor-pointer p-1 text-foreground-secondary hover:text-foreground rounded-md focus:outline-none focus-visible:ring-2 focus-visible:ring-accent",
2953
- style: iconColor ? { color: iconColor } : void 0,
2954
- onClick: () => setPasswordVisible(!passwordVisible),
2955
- "aria-label": passwordVisible ? "Hide password" : "Show password",
2956
- children: passwordVisible ? (
2957
- /* EyeSlash */
2958
- /* @__PURE__ */ jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "currentColor", className: "w-6 h-6", children: [
2959
- /* @__PURE__ */ jsx("path", { d: "M3.53 2.47a.75.75 0 00-1.06 1.06l18 18a.75.75 0 101.06-1.06l-18-18zM22.676 12.553a11.249 11.249 0 01-2.631 4.31l-3.099-3.099a5.25 5.25 0 00-6.71-6.71L7.759 4.577a11.217 11.217 0 014.242-.827c4.97 0 9.185 3.223 10.675 7.69.12.362.12.752 0 1.113z" }),
2960
- /* @__PURE__ */ jsx("path", { d: "M15.75 12c0 .18-.013.357-.037.53l-4.244-4.243A3.75 3.75 0 0115.75 12zM12.53 15.713l-4.243-4.244a3.75 3.75 0 004.243 4.243z" }),
2961
- /* @__PURE__ */ jsx("path", { d: "M6.75 12c0-.619.107-1.213.304-1.764l-3.1-3.1a11.25 11.25 0 00-2.63 4.31c-.12.362-.12.752 0 1.114 1.489 4.467 5.704 7.69 10.675 7.69 1.5 0 2.933-.294 4.242-.827l-2.477-2.477A5.25 5.25 0 016.75 12z" })
2962
- ] })
2963
- ) : (
2964
- /* Eye */
2965
- /* @__PURE__ */ jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "currentColor", className: "w-6 h-6", children: [
2966
- /* @__PURE__ */ jsx("path", { d: "M12 15a3 3 0 100-6 3 3 0 000 6z" }),
2967
- /* @__PURE__ */ jsx("path", { fillRule: "evenodd", d: "M1.323 11.447C2.811 6.976 7.028 3.75 12.001 3.75c4.97 0 9.185 3.223 10.675 7.69.12.362.12.752 0 1.113-1.487 4.471-5.705 7.697-10.677 7.697-4.97 0-9.186-3.223-10.675-7.69a1.762 1.762 0 010-1.113zM17.25 12a5.25 5.25 0 11-10.5 0 5.25 5.25 0 0110.5 0z", clipRule: "evenodd" })
2968
- ] })
3425
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col", children: [
3426
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
3427
+ /* @__PURE__ */ jsx(
3428
+ "input",
3429
+ {
3430
+ autoComplete: "off",
3431
+ disabled,
3432
+ value,
3433
+ onChange,
3434
+ onBlur,
3435
+ type: passwordVisible ? "text" : "password",
3436
+ name,
3437
+ id: htmlFor,
3438
+ "aria-invalid": hasError || void 0,
3439
+ "aria-describedby": hasError ? errorId : void 0,
3440
+ className: `${hasError ? "border border-status-error" : "border border-border"} bg-surface text-foreground p-2 h-9 w-52 rounded-lg disabled:bg-surface-raised disabled:text-foreground-muted disabled:cursor-not-allowed focus:outline-none focus:border-transparent focus:ring-2 focus:ring-accent transition-colors`,
3441
+ style: inputStyle ?? {},
3442
+ placeholder: placeholder ?? ""
3443
+ }
3444
+ ),
3445
+ /* @__PURE__ */ jsx(
3446
+ "button",
3447
+ {
3448
+ type: "button",
3449
+ className: "cursor-pointer p-1 text-foreground-secondary hover:text-foreground rounded-md focus:outline-none focus-visible:ring-2 focus-visible:ring-accent",
3450
+ style: iconColor ? { color: iconColor } : void 0,
3451
+ onClick: () => setPasswordVisible(!passwordVisible),
3452
+ "aria-label": passwordVisible ? "Hide password" : "Show password",
3453
+ children: passwordVisible ? (
3454
+ /* EyeSlash */
3455
+ /* @__PURE__ */ jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "currentColor", className: "w-6 h-6", children: [
3456
+ /* @__PURE__ */ jsx("path", { d: "M3.53 2.47a.75.75 0 00-1.06 1.06l18 18a.75.75 0 101.06-1.06l-18-18zM22.676 12.553a11.249 11.249 0 01-2.631 4.31l-3.099-3.099a5.25 5.25 0 00-6.71-6.71L7.759 4.577a11.217 11.217 0 014.242-.827c4.97 0 9.185 3.223 10.675 7.69.12.362.12.752 0 1.113z" }),
3457
+ /* @__PURE__ */ jsx("path", { d: "M15.75 12c0 .18-.013.357-.037.53l-4.244-4.243A3.75 3.75 0 0115.75 12zM12.53 15.713l-4.243-4.244a3.75 3.75 0 004.243 4.243z" }),
3458
+ /* @__PURE__ */ jsx("path", { d: "M6.75 12c0-.619.107-1.213.304-1.764l-3.1-3.1a11.25 11.25 0 00-2.63 4.31c-.12.362-.12.752 0 1.114 1.489 4.467 5.704 7.69 10.675 7.69 1.5 0 2.933-.294 4.242-.827l-2.477-2.477A5.25 5.25 0 016.75 12z" })
3459
+ ] })
3460
+ ) : (
3461
+ /* Eye */
3462
+ /* @__PURE__ */ jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "currentColor", className: "w-6 h-6", children: [
3463
+ /* @__PURE__ */ jsx("path", { d: "M12 15a3 3 0 100-6 3 3 0 000 6z" }),
3464
+ /* @__PURE__ */ jsx("path", { fillRule: "evenodd", d: "M1.323 11.447C2.811 6.976 7.028 3.75 12.001 3.75c4.97 0 9.185 3.223 10.675 7.69.12.362.12.752 0 1.113-1.487 4.471-5.705 7.697-10.677 7.697-4.97 0-9.186-3.223-10.675-7.69a1.762 1.762 0 010-1.113zM17.25 12a5.25 5.25 0 11-10.5 0 5.25 5.25 0 0110.5 0z", clipRule: "evenodd" })
3465
+ ] })
3466
+ )
3467
+ }
2969
3468
  )
2970
- }
2971
- )
2972
- ] })
2973
- ] }),
2974
- hasError && /* @__PURE__ */ jsx("div", { id: errorId, className: "text-center text-status-error text-xs mt-1", children: errorMessage })
2975
- ] });
3469
+ ] }),
3470
+ hasError && /* @__PURE__ */ jsx("div", { id: errorId, className: "text-status-error text-xs mt-1", children: errorMessage })
3471
+ ] })
3472
+ ]
3473
+ }
3474
+ );
2976
3475
  }
2977
3476
  function Checkbox({
2978
3477
  checked,
@@ -3085,15 +3584,54 @@ function AutoComplete({
3085
3584
  inputStyle,
3086
3585
  style,
3087
3586
  layout = "vertical",
3088
- items = [],
3587
+ items,
3588
+ onSearch,
3589
+ debounce = 250,
3089
3590
  onItemClick,
3090
- emptyText = "No results found"
3591
+ emptyText = "No results found",
3592
+ loadingText = "Searching\u2026"
3091
3593
  }) {
3092
3594
  const [term, setTerm] = useState("");
3093
3595
  const [open, setOpen] = useState(false);
3094
- const foundItems = term.trim() ? items.filter(
3596
+ const [asyncItems, setAsyncItems] = useState([]);
3597
+ const [loading, setLoading] = useState(false);
3598
+ const isAsync = typeof onSearch === "function";
3599
+ const debounceRef = useRef(null);
3600
+ const requestIdRef = useRef(0);
3601
+ const staticFiltered = isAsync || !items ? [] : term.trim() ? items.filter(
3095
3602
  ({ key, label: label2 }) => label2.toLowerCase().includes(term.toLowerCase()) || key.toLowerCase().includes(term.toLowerCase())
3096
3603
  ) : [];
3604
+ useEffect(() => {
3605
+ if (!isAsync) return;
3606
+ if (debounceRef.current) clearTimeout(debounceRef.current);
3607
+ if (!term.trim()) {
3608
+ setAsyncItems([]);
3609
+ setLoading(false);
3610
+ return;
3611
+ }
3612
+ const myId = ++requestIdRef.current;
3613
+ setLoading(true);
3614
+ debounceRef.current = setTimeout(async () => {
3615
+ try {
3616
+ const res = await onSearch(term);
3617
+ if (myId === requestIdRef.current) {
3618
+ setAsyncItems(res);
3619
+ }
3620
+ } catch {
3621
+ if (myId === requestIdRef.current) {
3622
+ setAsyncItems([]);
3623
+ }
3624
+ } finally {
3625
+ if (myId === requestIdRef.current) {
3626
+ setLoading(false);
3627
+ }
3628
+ }
3629
+ }, debounce);
3630
+ return () => {
3631
+ if (debounceRef.current) clearTimeout(debounceRef.current);
3632
+ };
3633
+ }, [term, isAsync, debounce, onSearch]);
3634
+ const foundItems = isAsync ? asyncItems : staticFiltered;
3097
3635
  const handleSelect = (item) => {
3098
3636
  setTerm(`${item.label} (${item.value})`);
3099
3637
  onItemClick?.(item.value);
@@ -3126,10 +3664,11 @@ function AutoComplete({
3126
3664
  autoComplete: "off",
3127
3665
  "aria-haspopup": "listbox",
3128
3666
  "aria-expanded": open,
3129
- "aria-autocomplete": "list"
3667
+ "aria-autocomplete": "list",
3668
+ "aria-busy": loading || void 0
3130
3669
  }
3131
3670
  ),
3132
- /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "currentColor", className: "w-5 h-5 flex-shrink-0 text-foreground-muted", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { fillRule: "evenodd", d: "M10.5 3.75a6.75 6.75 0 100 13.5 6.75 6.75 0 000-13.5zM2.25 10.5a8.25 8.25 0 1114.59 5.28l4.69 4.69a.75.75 0 11-1.06 1.06l-4.69-4.69A8.25 8.25 0 012.25 10.5z", clipRule: "evenodd" }) })
3671
+ loading ? /* @__PURE__ */ jsx("span", { className: "w-5 h-5 flex-shrink-0 flex items-center justify-center text-accent", "aria-hidden": "true", children: /* @__PURE__ */ jsx(LoadingSpinner, { inline: true, size: "xs", spinnerColor: "currentColor" }) }) : /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "currentColor", className: "w-5 h-5 flex-shrink-0 text-foreground-muted", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { fillRule: "evenodd", d: "M10.5 3.75a6.75 6.75 0 100 13.5 6.75 6.75 0 000-13.5zM2.25 10.5a8.25 8.25 0 1114.59 5.28l4.69 4.69a.75.75 0 11-1.06 1.06l-4.69-4.69A8.25 8.25 0 012.25 10.5z", clipRule: "evenodd" }) })
3133
3672
  ] }) }),
3134
3673
  /* @__PURE__ */ jsx(Popover.Portal, { children: /* @__PURE__ */ jsx(
3135
3674
  Popover.Content,
@@ -3138,36 +3677,33 @@ function AutoComplete({
3138
3677
  sideOffset: 4,
3139
3678
  onOpenAutoFocus: (e) => e.preventDefault(),
3140
3679
  className: "w-64 bg-surface border border-border rounded-lg mt-1 shadow-md z-50 overflow-y-auto max-h-36 animate-in fade-in-0 zoom-in-95",
3141
- children: foundItems.length === 0 ? /* @__PURE__ */ jsx("div", { className: "h-full w-full flex flex-col items-center justify-center py-4 text-sm text-foreground-secondary", children: emptyText }) : /* @__PURE__ */ jsx("div", { role: "listbox", children: foundItems.map((item) => (
3142
- // tabIndex + Enter/Space onKeyDown
3143
- // makes each option keyboard-activatable.
3144
- // Full roving-tabindex / arrow-key nav
3145
- // is deferred to the Phase-5 rewrite.
3146
- /* @__PURE__ */ jsxs(
3147
- "div",
3148
- {
3149
- role: "option",
3150
- tabIndex: 0,
3151
- className: "text-sm flex items-center gap-2 p-2 transition-colors duration-150 hover:bg-surface-raised cursor-pointer text-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-accent",
3152
- onClick: () => handleSelect(item),
3153
- onKeyDown: (e) => {
3154
- if (e.key === "Enter" || e.key === " ") {
3155
- e.preventDefault();
3156
- handleSelect(item);
3157
- }
3158
- },
3159
- children: [
3160
- item.icon,
3161
- /* @__PURE__ */ jsxs("span", { children: [
3162
- item.label,
3163
- " (",
3164
- item.value,
3165
- ")"
3166
- ] })
3167
- ]
3680
+ children: loading ? /* @__PURE__ */ jsxs("div", { className: "h-full w-full flex items-center justify-center gap-2 py-4 text-sm text-foreground-secondary", children: [
3681
+ /* @__PURE__ */ jsx(LoadingSpinner, { inline: true, size: "xs" }),
3682
+ /* @__PURE__ */ jsx("span", { children: loadingText })
3683
+ ] }) : foundItems.length === 0 ? /* @__PURE__ */ jsx("div", { className: "h-full w-full flex flex-col items-center justify-center py-4 text-sm text-foreground-secondary", children: emptyText }) : /* @__PURE__ */ jsx("div", { role: "listbox", children: foundItems.map((item) => /* @__PURE__ */ jsxs(
3684
+ "div",
3685
+ {
3686
+ role: "option",
3687
+ tabIndex: 0,
3688
+ className: "text-sm flex items-center gap-2 p-2 transition-colors duration-150 hover:bg-surface-raised cursor-pointer text-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-accent",
3689
+ onClick: () => handleSelect(item),
3690
+ onKeyDown: (e) => {
3691
+ if (e.key === "Enter" || e.key === " ") {
3692
+ e.preventDefault();
3693
+ handleSelect(item);
3694
+ }
3168
3695
  },
3169
- item.key
3170
- )
3696
+ children: [
3697
+ item.icon,
3698
+ /* @__PURE__ */ jsxs("span", { children: [
3699
+ item.label,
3700
+ " (",
3701
+ item.value,
3702
+ ")"
3703
+ ] })
3704
+ ]
3705
+ },
3706
+ item.key
3171
3707
  )) })
3172
3708
  }
3173
3709
  ) })
@@ -3883,6 +4419,6 @@ function ChevronRight3() {
3883
4419
  return /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, className: "w-4 h-4", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M9 5l7 7-7 7" }) });
3884
4420
  }
3885
4421
 
3886
- export { AppShell, AutoComplete, Button, Catalog, CatalogCarousel, CatalogGrid, Checkbox, ContextMenu, Drawer, Dropdown, FadingBase, FileInput, GridCard, icons_default as Icon, IconButton, List2 as List, LoadingSpinner, Modal, NotificationProvider, NumberInput, OpaqueGridCard, Password, Portal, ScalableContainer, SearchInput_default as SearchInput, Sidebar, SkeletonBox, SkeletonCard, SkeletonCircle, SkeletonText, Switch, Table, Tabs, DatePicker as Temporal, TextInput, ThemeProvider, ThemeSwitch, ToggleButton, Tooltip, TooltipProvider, TopBar, Tree, TreeSelect, Wizard, useNotification };
4422
+ export { AppShell, AutoComplete, Avatar, Box, Button, Catalog, CatalogCarousel, CatalogGrid, Checkbox, ContextMenu, Drawer, Dropdown, FadingBase, FileInput, Flex, Grid2 as Grid, GridCard, icons_default as Icon, IconButton, List2 as List, LoadingSpinner, Modal, NotificationProvider, NumberInput, OpaqueGridCard, Password, Portal, ScalableContainer, SearchInput_default as SearchInput, Sidebar, SkeletonBox, SkeletonCard, SkeletonCircle, SkeletonText, Switch, Table, Tabs, DatePicker as Temporal, TextInput, ThemeProvider, ThemeSwitch, ToggleButton, Tooltip, TooltipProvider, TopBar, Tree, TreeSelect, Typography, Wizard, useNotification };
3887
4423
  //# sourceMappingURL=index.js.map
3888
4424
  //# sourceMappingURL=index.js.map