@geomak/ui 5.1.0 → 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.cjs CHANGED
@@ -2397,9 +2397,105 @@ function Dropdown({
2397
2397
  ]
2398
2398
  }
2399
2399
  ),
2400
- hasError && /* @__PURE__ */ jsxRuntime.jsx("div", { id: errorId, className: "text-center text-status-error text-xs mt-1", children: errorMessage })
2400
+ hasError && /* @__PURE__ */ jsxRuntime.jsx("div", { id: errorId, className: "text-status-error text-xs mt-1", children: errorMessage })
2401
2401
  ] });
2402
2402
  }
2403
+ var SHIMMER = [
2404
+ "relative overflow-hidden rounded-sm bg-surface-raised",
2405
+ 'before:absolute before:inset-0 before:content-[""]',
2406
+ "before:bg-gradient-to-r before:from-transparent before:via-white/30 before:to-transparent",
2407
+ "before:animate-[shimmer_1.6s_linear_infinite]",
2408
+ // Respect prefers-reduced-motion — the resting bg-surface-raised is still
2409
+ // a perfectly legible placeholder for users who have animations off.
2410
+ "motion-reduce:before:hidden"
2411
+ ].join(" ");
2412
+ function SkeletonBox({ width, height = 16, radius, className = "", style }) {
2413
+ return /* @__PURE__ */ jsxRuntime.jsx(
2414
+ "span",
2415
+ {
2416
+ role: "presentation",
2417
+ "aria-hidden": "true",
2418
+ className: `block ${SHIMMER} ${className}`,
2419
+ style: {
2420
+ width: width ?? "100%",
2421
+ height,
2422
+ borderRadius: radius ?? "var(--radius-md)",
2423
+ ...style
2424
+ }
2425
+ }
2426
+ );
2427
+ }
2428
+ function SkeletonText({
2429
+ lines = 3,
2430
+ lastLineWidth = 60,
2431
+ lineHeight = 14,
2432
+ gap = 8,
2433
+ className = "",
2434
+ style
2435
+ }) {
2436
+ return /* @__PURE__ */ jsxRuntime.jsx(
2437
+ "div",
2438
+ {
2439
+ role: "presentation",
2440
+ "aria-hidden": "true",
2441
+ className: `flex flex-col ${className}`,
2442
+ style: { gap, ...style },
2443
+ children: Array.from({ length: lines }).map((_, i) => {
2444
+ const isLast = i === lines - 1;
2445
+ const width = isLast && lines > 1 ? `${lastLineWidth}%` : "100%";
2446
+ return /* @__PURE__ */ jsxRuntime.jsx(
2447
+ "span",
2448
+ {
2449
+ className: `block ${SHIMMER}`,
2450
+ style: { height: lineHeight, width, borderRadius: "var(--radius-sm)" }
2451
+ },
2452
+ i
2453
+ );
2454
+ })
2455
+ }
2456
+ );
2457
+ }
2458
+ function SkeletonCircle({ size = 40, className = "", style }) {
2459
+ return /* @__PURE__ */ jsxRuntime.jsx(
2460
+ "span",
2461
+ {
2462
+ role: "presentation",
2463
+ "aria-hidden": "true",
2464
+ className: `block flex-shrink-0 ${SHIMMER} ${className}`,
2465
+ style: {
2466
+ width: size,
2467
+ height: size,
2468
+ borderRadius: "50%",
2469
+ ...style
2470
+ }
2471
+ }
2472
+ );
2473
+ }
2474
+ function SkeletonCard({ hasAvatar = true, lines = 3, className = "", style }) {
2475
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2476
+ "div",
2477
+ {
2478
+ role: "presentation",
2479
+ "aria-hidden": "true",
2480
+ className: `rounded-lg border border-border bg-surface p-4 ${className}`,
2481
+ style,
2482
+ children: [
2483
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 mb-4", children: [
2484
+ hasAvatar && /* @__PURE__ */ jsxRuntime.jsx(SkeletonCircle, { size: 36 }),
2485
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 flex flex-col gap-2", children: [
2486
+ /* @__PURE__ */ jsxRuntime.jsx(SkeletonBox, { height: 12, width: "55%" }),
2487
+ /* @__PURE__ */ jsxRuntime.jsx(SkeletonBox, { height: 10, width: "35%" })
2488
+ ] })
2489
+ ] }),
2490
+ /* @__PURE__ */ jsxRuntime.jsx(SkeletonText, { lines, lastLineWidth: 55 }),
2491
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-4 flex gap-2", children: [
2492
+ /* @__PURE__ */ jsxRuntime.jsx(SkeletonBox, { height: 28, width: 72 }),
2493
+ /* @__PURE__ */ jsxRuntime.jsx(SkeletonBox, { height: 28, width: 56 })
2494
+ ] })
2495
+ ]
2496
+ }
2497
+ );
2498
+ }
2403
2499
  var DEFAULT_PICKER = [
2404
2500
  { key: 1, value: 5, label: 5 },
2405
2501
  { key: 2, value: 10, label: 10 },
@@ -2585,7 +2681,9 @@ function Table({
2585
2681
  expandRow = DEFAULT_EXPAND,
2586
2682
  hasSearch = true,
2587
2683
  footer = null,
2588
- header = null
2684
+ header = null,
2685
+ loading = false,
2686
+ loadingRowCount = 8
2589
2687
  }) {
2590
2688
  const searchRef = React8.useRef(null);
2591
2689
  const [searchTerm, setSearchTerm] = React8.useState("");
@@ -2669,9 +2767,16 @@ function Table({
2669
2767
  )
2670
2768
  ] }),
2671
2769
  /* @__PURE__ */ jsxRuntime.jsx("div", { children: header }),
2672
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "overflow-x-auto rounded-lg", children: /* @__PURE__ */ jsxRuntime.jsxs("table", { className: "w-full border-collapse", children: [
2770
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "overflow-x-auto rounded-lg", children: /* @__PURE__ */ jsxRuntime.jsxs("table", { className: "w-full border-collapse", "aria-busy": loading || void 0, children: [
2673
2771
  /* @__PURE__ */ jsxRuntime.jsx(TableHeader, { columns, hasExpand: !!expandRow.enabled }),
2674
- /* @__PURE__ */ jsxRuntime.jsx(
2772
+ loading ? /* @__PURE__ */ jsxRuntime.jsx(
2773
+ TableSkeletonBody,
2774
+ {
2775
+ columns,
2776
+ rowCount: loadingRowCount,
2777
+ hasExpand: !!expandRow.enabled
2778
+ }
2779
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
2675
2780
  TableBody,
2676
2781
  {
2677
2782
  columns,
@@ -2684,6 +2789,23 @@ function Table({
2684
2789
  /* @__PURE__ */ jsxRuntime.jsx("div", { children: footer })
2685
2790
  ] });
2686
2791
  }
2792
+ function TableSkeletonBody({
2793
+ columns,
2794
+ rowCount,
2795
+ hasExpand
2796
+ }) {
2797
+ return /* @__PURE__ */ jsxRuntime.jsx("tbody", { "aria-hidden": "true", children: Array.from({ length: rowCount }).map((_, i) => /* @__PURE__ */ jsxRuntime.jsxs(
2798
+ "tr",
2799
+ {
2800
+ className: `border-b border-border ${i % 2 === 0 ? "bg-surface" : "bg-surface-raised"}`,
2801
+ children: [
2802
+ hasExpand && /* @__PURE__ */ jsxRuntime.jsx("td", { className: "p-0 align-middle w-9" }),
2803
+ columns.map((col) => /* @__PURE__ */ jsxRuntime.jsx("td", { className: "py-3 px-3 align-middle", children: /* @__PURE__ */ jsxRuntime.jsx(SkeletonBox, { height: 12, width: `${50 + i % 4 * 12}%` }) }, col.key))
2804
+ ]
2805
+ },
2806
+ i
2807
+ )) });
2808
+ }
2687
2809
  function ThemeSwitch({ checked, onChange, label = "Toggle dark mode" }) {
2688
2810
  const id = React8.useId();
2689
2811
  return /* @__PURE__ */ jsxRuntime.jsx("label", { htmlFor: id, className: "flex items-center gap-2 cursor-pointer select-none", children: /* @__PURE__ */ jsxRuntime.jsx(
@@ -3116,102 +3238,6 @@ function ThemeProvider({
3116
3238
  )
3117
3239
  ] });
3118
3240
  }
3119
- var SHIMMER = [
3120
- "relative overflow-hidden rounded-sm bg-surface-raised",
3121
- 'before:absolute before:inset-0 before:content-[""]',
3122
- "before:bg-gradient-to-r before:from-transparent before:via-white/30 before:to-transparent",
3123
- "before:animate-[shimmer_1.6s_linear_infinite]",
3124
- // Respect prefers-reduced-motion — the resting bg-surface-raised is still
3125
- // a perfectly legible placeholder for users who have animations off.
3126
- "motion-reduce:before:hidden"
3127
- ].join(" ");
3128
- function SkeletonBox({ width, height = 16, radius, className = "", style }) {
3129
- return /* @__PURE__ */ jsxRuntime.jsx(
3130
- "span",
3131
- {
3132
- role: "presentation",
3133
- "aria-hidden": "true",
3134
- className: `block ${SHIMMER} ${className}`,
3135
- style: {
3136
- width: width ?? "100%",
3137
- height,
3138
- borderRadius: radius ?? "var(--radius-md)",
3139
- ...style
3140
- }
3141
- }
3142
- );
3143
- }
3144
- function SkeletonText({
3145
- lines = 3,
3146
- lastLineWidth = 60,
3147
- lineHeight = 14,
3148
- gap = 8,
3149
- className = "",
3150
- style
3151
- }) {
3152
- return /* @__PURE__ */ jsxRuntime.jsx(
3153
- "div",
3154
- {
3155
- role: "presentation",
3156
- "aria-hidden": "true",
3157
- className: `flex flex-col ${className}`,
3158
- style: { gap, ...style },
3159
- children: Array.from({ length: lines }).map((_, i) => {
3160
- const isLast = i === lines - 1;
3161
- const width = isLast && lines > 1 ? `${lastLineWidth}%` : "100%";
3162
- return /* @__PURE__ */ jsxRuntime.jsx(
3163
- "span",
3164
- {
3165
- className: `block ${SHIMMER}`,
3166
- style: { height: lineHeight, width, borderRadius: "var(--radius-sm)" }
3167
- },
3168
- i
3169
- );
3170
- })
3171
- }
3172
- );
3173
- }
3174
- function SkeletonCircle({ size = 40, className = "", style }) {
3175
- return /* @__PURE__ */ jsxRuntime.jsx(
3176
- "span",
3177
- {
3178
- role: "presentation",
3179
- "aria-hidden": "true",
3180
- className: `block flex-shrink-0 ${SHIMMER} ${className}`,
3181
- style: {
3182
- width: size,
3183
- height: size,
3184
- borderRadius: "50%",
3185
- ...style
3186
- }
3187
- }
3188
- );
3189
- }
3190
- function SkeletonCard({ hasAvatar = true, lines = 3, className = "", style }) {
3191
- return /* @__PURE__ */ jsxRuntime.jsxs(
3192
- "div",
3193
- {
3194
- role: "presentation",
3195
- "aria-hidden": "true",
3196
- className: `rounded-lg border border-border bg-surface p-4 ${className}`,
3197
- style,
3198
- children: [
3199
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 mb-4", children: [
3200
- hasAvatar && /* @__PURE__ */ jsxRuntime.jsx(SkeletonCircle, { size: 36 }),
3201
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 flex flex-col gap-2", children: [
3202
- /* @__PURE__ */ jsxRuntime.jsx(SkeletonBox, { height: 12, width: "55%" }),
3203
- /* @__PURE__ */ jsxRuntime.jsx(SkeletonBox, { height: 10, width: "35%" })
3204
- ] })
3205
- ] }),
3206
- /* @__PURE__ */ jsxRuntime.jsx(SkeletonText, { lines, lastLineWidth: 55 }),
3207
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-4 flex gap-2", children: [
3208
- /* @__PURE__ */ jsxRuntime.jsx(SkeletonBox, { height: 28, width: 72 }),
3209
- /* @__PURE__ */ jsxRuntime.jsx(SkeletonBox, { height: 28, width: 56 })
3210
- ] })
3211
- ]
3212
- }
3213
- );
3214
- }
3215
3241
  function TextInput({
3216
3242
  value,
3217
3243
  onChange,
@@ -3229,48 +3255,50 @@ function TextInput({
3229
3255
  }) {
3230
3256
  const errorId = React8.useId();
3231
3257
  const hasError = errorMessage != null;
3232
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex flex-col items-center justify-center", children: [
3258
+ return (
3259
+ // In horizontal mode the row layout is [label, input-with-error-column].
3260
+ // The error sits under the input ONLY, not spanning the label too.
3261
+ // In vertical mode the whole thing is a column.
3233
3262
  /* @__PURE__ */ jsxRuntime.jsxs(
3234
3263
  "div",
3235
3264
  {
3236
- className: `flex ${layout === "vertical" ? "flex-col" : "flex-row items-center gap-2"}`,
3265
+ className: `flex ${layout === "vertical" ? "flex-col gap-1" : "flex-row items-start gap-2"}`,
3237
3266
  style: style ?? {},
3238
3267
  children: [
3239
- label && // Render <label> only when a label is provided. An empty
3240
- // <label htmlFor=…> announces as an unlabeled control in
3241
- // some screen readers.
3242
- /* @__PURE__ */ jsxRuntime.jsx(
3268
+ label && /* @__PURE__ */ jsxRuntime.jsx(
3243
3269
  "label",
3244
3270
  {
3245
3271
  style: { color: labelColor || void 0 },
3246
- className: `text-sm font-medium ml-1 max-content ${!labelColor && "text-foreground"}`,
3272
+ className: `text-sm font-medium ${layout === "horizontal" ? "mt-2" : ""} max-content ${!labelColor && "text-foreground"}`,
3247
3273
  htmlFor,
3248
3274
  children: label
3249
3275
  }
3250
3276
  ),
3251
- /* @__PURE__ */ jsxRuntime.jsx(
3252
- "input",
3253
- {
3254
- autoComplete: "off",
3255
- disabled,
3256
- value,
3257
- onChange,
3258
- onBlur,
3259
- type: "text",
3260
- name,
3261
- id: htmlFor,
3262
- "aria-invalid": hasError || void 0,
3263
- "aria-describedby": hasError ? errorId : void 0,
3264
- 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`,
3265
- style: inputStyle ?? {},
3266
- placeholder: placeholder ?? ""
3267
- }
3268
- )
3277
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col", children: [
3278
+ /* @__PURE__ */ jsxRuntime.jsx(
3279
+ "input",
3280
+ {
3281
+ autoComplete: "off",
3282
+ disabled,
3283
+ value,
3284
+ onChange,
3285
+ onBlur,
3286
+ type: "text",
3287
+ name,
3288
+ id: htmlFor,
3289
+ "aria-invalid": hasError || void 0,
3290
+ "aria-describedby": hasError ? errorId : void 0,
3291
+ 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`,
3292
+ style: inputStyle ?? {},
3293
+ placeholder: placeholder ?? ""
3294
+ }
3295
+ ),
3296
+ hasError && /* @__PURE__ */ jsxRuntime.jsx("div", { id: errorId, className: "text-status-error text-xs mt-1", children: errorMessage })
3297
+ ] })
3269
3298
  ]
3270
3299
  }
3271
- ),
3272
- hasError && /* @__PURE__ */ jsxRuntime.jsx("div", { id: errorId, className: "text-center text-status-error text-xs mt-1", children: errorMessage })
3273
- ] });
3300
+ )
3301
+ );
3274
3302
  }
3275
3303
  function NumberInput({
3276
3304
  step = 1,
@@ -3412,64 +3440,71 @@ function Password({
3412
3440
  const [passwordVisible, setPasswordVisible] = React8.useState(false);
3413
3441
  const errorId = React8.useId();
3414
3442
  const hasError = errorMessage != null;
3415
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex flex-col items-center justify-center", style: style ?? {}, children: [
3416
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `flex ${layout === "vertical" ? "flex-col" : "flex-row items-center gap-2"}`, children: [
3417
- label && /* @__PURE__ */ jsxRuntime.jsx(
3418
- "label",
3419
- {
3420
- style: { color: labelColor || void 0 },
3421
- className: `text-sm font-medium ml-1 max-content ${!labelColor && "text-foreground"}`,
3422
- htmlFor,
3423
- children: label
3424
- }
3425
- ),
3426
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
3427
- /* @__PURE__ */ jsxRuntime.jsx(
3428
- "input",
3443
+ return /* @__PURE__ */ jsxRuntime.jsxs(
3444
+ "div",
3445
+ {
3446
+ className: `flex ${layout === "vertical" ? "flex-col gap-1" : "flex-row items-start gap-2"}`,
3447
+ style: style ?? {},
3448
+ children: [
3449
+ label && /* @__PURE__ */ jsxRuntime.jsx(
3450
+ "label",
3429
3451
  {
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 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`,
3441
- style: inputStyle ?? {},
3442
- placeholder: placeholder ?? ""
3452
+ style: { color: labelColor || void 0 },
3453
+ className: `text-sm font-medium ${layout === "horizontal" ? "mt-2" : ""} max-content ${!labelColor && "text-foreground"}`,
3454
+ htmlFor,
3455
+ children: label
3443
3456
  }
3444
3457
  ),
3445
- /* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "currentColor", className: "w-6 h-6", children: [
3456
- /* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.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__ */ jsxRuntime.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__ */ jsxRuntime.jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "currentColor", className: "w-6 h-6", children: [
3463
- /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 15a3 3 0 100-6 3 3 0 000 6z" }),
3464
- /* @__PURE__ */ jsxRuntime.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
- ] })
3458
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col", children: [
3459
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
3460
+ /* @__PURE__ */ jsxRuntime.jsx(
3461
+ "input",
3462
+ {
3463
+ autoComplete: "off",
3464
+ disabled,
3465
+ value,
3466
+ onChange,
3467
+ onBlur,
3468
+ type: passwordVisible ? "text" : "password",
3469
+ name,
3470
+ id: htmlFor,
3471
+ "aria-invalid": hasError || void 0,
3472
+ "aria-describedby": hasError ? errorId : void 0,
3473
+ 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`,
3474
+ style: inputStyle ?? {},
3475
+ placeholder: placeholder ?? ""
3476
+ }
3477
+ ),
3478
+ /* @__PURE__ */ jsxRuntime.jsx(
3479
+ "button",
3480
+ {
3481
+ type: "button",
3482
+ className: "cursor-pointer p-1 text-foreground-secondary hover:text-foreground rounded-md focus:outline-none focus-visible:ring-2 focus-visible:ring-accent",
3483
+ style: iconColor ? { color: iconColor } : void 0,
3484
+ onClick: () => setPasswordVisible(!passwordVisible),
3485
+ "aria-label": passwordVisible ? "Hide password" : "Show password",
3486
+ children: passwordVisible ? (
3487
+ /* EyeSlash */
3488
+ /* @__PURE__ */ jsxRuntime.jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "currentColor", className: "w-6 h-6", children: [
3489
+ /* @__PURE__ */ jsxRuntime.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" }),
3490
+ /* @__PURE__ */ jsxRuntime.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" }),
3491
+ /* @__PURE__ */ jsxRuntime.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" })
3492
+ ] })
3493
+ ) : (
3494
+ /* Eye */
3495
+ /* @__PURE__ */ jsxRuntime.jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "currentColor", className: "w-6 h-6", children: [
3496
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 15a3 3 0 100-6 3 3 0 000 6z" }),
3497
+ /* @__PURE__ */ jsxRuntime.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" })
3498
+ ] })
3499
+ )
3500
+ }
3466
3501
  )
3467
- }
3468
- )
3469
- ] })
3470
- ] }),
3471
- hasError && /* @__PURE__ */ jsxRuntime.jsx("div", { id: errorId, className: "text-center text-status-error text-xs mt-1", children: errorMessage })
3472
- ] });
3502
+ ] }),
3503
+ hasError && /* @__PURE__ */ jsxRuntime.jsx("div", { id: errorId, className: "text-status-error text-xs mt-1", children: errorMessage })
3504
+ ] })
3505
+ ]
3506
+ }
3507
+ );
3473
3508
  }
3474
3509
  function Checkbox({
3475
3510
  checked,
@@ -3582,15 +3617,54 @@ function AutoComplete({
3582
3617
  inputStyle,
3583
3618
  style,
3584
3619
  layout = "vertical",
3585
- items = [],
3620
+ items,
3621
+ onSearch,
3622
+ debounce = 250,
3586
3623
  onItemClick,
3587
- emptyText = "No results found"
3624
+ emptyText = "No results found",
3625
+ loadingText = "Searching\u2026"
3588
3626
  }) {
3589
3627
  const [term, setTerm] = React8.useState("");
3590
3628
  const [open, setOpen] = React8.useState(false);
3591
- const foundItems = term.trim() ? items.filter(
3629
+ const [asyncItems, setAsyncItems] = React8.useState([]);
3630
+ const [loading, setLoading] = React8.useState(false);
3631
+ const isAsync = typeof onSearch === "function";
3632
+ const debounceRef = React8.useRef(null);
3633
+ const requestIdRef = React8.useRef(0);
3634
+ const staticFiltered = isAsync || !items ? [] : term.trim() ? items.filter(
3592
3635
  ({ key, label: label2 }) => label2.toLowerCase().includes(term.toLowerCase()) || key.toLowerCase().includes(term.toLowerCase())
3593
3636
  ) : [];
3637
+ React8.useEffect(() => {
3638
+ if (!isAsync) return;
3639
+ if (debounceRef.current) clearTimeout(debounceRef.current);
3640
+ if (!term.trim()) {
3641
+ setAsyncItems([]);
3642
+ setLoading(false);
3643
+ return;
3644
+ }
3645
+ const myId = ++requestIdRef.current;
3646
+ setLoading(true);
3647
+ debounceRef.current = setTimeout(async () => {
3648
+ try {
3649
+ const res = await onSearch(term);
3650
+ if (myId === requestIdRef.current) {
3651
+ setAsyncItems(res);
3652
+ }
3653
+ } catch {
3654
+ if (myId === requestIdRef.current) {
3655
+ setAsyncItems([]);
3656
+ }
3657
+ } finally {
3658
+ if (myId === requestIdRef.current) {
3659
+ setLoading(false);
3660
+ }
3661
+ }
3662
+ }, debounce);
3663
+ return () => {
3664
+ if (debounceRef.current) clearTimeout(debounceRef.current);
3665
+ };
3666
+ }, [term, isAsync, debounce, onSearch]);
3667
+ const foundItems = isAsync ? asyncItems : staticFiltered;
3594
3668
  const handleSelect = (item) => {
3595
3669
  setTerm(`${item.label} (${item.value})`);
3596
3670
  onItemClick?.(item.value);
@@ -3623,10 +3697,11 @@ function AutoComplete({
3623
3697
  autoComplete: "off",
3624
3698
  "aria-haspopup": "listbox",
3625
3699
  "aria-expanded": open,
3626
- "aria-autocomplete": "list"
3700
+ "aria-autocomplete": "list",
3701
+ "aria-busy": loading || void 0
3627
3702
  }
3628
3703
  ),
3629
- /* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.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" }) })
3704
+ loading ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "w-5 h-5 flex-shrink-0 flex items-center justify-center text-accent", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx(LoadingSpinner, { inline: true, size: "xs", spinnerColor: "currentColor" }) }) : /* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.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" }) })
3630
3705
  ] }) }),
3631
3706
  /* @__PURE__ */ jsxRuntime.jsx(Popover__namespace.Portal, { children: /* @__PURE__ */ jsxRuntime.jsx(
3632
3707
  Popover__namespace.Content,
@@ -3635,36 +3710,33 @@ function AutoComplete({
3635
3710
  sideOffset: 4,
3636
3711
  onOpenAutoFocus: (e) => e.preventDefault(),
3637
3712
  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",
3638
- children: foundItems.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-full w-full flex flex-col items-center justify-center py-4 text-sm text-foreground-secondary", children: emptyText }) : /* @__PURE__ */ jsxRuntime.jsx("div", { role: "listbox", children: foundItems.map((item) => (
3639
- // tabIndex + Enter/Space onKeyDown
3640
- // makes each option keyboard-activatable.
3641
- // Full roving-tabindex / arrow-key nav
3642
- // is deferred to the Phase-5 rewrite.
3643
- /* @__PURE__ */ jsxRuntime.jsxs(
3644
- "div",
3645
- {
3646
- role: "option",
3647
- tabIndex: 0,
3648
- 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",
3649
- onClick: () => handleSelect(item),
3650
- onKeyDown: (e) => {
3651
- if (e.key === "Enter" || e.key === " ") {
3652
- e.preventDefault();
3653
- handleSelect(item);
3654
- }
3655
- },
3656
- children: [
3657
- item.icon,
3658
- /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
3659
- item.label,
3660
- " (",
3661
- item.value,
3662
- ")"
3663
- ] })
3664
- ]
3713
+ children: loading ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "h-full w-full flex items-center justify-center gap-2 py-4 text-sm text-foreground-secondary", children: [
3714
+ /* @__PURE__ */ jsxRuntime.jsx(LoadingSpinner, { inline: true, size: "xs" }),
3715
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: loadingText })
3716
+ ] }) : foundItems.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-full w-full flex flex-col items-center justify-center py-4 text-sm text-foreground-secondary", children: emptyText }) : /* @__PURE__ */ jsxRuntime.jsx("div", { role: "listbox", children: foundItems.map((item) => /* @__PURE__ */ jsxRuntime.jsxs(
3717
+ "div",
3718
+ {
3719
+ role: "option",
3720
+ tabIndex: 0,
3721
+ 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",
3722
+ onClick: () => handleSelect(item),
3723
+ onKeyDown: (e) => {
3724
+ if (e.key === "Enter" || e.key === " ") {
3725
+ e.preventDefault();
3726
+ handleSelect(item);
3727
+ }
3665
3728
  },
3666
- item.key
3667
- )
3729
+ children: [
3730
+ item.icon,
3731
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
3732
+ item.label,
3733
+ " (",
3734
+ item.value,
3735
+ ")"
3736
+ ] })
3737
+ ]
3738
+ },
3739
+ item.key
3668
3740
  )) })
3669
3741
  }
3670
3742
  ) })