@geomak/ui 5.2.0 → 5.4.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
@@ -14,6 +14,8 @@ import * as ContextMenuPrimitive from '@radix-ui/react-context-menu';
14
14
  import * as Popover from '@radix-ui/react-popover';
15
15
  import * as SwitchPrimitive from '@radix-ui/react-switch';
16
16
  import * as CheckboxPrimitive from '@radix-ui/react-checkbox';
17
+ import * as RadioGroupPrimitive from '@radix-ui/react-radio-group';
18
+ import * as SliderPrimitive from '@radix-ui/react-slider';
17
19
 
18
20
  var Moon = ({ color = "gray" }) => /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", fill: color, viewBox: "0 0 24 24", strokeWidth: 1.5, stroke: color, className: "w-8 h-8", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M21.752 15.002A9.718 9.718 0 0118 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 003 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 009.002-5.998z" }) });
19
21
  var Sun = ({ color = "yellow" }) => /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", fill: color, viewBox: "0 0 24 24", strokeWidth: 1.5, stroke: color, className: "w-8 h-8", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M12 3v2.25m6.364.386l-1.591 1.591M21 12h-2.25m-.386 6.364l-1.591-1.591M12 18.75V21m-4.773-4.227l-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M15.75 12a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0z" }) });
@@ -2132,46 +2134,112 @@ function Wizard({
2132
2134
  ] }) })
2133
2135
  ] });
2134
2136
  }
2135
- var SearchInput = React8.forwardRef(function SearchInput2({
2136
- value,
2137
- onChange,
2138
- disabled,
2137
+ var FIELD_SIZE = {
2138
+ sm: { control: "h-control-sm", text: "text-xs", padX: "px-2.5", gap: "gap-1.5" },
2139
+ md: { control: "h-control-md", text: "text-sm", padX: "px-3", gap: "gap-2" },
2140
+ lg: { control: "h-control-lg", text: "text-sm", padX: "px-3.5", gap: "gap-2.5" }
2141
+ };
2142
+ var FOCUS_WITHIN = "focus-within:outline-none focus-within:border-accent focus-within:ring-[3px] focus-within:ring-focus-ring";
2143
+ var FOCUS_VISIBLE = "focus-visible:outline-none focus-visible:border-accent focus-visible:ring-[3px] focus-visible:ring-focus-ring";
2144
+ var FOCUS_WITHIN_ERROR = "focus-within:border-status-error focus-within:ring-focus-ring-error";
2145
+ var FOCUS_VISIBLE_ERROR = "focus-visible:border-status-error focus-visible:ring-focus-ring-error";
2146
+ function fieldShell({
2147
+ size = "md",
2148
+ hasError = false,
2149
+ disabled = false,
2150
+ focusWithin = false,
2151
+ sized = true
2152
+ } = {}) {
2153
+ const s = FIELD_SIZE[size];
2154
+ return [
2155
+ "w-full rounded-lg border bg-surface text-foreground",
2156
+ "transition-[color,box-shadow,border-color] duration-150",
2157
+ s.text,
2158
+ sized ? `${s.control} ${s.padX}` : "",
2159
+ // resting border
2160
+ hasError ? "border-status-error" : "border-border",
2161
+ // hover (only when interactive + no error)
2162
+ disabled ? "bg-surface-raised text-foreground-muted cursor-not-allowed" : hasError ? "" : "hover:border-border-strong",
2163
+ // focus
2164
+ focusWithin ? FOCUS_WITHIN : FOCUS_VISIBLE,
2165
+ hasError ? focusWithin ? FOCUS_WITHIN_ERROR : FOCUS_VISIBLE_ERROR : "",
2166
+ // placeholder colour for native inputs
2167
+ "placeholder:text-foreground-muted"
2168
+ ].filter(Boolean).join(" ");
2169
+ }
2170
+ function Field({
2139
2171
  label,
2140
2172
  htmlFor,
2141
- placeholder,
2142
- name,
2143
- inputStyle,
2144
- style,
2145
- layout = "vertical"
2146
- }, ref) {
2147
- return /* @__PURE__ */ jsx("div", { className: "relative flex flex-col items-center justify-center", children: /* @__PURE__ */ jsxs(
2173
+ errorId,
2174
+ errorMessage,
2175
+ layout = "vertical",
2176
+ required,
2177
+ labelStyle,
2178
+ labelWidth,
2179
+ className = "",
2180
+ children
2181
+ }) {
2182
+ const hasError = errorMessage != null;
2183
+ const horizontal = layout === "horizontal";
2184
+ return /* @__PURE__ */ jsxs(
2148
2185
  "div",
2149
2186
  {
2150
- className: `flex ${layout === "vertical" ? "flex-col" : "flex-row items-center gap-2"}`,
2151
- style: style ?? {},
2187
+ className: [
2188
+ "flex",
2189
+ horizontal ? "flex-row items-start gap-3" : "flex-col gap-1.5",
2190
+ className
2191
+ ].filter(Boolean).join(" "),
2152
2192
  children: [
2153
- label && /* @__PURE__ */ jsx("label", { className: "text-sm font-medium ml-1 max-content text-foreground", htmlFor, children: label }),
2154
- /* @__PURE__ */ jsxs("div", { className: "bg-surface text-foreground flex items-center gap-1 rounded-lg border border-border pr-2 focus-within:border-transparent focus-within:ring-2 focus-within:ring-accent transition-colors", children: [
2155
- /* @__PURE__ */ jsx(
2156
- "input",
2157
- {
2158
- ref,
2159
- disabled,
2160
- value,
2161
- onChange,
2162
- type: "search",
2163
- enterKeyHint: "search",
2164
- name,
2165
- id: htmlFor,
2166
- className: "bg-transparent focus:outline-none pl-2 h-9 w-56 rounded-lg disabled:cursor-not-allowed",
2167
- style: inputStyle ?? {},
2168
- placeholder: placeholder ?? ""
2169
- }
2170
- ),
2171
- /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "currentColor", className: "w-5 h-5 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" }) })
2193
+ label && /* @__PURE__ */ jsxs(
2194
+ "label",
2195
+ {
2196
+ htmlFor,
2197
+ style: { width: horizontal ? labelWidth : void 0, ...labelStyle },
2198
+ className: [
2199
+ "text-sm font-medium text-foreground select-none",
2200
+ horizontal ? "mt-2 flex-shrink-0" : ""
2201
+ ].filter(Boolean).join(" "),
2202
+ children: [
2203
+ label,
2204
+ required && /* @__PURE__ */ jsx("span", { className: "text-status-error ml-0.5", "aria-hidden": "true", children: "*" })
2205
+ ]
2206
+ }
2207
+ ),
2208
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col min-w-0 flex-1", children: [
2209
+ children,
2210
+ hasError && /* @__PURE__ */ jsx("div", { id: errorId, className: "text-status-error text-xs mt-1", children: errorMessage })
2172
2211
  ] })
2173
2212
  ]
2174
2213
  }
2214
+ );
2215
+ }
2216
+ var SearchIcon = /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "currentColor", className: "w-4 h-4", "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" }) });
2217
+ var SearchInput = React8.forwardRef(function SearchInput2({ value, onChange, disabled, label, htmlFor, placeholder, name, inputStyle, style, layout = "vertical", size = "md", icon }, ref) {
2218
+ return /* @__PURE__ */ jsx(Field, { label, htmlFor, layout, children: /* @__PURE__ */ jsxs(
2219
+ "div",
2220
+ {
2221
+ className: `flex items-center ${fieldShell({ size, disabled, focusWithin: true })}`,
2222
+ style,
2223
+ children: [
2224
+ /* @__PURE__ */ jsx("span", { className: "flex-shrink-0 mr-2 text-foreground-muted", children: icon ?? SearchIcon }),
2225
+ /* @__PURE__ */ jsx(
2226
+ "input",
2227
+ {
2228
+ ref,
2229
+ disabled,
2230
+ value,
2231
+ onChange,
2232
+ type: "search",
2233
+ enterKeyHint: "search",
2234
+ name,
2235
+ id: htmlFor,
2236
+ className: "min-w-0 flex-1 bg-transparent outline-none disabled:cursor-not-allowed placeholder:text-foreground-muted",
2237
+ style: inputStyle,
2238
+ placeholder: placeholder ?? ""
2239
+ }
2240
+ )
2241
+ ]
2242
+ }
2175
2243
  ) });
2176
2244
  });
2177
2245
  var SearchInput_default = SearchInput;
@@ -2199,7 +2267,8 @@ function Dropdown({
2199
2267
  items = [],
2200
2268
  labelStyle = {},
2201
2269
  placeholder,
2202
- showSelectedCount = false
2270
+ showSelectedCount = false,
2271
+ size = "md"
2203
2272
  }) {
2204
2273
  const [open, setOpen] = useState(false);
2205
2274
  const [selectedItems, setSelectedItems] = useState([]);
@@ -2262,7 +2331,7 @@ function Dropdown({
2262
2331
  "aria-invalid": hasError || void 0,
2263
2332
  "aria-describedby": hasError ? errorId : void 0,
2264
2333
  style,
2265
- className: `flex items-center justify-between relative h-9 rounded-lg border border-border cursor-pointer select-none focus:outline-none focus-visible:border-transparent focus-visible:ring-2 focus-visible:ring-accent ${disabled ? "cursor-not-allowed bg-surface-raised text-foreground-muted" : "bg-surface text-foreground"} ${hasError ? "border-status-error" : ""}`,
2334
+ className: `flex items-center justify-between cursor-pointer select-none ${fieldShell({ size, hasError, disabled })}`,
2266
2335
  tabIndex: disabled ? -1 : 0,
2267
2336
  onKeyDown: (e) => {
2268
2337
  if (disabled) return;
@@ -2275,7 +2344,7 @@ function Dropdown({
2275
2344
  /* @__PURE__ */ jsx(
2276
2345
  "div",
2277
2346
  {
2278
- className: `h-7 pl-2 ${!style?.width ? "min-w-[240px]" : ""} flex items-center gap-1 overflow-hidden`,
2347
+ className: `${!style?.width ? "min-w-[200px]" : ""} flex items-center gap-1 overflow-hidden`,
2279
2348
  children: !value || Array.isArray(value) && value.length === 0 ? /* @__PURE__ */ jsx("span", { className: "text-foreground-muted text-sm", children: placeholder }) : Array.isArray(value) ? /* @__PURE__ */ jsxs(Fragment, { children: [
2280
2349
  value.slice(0, 1).map((val) => /* @__PURE__ */ jsx(
2281
2350
  DropdownPill,
@@ -2289,7 +2358,7 @@ function Dropdown({
2289
2358
  ] }) : /* @__PURE__ */ jsx(DropdownPill, { value: innerItems.find((it) => it.key === value)?.label })
2290
2359
  }
2291
2360
  ),
2292
- /* @__PURE__ */ jsx("div", { className: `transition-transform duration-200 mr-2 ${open ? "rotate-180" : "rotate-0"}`, "aria-hidden": "true", children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, className: "h-4 w-4", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M19 9l-7 7-7-7" }) }) })
2361
+ /* @__PURE__ */ jsx("div", { className: `flex-shrink-0 ml-2 text-foreground-muted transition-transform duration-200 ${open ? "rotate-180" : "rotate-0"}`, "aria-hidden": "true", children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, className: "h-4 w-4", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M19 9l-7 7-7-7" }) }) })
2293
2362
  ]
2294
2363
  }
2295
2364
  ) }),
@@ -3213,58 +3282,61 @@ function TextInput({
3213
3282
  htmlFor,
3214
3283
  placeholder,
3215
3284
  name,
3285
+ type = "text",
3216
3286
  inputStyle,
3217
3287
  style,
3218
- layout,
3288
+ layout = "vertical",
3289
+ size = "md",
3219
3290
  onBlur,
3220
3291
  errorMessage,
3221
- labelColor
3292
+ required,
3293
+ prefix,
3294
+ suffix
3222
3295
  }) {
3223
3296
  const errorId = useId();
3224
3297
  const hasError = errorMessage != null;
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.
3229
- /* @__PURE__ */ jsxs(
3230
- "div",
3231
- {
3232
- className: `flex ${layout === "vertical" ? "flex-col gap-1" : "flex-row items-start gap-2"}`,
3233
- style: style ?? {},
3234
- children: [
3235
- label && /* @__PURE__ */ jsx(
3236
- "label",
3237
- {
3238
- style: { color: labelColor || void 0 },
3239
- className: `text-sm font-medium ${layout === "horizontal" ? "mt-2" : ""} max-content ${!labelColor && "text-foreground"}`,
3240
- htmlFor,
3241
- children: label
3242
- }
3243
- ),
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
- ] })
3265
- ]
3266
- }
3267
- )
3298
+ const hasAdornment = prefix != null || suffix != null;
3299
+ const input = /* @__PURE__ */ jsx(
3300
+ "input",
3301
+ {
3302
+ autoComplete: "off",
3303
+ disabled,
3304
+ value,
3305
+ onChange,
3306
+ onBlur,
3307
+ type,
3308
+ name,
3309
+ id: htmlFor,
3310
+ "aria-invalid": hasError || void 0,
3311
+ "aria-describedby": hasError ? errorId : void 0,
3312
+ placeholder: placeholder ?? "",
3313
+ className: hasAdornment ? "min-w-0 flex-1 bg-transparent outline-none disabled:cursor-not-allowed placeholder:text-foreground-muted" : fieldShell({ size, hasError, disabled }),
3314
+ style: inputStyle
3315
+ }
3316
+ );
3317
+ return /* @__PURE__ */ jsx(
3318
+ Field,
3319
+ {
3320
+ label,
3321
+ htmlFor,
3322
+ errorId,
3323
+ errorMessage,
3324
+ layout,
3325
+ required,
3326
+ className: style ? void 0 : void 0,
3327
+ children: hasAdornment ? /* @__PURE__ */ jsxs(
3328
+ "div",
3329
+ {
3330
+ className: `flex items-center ${fieldShell({ size, hasError, disabled, focusWithin: true })}`,
3331
+ style,
3332
+ children: [
3333
+ prefix && /* @__PURE__ */ jsx("span", { className: "flex-shrink-0 mr-2 text-foreground-muted", children: prefix }),
3334
+ input,
3335
+ suffix && /* @__PURE__ */ jsx("span", { className: "flex-shrink-0 ml-2 text-foreground-muted", children: suffix })
3336
+ ]
3337
+ }
3338
+ ) : input
3339
+ }
3268
3340
  );
3269
3341
  }
3270
3342
  function NumberInput({
@@ -3275,8 +3347,10 @@ function NumberInput({
3275
3347
  htmlFor,
3276
3348
  name,
3277
3349
  disabled,
3278
- layout = "horizontal",
3350
+ layout = "vertical",
3351
+ size = "md",
3279
3352
  errorMessage,
3353
+ required,
3280
3354
  inputStyle,
3281
3355
  labelStyle,
3282
3356
  placeholder,
@@ -3317,22 +3391,21 @@ function NumberInput({
3317
3391
  if (Number.isNaN(parsed)) return;
3318
3392
  onChange?.({ target: { value: round(parsed), id: htmlFor, name } });
3319
3393
  };
3320
- return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
3321
- /* @__PURE__ */ jsxs("div", { className: `flex ${layout === "vertical" ? "flex-col gap-1" : "flex-row items-center gap-2"}`, children: [
3322
- label && /* @__PURE__ */ jsx(
3323
- "label",
3324
- {
3325
- className: "text-sm font-medium ml-1 max-content select-none text-foreground",
3326
- style: labelStyle,
3327
- htmlFor,
3328
- children: label
3329
- }
3330
- ),
3331
- /* @__PURE__ */ jsxs(
3394
+ return /* @__PURE__ */ jsx(
3395
+ Field,
3396
+ {
3397
+ label,
3398
+ htmlFor,
3399
+ errorId,
3400
+ errorMessage,
3401
+ layout,
3402
+ required,
3403
+ labelStyle,
3404
+ children: /* @__PURE__ */ jsxs(
3332
3405
  "div",
3333
3406
  {
3334
3407
  style,
3335
- className: `flex items-center rounded-lg border overflow-hidden ${hasError ? "border-status-error" : "border-border"} ${disabled ? "bg-surface-raised text-foreground-muted cursor-not-allowed" : "bg-surface text-foreground"} focus-within:border-transparent focus-within:ring-2 focus-within:ring-accent transition-colors`,
3408
+ className: `flex items-center overflow-hidden pr-0 ${fieldShell({ size, hasError, disabled, focusWithin: true })}`,
3336
3409
  children: [
3337
3410
  /* @__PURE__ */ jsx(
3338
3411
  "input",
@@ -3349,13 +3422,13 @@ function NumberInput({
3349
3422
  type: "number",
3350
3423
  "aria-invalid": hasError || void 0,
3351
3424
  "aria-describedby": hasError ? errorId : void 0,
3352
- className: "bg-transparent focus:outline-none h-9 w-full px-3 disabled:cursor-not-allowed [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none",
3353
- style: inputStyle ?? {},
3425
+ className: "min-w-0 flex-1 bg-transparent outline-none h-full disabled:cursor-not-allowed [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none placeholder:text-foreground-muted",
3426
+ style: inputStyle,
3354
3427
  placeholder: placeholder ?? "",
3355
3428
  readOnly
3356
3429
  }
3357
3430
  ),
3358
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col border-l border-border h-9", children: [
3431
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col self-stretch border-l border-border flex-shrink-0", children: [
3359
3432
  /* @__PURE__ */ jsx(
3360
3433
  "button",
3361
3434
  {
@@ -3364,7 +3437,7 @@ function NumberInput({
3364
3437
  onClick: onIncrement,
3365
3438
  disabled: disabled || readOnly || max !== void 0 && numeric >= max,
3366
3439
  "aria-label": "Increase value",
3367
- className: "flex-1 px-1.5 flex items-center justify-center hover:bg-surface-raised disabled:opacity-30 disabled:cursor-not-allowed transition-colors focus:outline-none focus-visible:bg-surface-raised",
3440
+ className: "flex-1 px-1.5 flex items-center justify-center text-foreground-muted hover:bg-surface-raised hover:text-foreground disabled:opacity-30 disabled:cursor-not-allowed transition-colors",
3368
3441
  children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2.5, className: "h-3 w-3", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M5 15l7-7 7 7" }) })
3369
3442
  }
3370
3443
  ),
@@ -3376,7 +3449,7 @@ function NumberInput({
3376
3449
  onClick: onDecrement,
3377
3450
  disabled: disabled || readOnly || min !== void 0 && numeric <= min,
3378
3451
  "aria-label": "Decrease value",
3379
- className: "flex-1 px-1.5 flex items-center justify-center hover:bg-surface-raised disabled:opacity-30 disabled:cursor-not-allowed transition-colors focus:outline-none focus-visible:bg-surface-raised border-t border-border",
3452
+ className: "flex-1 px-1.5 flex items-center justify-center text-foreground-muted hover:bg-surface-raised hover:text-foreground disabled:opacity-30 disabled:cursor-not-allowed transition-colors border-t border-border",
3380
3453
  children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2.5, className: "h-3 w-3", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M19 9l-7 7-7-7" }) })
3381
3454
  }
3382
3455
  )
@@ -3384,10 +3457,18 @@ function NumberInput({
3384
3457
  ]
3385
3458
  }
3386
3459
  )
3387
- ] }),
3388
- hasError && /* @__PURE__ */ jsx("div", { id: errorId, className: "text-xs text-status-error ml-1", children: errorMessage })
3389
- ] });
3460
+ }
3461
+ );
3390
3462
  }
3463
+ var EyeIcon = /* @__PURE__ */ jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "currentColor", className: "w-5 h-5", "aria-hidden": "true", children: [
3464
+ /* @__PURE__ */ jsx("path", { d: "M12 15a3 3 0 100-6 3 3 0 000 6z" }),
3465
+ /* @__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" })
3466
+ ] });
3467
+ var EyeSlashIcon = /* @__PURE__ */ jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "currentColor", className: "w-5 h-5", "aria-hidden": "true", children: [
3468
+ /* @__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" }),
3469
+ /* @__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" }),
3470
+ /* @__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" })
3471
+ ] });
3391
3472
  function Password({
3392
3473
  value,
3393
3474
  onChange,
@@ -3398,32 +3479,32 @@ function Password({
3398
3479
  name,
3399
3480
  inputStyle,
3400
3481
  style,
3401
- layout,
3482
+ layout = "vertical",
3483
+ size = "md",
3402
3484
  onBlur,
3403
3485
  errorMessage,
3404
- labelColor,
3405
- iconColor
3486
+ required,
3487
+ showIcon,
3488
+ hideIcon
3406
3489
  }) {
3407
- const [passwordVisible, setPasswordVisible] = useState(false);
3490
+ const [visible, setVisible] = useState(false);
3408
3491
  const errorId = useId();
3409
3492
  const hasError = errorMessage != null;
3410
- return /* @__PURE__ */ jsxs(
3411
- "div",
3493
+ return /* @__PURE__ */ jsx(
3494
+ Field,
3412
3495
  {
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",
3418
- {
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
3423
- }
3424
- ),
3425
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col", children: [
3426
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
3496
+ label,
3497
+ htmlFor,
3498
+ errorId,
3499
+ errorMessage,
3500
+ layout,
3501
+ required,
3502
+ children: /* @__PURE__ */ jsxs(
3503
+ "div",
3504
+ {
3505
+ className: `flex items-center ${fieldShell({ size, hasError, disabled, focusWithin: true })}`,
3506
+ style,
3507
+ children: [
3427
3508
  /* @__PURE__ */ jsx(
3428
3509
  "input",
3429
3510
  {
@@ -3432,44 +3513,30 @@ function Password({
3432
3513
  value,
3433
3514
  onChange,
3434
3515
  onBlur,
3435
- type: passwordVisible ? "text" : "password",
3516
+ type: visible ? "text" : "password",
3436
3517
  name,
3437
3518
  id: htmlFor,
3438
3519
  "aria-invalid": hasError || void 0,
3439
3520
  "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 ?? ""
3521
+ placeholder: placeholder ?? "",
3522
+ className: "min-w-0 flex-1 bg-transparent outline-none disabled:cursor-not-allowed placeholder:text-foreground-muted",
3523
+ style: inputStyle
3443
3524
  }
3444
3525
  ),
3445
3526
  /* @__PURE__ */ jsx(
3446
3527
  "button",
3447
3528
  {
3448
3529
  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
- )
3530
+ disabled,
3531
+ className: `flex-shrink-0 ml-2 ${FIELD_SIZE[size].gap} rounded text-foreground-muted hover:text-foreground transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-accent disabled:cursor-not-allowed`,
3532
+ onClick: () => setVisible((v) => !v),
3533
+ "aria-label": visible ? "Hide password" : "Show password",
3534
+ children: visible ? hideIcon ?? EyeSlashIcon : showIcon ?? EyeIcon
3467
3535
  }
3468
3536
  )
3469
- ] }),
3470
- hasError && /* @__PURE__ */ jsx("div", { id: errorId, className: "text-status-error text-xs mt-1", children: errorMessage })
3471
- ] })
3472
- ]
3537
+ ]
3538
+ }
3539
+ )
3473
3540
  }
3474
3541
  );
3475
3542
  }
@@ -3552,6 +3619,96 @@ function Checkbox({
3552
3619
  errorMessage && /* @__PURE__ */ jsx("span", { className: "text-xs text-status-error pl-[26px]", children: errorMessage })
3553
3620
  ] });
3554
3621
  }
3622
+ var DOT_SIZE = {
3623
+ sm: "h-3.5 w-3.5",
3624
+ md: "h-4 w-4",
3625
+ lg: "h-5 w-5"
3626
+ };
3627
+ var TEXT_SIZE = {
3628
+ sm: "text-xs",
3629
+ md: "text-sm",
3630
+ lg: "text-base"
3631
+ };
3632
+ function RadioGroup({
3633
+ options,
3634
+ value,
3635
+ defaultValue,
3636
+ onChange,
3637
+ name,
3638
+ label,
3639
+ orientation = "vertical",
3640
+ size = "md",
3641
+ disabled,
3642
+ required,
3643
+ errorMessage
3644
+ }) {
3645
+ const errorId = useId();
3646
+ const groupId = useId();
3647
+ const hasError = errorMessage != null;
3648
+ return /* @__PURE__ */ jsx(
3649
+ Field,
3650
+ {
3651
+ label,
3652
+ htmlFor: groupId,
3653
+ errorId,
3654
+ errorMessage,
3655
+ required,
3656
+ children: /* @__PURE__ */ jsx(
3657
+ RadioGroupPrimitive.Root,
3658
+ {
3659
+ id: groupId,
3660
+ name,
3661
+ value,
3662
+ defaultValue,
3663
+ onValueChange: onChange,
3664
+ disabled,
3665
+ required,
3666
+ "aria-invalid": hasError || void 0,
3667
+ "aria-describedby": hasError ? errorId : void 0,
3668
+ orientation,
3669
+ className: orientation === "horizontal" ? "flex flex-row flex-wrap gap-5" : "flex flex-col gap-3",
3670
+ children: options.map((opt) => {
3671
+ const itemId = `${groupId}-${opt.value}`;
3672
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2.5", children: [
3673
+ /* @__PURE__ */ jsx(
3674
+ RadioGroupPrimitive.Item,
3675
+ {
3676
+ id: itemId,
3677
+ value: opt.value,
3678
+ disabled: opt.disabled,
3679
+ className: [
3680
+ DOT_SIZE[size],
3681
+ "mt-0.5 flex-shrink-0 rounded-full border bg-surface transition-colors duration-150",
3682
+ "border-border-strong",
3683
+ "hover:border-accent",
3684
+ "data-[state=checked]:border-accent",
3685
+ "focus:outline-none focus-visible:ring-[3px] focus-visible:ring-focus-ring",
3686
+ "disabled:cursor-not-allowed disabled:opacity-50"
3687
+ ].join(" "),
3688
+ children: /* @__PURE__ */ jsx(RadioGroupPrimitive.Indicator, { className: "flex h-full w-full items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "block h-1/2 w-1/2 rounded-full bg-accent" }) })
3689
+ }
3690
+ ),
3691
+ /* @__PURE__ */ jsxs(
3692
+ "label",
3693
+ {
3694
+ htmlFor: itemId,
3695
+ className: [
3696
+ "select-none",
3697
+ opt.disabled ? "cursor-not-allowed opacity-50" : "cursor-pointer"
3698
+ ].join(" "),
3699
+ children: [
3700
+ /* @__PURE__ */ jsx("span", { className: `block ${TEXT_SIZE[size]} text-foreground`, children: opt.label }),
3701
+ opt.description && /* @__PURE__ */ jsx("span", { className: "block text-xs text-foreground-secondary mt-0.5", children: opt.description })
3702
+ ]
3703
+ }
3704
+ )
3705
+ ] }, opt.value);
3706
+ })
3707
+ }
3708
+ )
3709
+ }
3710
+ );
3711
+ }
3555
3712
  function Switch({
3556
3713
  checked = false,
3557
3714
  onChange,
@@ -3589,7 +3746,9 @@ function AutoComplete({
3589
3746
  debounce = 250,
3590
3747
  onItemClick,
3591
3748
  emptyText = "No results found",
3592
- loadingText = "Searching\u2026"
3749
+ loadingText = "Searching\u2026",
3750
+ size = "md",
3751
+ icon
3593
3752
  }) {
3594
3753
  const [term, setTerm] = useState("");
3595
3754
  const [open, setOpen] = useState(false);
@@ -3637,15 +3796,15 @@ function AutoComplete({
3637
3796
  onItemClick?.(item.value);
3638
3797
  setOpen(false);
3639
3798
  };
3640
- return /* @__PURE__ */ jsx("div", { className: "relative flex flex-col items-center justify-center", children: /* @__PURE__ */ jsxs(
3799
+ return /* @__PURE__ */ jsxs(
3641
3800
  "div",
3642
3801
  {
3643
- className: `flex ${layout === "vertical" ? "flex-col" : "flex-row items-center gap-2"}`,
3644
- style: style ?? {},
3802
+ className: `flex ${layout === "vertical" ? "flex-col gap-1.5" : "flex-row items-start gap-3"}`,
3803
+ style,
3645
3804
  children: [
3646
- label && /* @__PURE__ */ jsx("label", { className: "text-sm font-medium ml-1 max-content text-foreground", children: label }),
3647
- /* @__PURE__ */ jsxs(Popover.Root, { open: open && !disabled, onOpenChange: (o) => !disabled && setOpen(o), children: [
3648
- /* @__PURE__ */ jsx(Popover.Anchor, { asChild: true, children: /* @__PURE__ */ jsxs("div", { className: "bg-surface text-foreground flex items-center gap-1 rounded-lg border border-border pr-2 focus-within:border-transparent focus-within:ring-2 focus-within:ring-accent transition-colors", children: [
3805
+ label && /* @__PURE__ */ jsx("label", { className: `text-sm font-medium text-foreground select-none ${layout === "horizontal" ? "mt-2 flex-shrink-0" : ""}`, children: label }),
3806
+ /* @__PURE__ */ jsx("div", { className: "flex flex-col min-w-0 flex-1", children: /* @__PURE__ */ jsxs(Popover.Root, { open: open && !disabled, onOpenChange: (o) => !disabled && setOpen(o), children: [
3807
+ /* @__PURE__ */ jsx(Popover.Anchor, { asChild: true, children: /* @__PURE__ */ jsxs("div", { className: `flex items-center ${fieldShell({ size, disabled, focusWithin: true })}`, children: [
3649
3808
  /* @__PURE__ */ jsx(
3650
3809
  "input",
3651
3810
  {
@@ -3658,8 +3817,8 @@ function AutoComplete({
3658
3817
  onFocus: () => setOpen(true),
3659
3818
  type: "text",
3660
3819
  name,
3661
- className: "bg-transparent focus:outline-none pl-2 h-9 w-56 rounded-lg disabled:cursor-not-allowed",
3662
- style: inputStyle ?? {},
3820
+ className: "min-w-0 flex-1 bg-transparent outline-none disabled:cursor-not-allowed placeholder:text-foreground-muted",
3821
+ style: inputStyle,
3663
3822
  placeholder: placeholder ?? "",
3664
3823
  autoComplete: "off",
3665
3824
  "aria-haspopup": "listbox",
@@ -3668,7 +3827,7 @@ function AutoComplete({
3668
3827
  "aria-busy": loading || void 0
3669
3828
  }
3670
3829
  ),
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" }) })
3830
+ loading ? /* @__PURE__ */ jsx("span", { className: "ml-2 w-4 h-4 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("span", { className: "ml-2 flex-shrink-0 text-foreground-muted", children: icon ?? /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "currentColor", className: "w-4 h-4", "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" }) }) })
3672
3831
  ] }) }),
3673
3832
  /* @__PURE__ */ jsx(Popover.Portal, { children: /* @__PURE__ */ jsx(
3674
3833
  Popover.Content,
@@ -3707,10 +3866,10 @@ function AutoComplete({
3707
3866
  )) })
3708
3867
  }
3709
3868
  ) })
3710
- ] })
3869
+ ] }) })
3711
3870
  ]
3712
3871
  }
3713
- ) });
3872
+ );
3714
3873
  }
3715
3874
  function flattenVisible(items, expanded, depth = 0, out = []) {
3716
3875
  for (const node of items) {
@@ -3745,7 +3904,8 @@ function TreeSelect({
3745
3904
  items = [],
3746
3905
  placeholder = "Select\u2026",
3747
3906
  parentsSelectable = true,
3748
- defaultExpandedKeys = []
3907
+ defaultExpandedKeys = [],
3908
+ size = "md"
3749
3909
  }) {
3750
3910
  const errorId = useId();
3751
3911
  const hasError = errorMessage != null;
@@ -3844,7 +4004,7 @@ function TreeSelect({
3844
4004
  "aria-invalid": hasError || void 0,
3845
4005
  "aria-describedby": hasError ? errorId : void 0,
3846
4006
  disabled,
3847
- className: `flex items-center justify-between h-9 rounded-lg border ${hasError ? "border-status-error" : "border-border"} px-3 cursor-pointer select-none focus:outline-none focus-visible:border-transparent focus-visible:ring-2 focus-visible:ring-accent ${disabled ? "cursor-not-allowed bg-surface-raised text-foreground-muted" : "bg-surface text-foreground"} ${!style?.width ? "min-w-[240px]" : ""}`,
4007
+ className: `flex items-center justify-between cursor-pointer select-none ${!style?.width ? "min-w-[240px]" : ""} ${fieldShell({ size, hasError, disabled })}`,
3848
4008
  children: [
3849
4009
  /* @__PURE__ */ jsx("span", { className: "text-sm truncate text-left", children: selectedNode ? selectedNode.label : /* @__PURE__ */ jsx("span", { className: "text-foreground-muted", children: placeholder }) }),
3850
4010
  /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, className: `h-4 w-4 flex-shrink-0 transition-transform duration-200 ${open ? "rotate-180" : ""}`, "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M19 9l-7 7-7-7" }) })
@@ -3971,111 +4131,162 @@ function TreeNodeRow({
3971
4131
  }
3972
4132
  );
3973
4133
  }
4134
+ function formatBytes(bytes) {
4135
+ if (bytes === 0) return "0 B";
4136
+ const units = ["B", "KB", "MB", "GB"];
4137
+ const i = Math.floor(Math.log(bytes) / Math.log(1024));
4138
+ return `${(bytes / Math.pow(1024, i)).toFixed(i === 0 ? 0 : 1)} ${units[i]}`;
4139
+ }
4140
+ var UploadGlyph = /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 1.5, className: "w-6 h-6", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M12 16V4m0 0L8 8m4-4l4 4M4 16v2a2 2 0 002 2h12a2 2 0 002-2v-2" }) });
4141
+ var FileGlyph = /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 1.5, className: "w-5 h-5", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M14 3v4a1 1 0 001 1h4M5 3h9l5 5v11a2 2 0 01-2 2H5a2 2 0 01-2-2V5a2 2 0 012-2z" }) });
3974
4142
  function FileInput({
3975
4143
  allowMultiple = false,
3976
4144
  onChange,
3977
4145
  name,
3978
- accept = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,.xlsx"
4146
+ htmlFor,
4147
+ label,
4148
+ accept,
4149
+ prompt = "Click to upload or drag and drop",
4150
+ hint,
4151
+ maxSize,
4152
+ errorMessage,
4153
+ disabled,
4154
+ required,
4155
+ icon
3979
4156
  }) {
3980
- const fileInput = useRef(null);
4157
+ const inputRef = useRef(null);
4158
+ const errorId = useId();
3981
4159
  const [files, setFiles] = useState([]);
4160
+ const [dragging, setDragging] = useState(false);
4161
+ const [sizeError, setSizeError] = useState(null);
4162
+ const effectiveError = errorMessage ?? sizeError ?? void 0;
3982
4163
  const openPicker = () => {
3983
- fileInput.current?.click();
4164
+ if (!disabled) inputRef.current?.click();
3984
4165
  };
3985
- const handleFiles = (list) => {
4166
+ const commit = (list) => {
4167
+ if (maxSize != null) {
4168
+ const tooBig = list.find((f) => f.size > maxSize);
4169
+ if (tooBig) {
4170
+ setSizeError(`"${tooBig.name}" exceeds the ${formatBytes(maxSize)} limit`);
4171
+ return;
4172
+ }
4173
+ }
4174
+ setSizeError(null);
3986
4175
  setFiles(list);
3987
- onChange?.({ target: { files: list } });
4176
+ onChange?.({ target: { files: list, name, id: htmlFor ?? name } });
3988
4177
  };
3989
4178
  const onDrop = (e) => {
3990
4179
  e.preventDefault();
3991
- const fileList = [];
3992
- if (e.dataTransfer.items) {
3993
- for (let i = 0; i < e.dataTransfer.items.length; i++) {
3994
- if (e.dataTransfer.items[i].kind === "file") {
3995
- const f = e.dataTransfer.items[i].getAsFile();
3996
- if (f) fileList.push(f);
3997
- }
3998
- }
3999
- } else {
4000
- for (let i = 0; i < e.dataTransfer.files.length; i++) {
4001
- fileList.push(e.dataTransfer.files[i]);
4002
- }
4003
- }
4004
- handleFiles(fileList);
4005
- };
4006
- const localOnChange = (e) => {
4007
- handleFiles(Array.from(e.target.files ?? []));
4180
+ setDragging(false);
4181
+ if (disabled) return;
4182
+ const dropped = Array.from(e.dataTransfer.files ?? []);
4183
+ commit(allowMultiple ? dropped : dropped.slice(0, 1));
4008
4184
  };
4009
- const removeFile = (e) => {
4010
- e.stopPropagation();
4011
- setFiles([]);
4012
- onChange?.({ target: { files: [], name, id: name, value: "" } });
4013
- if (fileInput.current) fileInput.current.value = "";
4185
+ const removeFile = (idx) => {
4186
+ const next = files.filter((_, i) => i !== idx);
4187
+ setFiles(next);
4188
+ setSizeError(null);
4189
+ onChange?.({ target: { files: next, name, id: htmlFor ?? name, value: "" } });
4190
+ if (next.length === 0 && inputRef.current) inputRef.current.value = "";
4014
4191
  };
4015
- return (
4016
- // Dropzone is keyboard-activatable: role="button", focusable via
4017
- // tabIndex, and Space/Enter trigger the file picker. Without these
4018
- // a keyboard-only user could not upload a file.
4019
- /* @__PURE__ */ jsxs(
4020
- "div",
4021
- {
4022
- role: "button",
4023
- tabIndex: 0,
4024
- "aria-label": "Upload file \u2014 click or drop",
4025
- onClick: openPicker,
4026
- onKeyDown: (e) => {
4027
- if (e.key === "Enter" || e.key === " ") {
4028
- e.preventDefault();
4029
- openPicker();
4030
- }
4031
- },
4032
- className: "border-2 border-dashed border-border hover:border-accent w-full h-full rounded-md transition-colors duration-200 cursor-pointer focus:outline-none focus-visible:ring-2 focus-visible:ring-accent text-foreground-secondary hover:text-foreground",
4033
- onDragOver: (e) => e.preventDefault(),
4034
- onDrop,
4035
- children: [
4036
- /* @__PURE__ */ jsx(
4037
- "input",
4038
- {
4039
- id: name,
4040
- name,
4041
- onChange: localOnChange,
4042
- ref: fileInput,
4043
- hidden: true,
4044
- type: "file",
4045
- accept,
4046
- multiple: allowMultiple
4047
- }
4048
- ),
4049
- files.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col h-full items-center justify-center gap-2", children: [
4050
- /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "currentColor", className: "w-16 h-16", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { fillRule: "evenodd", d: "M11.47 2.47a.75.75 0 011.06 0l4.5 4.5a.75.75 0 01-1.06 1.06l-3.22-3.22V16.5a.75.75 0 01-1.5 0V4.81L8.03 8.03a.75.75 0 01-1.06-1.06l4.5-4.5zM3 15.75a.75.75 0 01.75.75v2.25a1.5 1.5 0 001.5 1.5h13.5a1.5 1.5 0 001.5-1.5V16.5a.75.75 0 011.5 0v2.25a3 3 0 01-3 3H5.25a3 3 0 01-3-3V16.5a.75.75 0 01.75-.75z", clipRule: "evenodd" }) }),
4051
- /* @__PURE__ */ jsx("div", { className: "text-sm", children: "Click or Drop a file" })
4052
- ] }) : /* @__PURE__ */ jsx("div", { className: "flex gap-3 items-center justify-center w-full h-full p-3", children: files.map((file, id) => /* @__PURE__ */ jsxs(
4053
- "div",
4054
- {
4055
- className: "text-xs flex flex-col items-center w-20 h-24 text-center bg-surface-raised text-foreground p-4 rounded-md relative",
4056
- children: [
4057
- /* @__PURE__ */ jsx(
4058
- "button",
4059
- {
4060
- type: "button",
4061
- onClick: removeFile,
4062
- className: "bg-status-error rounded-full w-4 h-4 absolute right-[-5px] top-[-5px] cursor-pointer flex items-center justify-center focus:outline-none focus-visible:ring-2 focus-visible:ring-accent",
4063
- "aria-label": "Remove file",
4064
- children: /* @__PURE__ */ jsx("svg", { width: "10", height: "10", viewBox: "0 0 20 20", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M15 5L5 15M5 5l10 10", stroke: "#fff", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) })
4065
- }
4066
- ),
4067
- /* @__PURE__ */ jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "currentColor", className: "w-10 h-10", "aria-hidden": "true", children: [
4068
- /* @__PURE__ */ jsx("path", { fillRule: "evenodd", d: "M5.625 1.5c-1.036 0-1.875.84-1.875 1.875v17.25c0 1.035.84 1.875 1.875 1.875h12.75c1.035 0 1.875-.84 1.875-1.875V12.75A3.75 3.75 0 0016.5 9h-1.875a1.875 1.875 0 01-1.875-1.875V5.25A3.75 3.75 0 009 1.5H5.625z", clipRule: "evenodd" }),
4069
- /* @__PURE__ */ jsx("path", { d: "M12.971 1.816A5.23 5.23 0 0114.25 5.25v1.875c0 .207.168.375.375.375H16.5a5.23 5.23 0 013.434 1.279 9.768 9.768 0 00-6.963-6.963z" })
4070
- ] }),
4071
- /* @__PURE__ */ jsx("span", { className: "text-ellipsis whitespace-nowrap overflow-hidden w-full", children: file.name })
4072
- ]
4192
+ return /* @__PURE__ */ jsxs(
4193
+ Field,
4194
+ {
4195
+ label,
4196
+ htmlFor,
4197
+ errorId,
4198
+ errorMessage: effectiveError,
4199
+ required,
4200
+ children: [
4201
+ /* @__PURE__ */ jsxs(
4202
+ "div",
4203
+ {
4204
+ role: "button",
4205
+ tabIndex: disabled ? -1 : 0,
4206
+ "aria-label": typeof prompt === "string" ? prompt : "Upload file",
4207
+ "aria-disabled": disabled || void 0,
4208
+ "aria-invalid": effectiveError != null || void 0,
4209
+ "aria-describedby": effectiveError != null ? errorId : void 0,
4210
+ onClick: openPicker,
4211
+ onKeyDown: (e) => {
4212
+ if (disabled) return;
4213
+ if (e.key === "Enter" || e.key === " ") {
4214
+ e.preventDefault();
4215
+ openPicker();
4216
+ }
4073
4217
  },
4074
- `${id}${file.name}`
4075
- )) })
4076
- ]
4077
- }
4078
- )
4218
+ onDragOver: (e) => {
4219
+ e.preventDefault();
4220
+ if (!disabled) setDragging(true);
4221
+ },
4222
+ onDragLeave: () => setDragging(false),
4223
+ onDrop,
4224
+ className: [
4225
+ "group flex flex-col items-center justify-center gap-3 w-full rounded-xl border border-dashed px-6 py-8 text-center",
4226
+ "transition-colors duration-150 focus:outline-none focus-visible:ring-[3px] focus-visible:ring-focus-ring",
4227
+ disabled ? "border-border bg-surface-raised cursor-not-allowed opacity-60" : effectiveError != null ? "border-status-error bg-surface cursor-pointer" : dragging ? "border-accent bg-surface-raised cursor-copy" : "border-border bg-surface hover:border-border-strong cursor-pointer"
4228
+ ].join(" "),
4229
+ children: [
4230
+ /* @__PURE__ */ jsx(
4231
+ "input",
4232
+ {
4233
+ id: htmlFor ?? name,
4234
+ name,
4235
+ onChange: (e) => commit(Array.from(e.target.files ?? [])),
4236
+ ref: inputRef,
4237
+ hidden: true,
4238
+ type: "file",
4239
+ accept,
4240
+ multiple: allowMultiple,
4241
+ disabled
4242
+ }
4243
+ ),
4244
+ /* @__PURE__ */ jsx(
4245
+ "span",
4246
+ {
4247
+ className: [
4248
+ "flex h-11 w-11 items-center justify-center rounded-full transition-colors duration-150",
4249
+ dragging ? "bg-accent text-accent-fg" : "bg-surface-raised text-foreground-secondary group-hover:text-foreground"
4250
+ ].join(" "),
4251
+ children: icon ?? UploadGlyph
4252
+ }
4253
+ ),
4254
+ /* @__PURE__ */ jsxs("div", { className: "space-y-0.5", children: [
4255
+ /* @__PURE__ */ jsx("div", { className: "text-sm font-medium text-foreground", children: dragging ? "Drop to upload" : prompt }),
4256
+ hint && /* @__PURE__ */ jsx("div", { className: "text-xs text-foreground-muted", children: hint })
4257
+ ] })
4258
+ ]
4259
+ }
4260
+ ),
4261
+ files.length > 0 && /* @__PURE__ */ jsx("ul", { className: "mt-3 flex flex-col gap-2", children: files.map((file, idx) => /* @__PURE__ */ jsxs(
4262
+ "li",
4263
+ {
4264
+ className: "flex items-center gap-3 rounded-lg border border-border bg-surface px-3 py-2",
4265
+ children: [
4266
+ /* @__PURE__ */ jsx("span", { className: "flex-shrink-0 text-foreground-muted", children: FileGlyph }),
4267
+ /* @__PURE__ */ jsxs("span", { className: "flex-1 min-w-0", children: [
4268
+ /* @__PURE__ */ jsx("span", { className: "block text-sm text-foreground truncate", children: file.name }),
4269
+ /* @__PURE__ */ jsx("span", { className: "block text-xs text-foreground-muted", children: formatBytes(file.size) })
4270
+ ] }),
4271
+ /* @__PURE__ */ jsx(
4272
+ "button",
4273
+ {
4274
+ type: "button",
4275
+ onClick: (e) => {
4276
+ e.stopPropagation();
4277
+ removeFile(idx);
4278
+ },
4279
+ "aria-label": `Remove ${file.name}`,
4280
+ className: "flex-shrink-0 w-7 h-7 inline-flex items-center justify-center rounded-md text-foreground-muted hover:text-status-error hover:bg-surface-raised transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-accent",
4281
+ children: /* @__PURE__ */ jsx("svg", { width: "14", height: "14", viewBox: "0 0 20 20", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M15 5L5 15M5 5l10 10", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", strokeLinejoin: "round" }) })
4282
+ }
4283
+ )
4284
+ ]
4285
+ },
4286
+ `${idx}-${file.name}`
4287
+ )) })
4288
+ ]
4289
+ }
4079
4290
  );
4080
4291
  }
4081
4292
  var MONTH_NAMES = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
@@ -4130,7 +4341,8 @@ function DatePicker({
4130
4341
  style,
4131
4342
  format = defaultFormat,
4132
4343
  weekStartsOn = 0,
4133
- clearable = true
4344
+ clearable = true,
4345
+ size = "md"
4134
4346
  }) {
4135
4347
  const errorId = useId();
4136
4348
  const hasError = errorMessage != null;
@@ -4233,7 +4445,7 @@ function DatePicker({
4233
4445
  "aria-describedby": hasError ? errorId : void 0,
4234
4446
  "aria-haspopup": "dialog",
4235
4447
  "aria-expanded": open,
4236
- className: `flex items-center justify-between h-9 rounded-lg border px-3 cursor-pointer select-none focus:outline-none focus-visible:border-transparent focus-visible:ring-2 focus-visible:ring-accent ${hasError ? "border-status-error" : "border-border"} ${disabled ? "cursor-not-allowed bg-surface-raised text-foreground-muted" : "bg-surface text-foreground"} ${!style?.width ? "min-w-[200px]" : ""}`,
4448
+ className: `flex items-center justify-between cursor-pointer select-none ${!style?.width ? "min-w-[200px]" : ""} ${fieldShell({ size, hasError, disabled })}`,
4237
4449
  children: [
4238
4450
  /* @__PURE__ */ jsx("span", { className: `text-sm truncate ${displayValue ? "" : "text-foreground-muted"}`, children: displayValue || placeholder }),
4239
4451
  /* @__PURE__ */ jsx(CalendarIcon, {})
@@ -4418,7 +4630,378 @@ function ChevronLeft() {
4418
4630
  function ChevronRight3() {
4419
4631
  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" }) });
4420
4632
  }
4633
+ var LINE_HEIGHT_PX = 21;
4634
+ function TextArea({
4635
+ value,
4636
+ onChange,
4637
+ onBlur,
4638
+ disabled,
4639
+ label,
4640
+ htmlFor,
4641
+ placeholder,
4642
+ name,
4643
+ layout = "vertical",
4644
+ size = "md",
4645
+ rows = 4,
4646
+ autoGrow = false,
4647
+ maxRows = 12,
4648
+ maxLength,
4649
+ showCount = false,
4650
+ resize,
4651
+ errorMessage,
4652
+ required,
4653
+ style,
4654
+ inputStyle
4655
+ }) {
4656
+ const errorId = useId();
4657
+ const hasError = errorMessage != null;
4658
+ const ref = useRef(null);
4659
+ useLayoutEffect(() => {
4660
+ if (!autoGrow) return;
4661
+ const el = ref.current;
4662
+ if (!el) return;
4663
+ el.style.height = "auto";
4664
+ const maxH = maxRows * LINE_HEIGHT_PX + 16;
4665
+ el.style.height = `${Math.min(el.scrollHeight, maxH)}px`;
4666
+ el.style.overflowY = el.scrollHeight > maxH ? "auto" : "hidden";
4667
+ }, [value, autoGrow, maxRows]);
4668
+ const count = typeof value === "string" ? value.length : 0;
4669
+ const resizeClass = (resize ?? (autoGrow ? "none" : "vertical")) === "none" ? "resize-none" : (resize ?? "vertical") === "horizontal" ? "resize-x" : (resize ?? "vertical") === "both" ? "resize" : "resize-y";
4670
+ return /* @__PURE__ */ jsxs(
4671
+ Field,
4672
+ {
4673
+ label,
4674
+ htmlFor,
4675
+ errorId,
4676
+ errorMessage,
4677
+ layout,
4678
+ required,
4679
+ children: [
4680
+ /* @__PURE__ */ jsx(
4681
+ "textarea",
4682
+ {
4683
+ ref,
4684
+ disabled,
4685
+ value,
4686
+ onChange,
4687
+ onBlur,
4688
+ name,
4689
+ id: htmlFor,
4690
+ rows,
4691
+ maxLength,
4692
+ placeholder: placeholder ?? "",
4693
+ "aria-invalid": hasError || void 0,
4694
+ "aria-describedby": hasError ? errorId : void 0,
4695
+ className: `${fieldShell({ size, hasError, disabled, sized: false })} px-3 py-2 leading-normal ${resizeClass}`,
4696
+ style: { ...style, ...inputStyle }
4697
+ }
4698
+ ),
4699
+ showCount && /* @__PURE__ */ jsx("div", { className: "mt-1 text-right text-xs text-foreground-muted tabular-nums", children: maxLength != null ? `${count} / ${maxLength}` : count })
4700
+ ]
4701
+ }
4702
+ );
4703
+ }
4704
+ var SIZE = {
4705
+ sm: { h: "h-control-sm", text: "text-xs", pad: "px-2.5" },
4706
+ md: { h: "h-control-md", text: "text-sm", pad: "px-3.5" },
4707
+ lg: { h: "h-control-lg", text: "text-sm", pad: "px-4" }
4708
+ };
4709
+ function SegmentedControl({
4710
+ options,
4711
+ value,
4712
+ defaultValue,
4713
+ onChange,
4714
+ size = "md",
4715
+ fullWidth = false,
4716
+ disabled,
4717
+ "aria-label": ariaLabel
4718
+ }) {
4719
+ const sz = SIZE[size];
4720
+ return /* @__PURE__ */ jsx(
4721
+ ToggleGroup.Root,
4722
+ {
4723
+ type: "single",
4724
+ value,
4725
+ defaultValue,
4726
+ onValueChange: (v) => {
4727
+ if (v) onChange?.(v);
4728
+ },
4729
+ disabled,
4730
+ "aria-label": ariaLabel,
4731
+ className: [
4732
+ "inline-flex items-center gap-1 rounded-lg border border-border bg-surface-raised p-1",
4733
+ sz.h,
4734
+ fullWidth ? "flex w-full" : "",
4735
+ disabled ? "opacity-60 cursor-not-allowed" : ""
4736
+ ].filter(Boolean).join(" "),
4737
+ children: options.map((opt) => /* @__PURE__ */ jsxs(
4738
+ ToggleGroup.Item,
4739
+ {
4740
+ value: opt.value,
4741
+ disabled: opt.disabled,
4742
+ className: [
4743
+ "inline-flex items-center justify-center gap-1.5 rounded-md select-none whitespace-nowrap",
4744
+ "transition-colors duration-150 h-full",
4745
+ sz.text,
4746
+ sz.pad,
4747
+ fullWidth ? "flex-1" : "",
4748
+ // Resting: muted text, transparent. Hover lifts the text.
4749
+ "text-foreground-secondary hover:text-foreground",
4750
+ // Active: surface-white pill + accent text + subtle shadow.
4751
+ "data-[state=on]:bg-surface data-[state=on]:text-accent data-[state=on]:shadow-sm",
4752
+ "focus:outline-none focus-visible:ring-[3px] focus-visible:ring-focus-ring",
4753
+ "disabled:opacity-40 disabled:cursor-not-allowed"
4754
+ ].filter(Boolean).join(" "),
4755
+ children: [
4756
+ opt.icon && /* @__PURE__ */ jsx("span", { className: "flex-shrink-0", children: opt.icon }),
4757
+ opt.label
4758
+ ]
4759
+ },
4760
+ opt.value
4761
+ ))
4762
+ }
4763
+ );
4764
+ }
4765
+ var TRACK_H = { sm: "h-1", md: "h-1.5", lg: "h-2" };
4766
+ var THUMB = { sm: "h-3.5 w-3.5", md: "h-4 w-4", lg: "h-5 w-5" };
4767
+ var toArray = (v) => v == null ? void 0 : Array.isArray(v) ? v : [v];
4768
+ function Slider({
4769
+ value,
4770
+ defaultValue,
4771
+ onChange,
4772
+ onChangeEnd,
4773
+ min = 0,
4774
+ max = 100,
4775
+ step = 1,
4776
+ label,
4777
+ showValue = false,
4778
+ formatValue = (n) => String(n),
4779
+ marks,
4780
+ tooltip = false,
4781
+ size = "md",
4782
+ disabled,
4783
+ errorMessage,
4784
+ name,
4785
+ htmlFor
4786
+ }) {
4787
+ const errorId = useId();
4788
+ const hasError = errorMessage != null;
4789
+ const isRange = Array.isArray(value ?? defaultValue);
4790
+ const [internal, setInternal] = useState(
4791
+ () => toArray(value) ?? toArray(defaultValue) ?? [min]
4792
+ );
4793
+ const current = toArray(value) ?? internal;
4794
+ const [dragging, setDragging] = useState(false);
4795
+ const emit = (arr) => {
4796
+ setInternal(arr);
4797
+ const next = isRange ? [arr[0], arr[1]] : arr[0];
4798
+ onChange?.(next);
4799
+ };
4800
+ const valueText = current.map(formatValue).join(" \u2013 ");
4801
+ return /* @__PURE__ */ jsxs(Field, { label: void 0, errorId, errorMessage, children: [
4802
+ (label || showValue) && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-2", children: [
4803
+ label && /* @__PURE__ */ jsx("label", { htmlFor, className: "text-sm font-medium text-foreground select-none", children: label }),
4804
+ showValue && /* @__PURE__ */ jsx("span", { className: "text-sm text-foreground-secondary tabular-nums", children: valueText })
4805
+ ] }),
4806
+ /* @__PURE__ */ jsxs(
4807
+ SliderPrimitive.Root,
4808
+ {
4809
+ id: htmlFor,
4810
+ name,
4811
+ value: toArray(value),
4812
+ defaultValue: toArray(defaultValue),
4813
+ min,
4814
+ max,
4815
+ step,
4816
+ disabled,
4817
+ "aria-invalid": hasError || void 0,
4818
+ "aria-describedby": hasError ? errorId : void 0,
4819
+ onValueChange: (v) => {
4820
+ emit(v);
4821
+ setDragging(true);
4822
+ },
4823
+ onValueCommit: (v) => {
4824
+ setDragging(false);
4825
+ onChangeEnd?.(isRange ? [v[0], v[1]] : v[0]);
4826
+ },
4827
+ className: "relative flex items-center select-none touch-none w-full h-5",
4828
+ children: [
4829
+ /* @__PURE__ */ jsx(SliderPrimitive.Track, { className: `relative grow rounded-full bg-surface-raised border border-border ${TRACK_H[size]}`, children: /* @__PURE__ */ jsx(SliderPrimitive.Range, { className: "absolute h-full rounded-full bg-accent" }) }),
4830
+ current.map((_, i) => /* @__PURE__ */ jsx(
4831
+ SliderPrimitive.Thumb,
4832
+ {
4833
+ className: `group relative block ${THUMB[size]} rounded-full border-2 border-accent bg-surface shadow-sm transition-shadow
4834
+ focus:outline-none focus-visible:ring-[3px] focus-visible:ring-focus-ring
4835
+ disabled:cursor-not-allowed`,
4836
+ children: tooltip && /* @__PURE__ */ jsx(
4837
+ "span",
4838
+ {
4839
+ className: `pointer-events-none absolute -top-8 left-1/2 -translate-x-1/2 rounded-md bg-foreground px-1.5 py-0.5 text-xs text-background tabular-nums whitespace-nowrap transition-opacity ${dragging ? "opacity-100" : "opacity-0 group-hover:opacity-100 group-focus-visible:opacity-100"}`,
4840
+ children: formatValue(current[i])
4841
+ }
4842
+ )
4843
+ },
4844
+ i
4845
+ ))
4846
+ ]
4847
+ }
4848
+ ),
4849
+ marks && marks.length > 0 && /* @__PURE__ */ jsx("div", { className: "relative mt-2 h-4", children: marks.map((m) => {
4850
+ const pct = (m.value - min) / (max - min) * 100;
4851
+ return /* @__PURE__ */ jsx(
4852
+ "span",
4853
+ {
4854
+ className: "absolute -translate-x-1/2 text-xs text-foreground-muted tabular-nums",
4855
+ style: { left: `${pct}%` },
4856
+ children: m.label ?? m.value
4857
+ },
4858
+ m.value
4859
+ );
4860
+ }) })
4861
+ ] });
4862
+ }
4863
+ function TagsInput({
4864
+ value,
4865
+ defaultValue,
4866
+ onChange,
4867
+ label,
4868
+ htmlFor,
4869
+ name,
4870
+ placeholder = "Add and press Enter",
4871
+ layout = "vertical",
4872
+ size = "md",
4873
+ disabled,
4874
+ errorMessage,
4875
+ required,
4876
+ maxTags,
4877
+ dedupe = true,
4878
+ validate,
4879
+ separators = ["Enter", ","]
4880
+ }) {
4881
+ const errorId = useId();
4882
+ const inputRef = useRef(null);
4883
+ const [internal, setInternal] = useState(defaultValue ?? []);
4884
+ const [draft, setDraft] = useState("");
4885
+ const [localError, setLocalError] = useState(null);
4886
+ const tags = value ?? internal;
4887
+ const hasError = errorMessage != null || localError != null;
4888
+ const errorText = errorMessage ?? localError ?? void 0;
4889
+ const commitTags = (next) => {
4890
+ setInternal(next);
4891
+ onChange?.(next);
4892
+ };
4893
+ const addTag = (raw) => {
4894
+ const tag = raw.trim();
4895
+ if (!tag) return false;
4896
+ if (maxTags != null && tags.length >= maxTags) return false;
4897
+ if (dedupe && tags.some((t) => t.toLowerCase() === tag.toLowerCase())) {
4898
+ setLocalError(`"${tag}" is already added`);
4899
+ return false;
4900
+ }
4901
+ if (validate) {
4902
+ const res = validate(tag, tags);
4903
+ if (res !== true) {
4904
+ setLocalError(typeof res === "string" ? res : `"${tag}" is not valid`);
4905
+ return false;
4906
+ }
4907
+ }
4908
+ setLocalError(null);
4909
+ commitTags([...tags, tag]);
4910
+ return true;
4911
+ };
4912
+ const removeTag = (idx) => {
4913
+ commitTags(tags.filter((_, i) => i !== idx));
4914
+ setLocalError(null);
4915
+ };
4916
+ const onKeyDown = (e) => {
4917
+ if (separators.includes(e.key)) {
4918
+ e.preventDefault();
4919
+ if (addTag(draft)) setDraft("");
4920
+ } else if (e.key === "Backspace" && draft === "" && tags.length > 0) {
4921
+ removeTag(tags.length - 1);
4922
+ }
4923
+ };
4924
+ const onPaste = (e) => {
4925
+ const text = e.clipboardData.getData("text");
4926
+ const parts = text.split(/[\n,;]+/).map((p) => p.trim()).filter(Boolean);
4927
+ if (parts.length > 1) {
4928
+ e.preventDefault();
4929
+ let added = false;
4930
+ for (const p of parts) added = addTag(p) || added;
4931
+ if (added) setDraft("");
4932
+ }
4933
+ };
4934
+ const atMax = maxTags != null && tags.length >= maxTags;
4935
+ return /* @__PURE__ */ jsx(
4936
+ Field,
4937
+ {
4938
+ label,
4939
+ htmlFor,
4940
+ errorId,
4941
+ errorMessage: errorText,
4942
+ layout,
4943
+ required,
4944
+ children: /* @__PURE__ */ jsxs(
4945
+ "div",
4946
+ {
4947
+ className: `flex flex-wrap items-center gap-1.5 min-h-[36px] ${fieldShell({ size, hasError, disabled, focusWithin: true, sized: false })} px-2 py-1.5`,
4948
+ onClick: () => inputRef.current?.focus(),
4949
+ children: [
4950
+ tags.map((tag, idx) => /* @__PURE__ */ jsxs(
4951
+ "span",
4952
+ {
4953
+ className: "inline-flex items-center gap-1 rounded-md bg-surface-raised text-foreground text-xs pl-2 pr-1 py-0.5 border border-border",
4954
+ children: [
4955
+ tag,
4956
+ /* @__PURE__ */ jsx(
4957
+ "button",
4958
+ {
4959
+ type: "button",
4960
+ disabled,
4961
+ onClick: (e) => {
4962
+ e.stopPropagation();
4963
+ removeTag(idx);
4964
+ },
4965
+ "aria-label": `Remove ${tag}`,
4966
+ className: "inline-flex items-center justify-center w-4 h-4 rounded text-foreground-muted hover:text-status-error hover:bg-surface transition-colors focus:outline-none focus-visible:ring-1 focus-visible:ring-accent",
4967
+ children: /* @__PURE__ */ jsx("svg", { width: "10", height: "10", viewBox: "0 0 20 20", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M15 5L5 15M5 5l10 10", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round" }) })
4968
+ }
4969
+ )
4970
+ ]
4971
+ },
4972
+ `${tag}-${idx}`
4973
+ )),
4974
+ /* @__PURE__ */ jsx(
4975
+ "input",
4976
+ {
4977
+ ref: inputRef,
4978
+ id: htmlFor,
4979
+ name,
4980
+ disabled: disabled || atMax,
4981
+ value: draft,
4982
+ onChange: (e) => {
4983
+ setDraft(e.target.value);
4984
+ if (localError) setLocalError(null);
4985
+ },
4986
+ onKeyDown,
4987
+ onPaste,
4988
+ onBlur: () => {
4989
+ if (addTag(draft)) setDraft("");
4990
+ },
4991
+ placeholder: atMax ? "" : tags.length === 0 ? placeholder : "",
4992
+ "aria-invalid": hasError || void 0,
4993
+ "aria-describedby": hasError ? errorId : void 0,
4994
+ autoComplete: "off",
4995
+ className: "flex-1 min-w-[6rem] bg-transparent outline-none text-sm placeholder:text-foreground-muted disabled:cursor-not-allowed"
4996
+ }
4997
+ )
4998
+ ]
4999
+ }
5000
+ )
5001
+ }
5002
+ );
5003
+ }
4421
5004
 
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 };
5005
+ export { AppShell, AutoComplete, Avatar, Box, Button, Catalog, CatalogCarousel, CatalogGrid, Checkbox, ContextMenu, Drawer, Dropdown, FadingBase, Field, FileInput, Flex, Grid2 as Grid, GridCard, icons_default as Icon, IconButton, List2 as List, LoadingSpinner, Modal, NotificationProvider, NumberInput, OpaqueGridCard, Password, Portal, RadioGroup, ScalableContainer, SearchInput_default as SearchInput, SegmentedControl, Sidebar, SkeletonBox, SkeletonCard, SkeletonCircle, SkeletonText, Slider, Switch, Table, Tabs, TagsInput, DatePicker as Temporal, TextArea, TextInput, ThemeProvider, ThemeSwitch, ToggleButton, Tooltip, TooltipProvider, TopBar, Tree, TreeSelect, Typography, Wizard, fieldShell, useNotification };
4423
5006
  //# sourceMappingURL=index.js.map
4424
5007
  //# sourceMappingURL=index.js.map