@geomak/ui 5.5.3 → 5.7.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
@@ -75,7 +75,7 @@ var FleetIcon = ({ color = "#fff", size = 30 }) => /* @__PURE__ */ jsxRuntime.js
75
75
  /* @__PURE__ */ jsxRuntime.jsx("defs", { children: /* @__PURE__ */ jsxRuntime.jsx("clipPath", { id: "clip0_327_836", children: /* @__PURE__ */ jsxRuntime.jsx("rect", { width: "30", height: "30", fill: color }) }) })
76
76
  ] });
77
77
  var Performance = ({ color = "#fff", size = 30 }) => /* @__PURE__ */ jsxRuntime.jsx("svg", { width: size, height: size, viewBox: "0 0 30 30", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M5.64 25.515H24.36C26.0709 23.3842 27.0024 20.7327 27 18C27 11.385 21.63 6 15 6C8.37 6 3 11.385 3 18C3 20.85 3.99 23.445 5.64 25.515ZM13.5 9C13.5 8.175 14.175 7.5 15 7.5C15.825 7.5 16.5 8.175 16.5 9C16.5 9.84 15.825 10.5 15 10.5C14.175 10.5 13.5 9.84 13.5 9ZM6 12C6 11.175 6.675 10.5 7.5 10.5C8.325 10.5 9 11.175 9 12C9 12.84 8.325 13.5 7.5 13.5C6.675 13.5 6 12.84 6 12ZM12.78 17.1C14.04 15.855 22.545 11.85 22.545 11.85C22.545 11.85 18.555 20.37 17.31 21.615C16.05 22.875 14.04 22.875 12.78 21.615C12.1819 21.016 11.8459 20.204 11.8459 19.3575C11.8459 18.511 12.1819 17.699 12.78 17.1ZM4.5 19.5C4.5 18.675 5.175 18 6 18C6.825 18 7.5 18.675 7.5 19.5C7.5 20.34 6.825 21 6 21C5.175 21 4.5 20.34 4.5 19.5ZM13.5 19.5C13.5 18.675 14.175 18 15 18C15.825 18 16.5 18.675 16.5 19.5C16.5 20.34 15.825 21 15 21C14.175 21 13.5 20.34 13.5 19.5ZM22.5 19.5C22.5 18.675 23.175 18 24 18C24.825 18 25.5 18.675 25.5 19.5C25.5 20.34 24.825 21 24 21C23.175 21 22.5 20.34 22.5 19.5Z", fill: color }) });
78
- var Map = ({ color = "#fff", size = 30 }) => /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: size, height: size, viewBox: "0 0 34 30", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
78
+ var Map2 = ({ color = "#fff", size = 30 }) => /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: size, height: size, viewBox: "0 0 34 30", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
79
79
  /* @__PURE__ */ jsxRuntime.jsx("g", { clipPath: "url(#clip0_327_830)", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M16.875 0C12.7975 0 9.49219 3.30527 9.49219 7.38281C9.49219 10.6793 14.3174 16.6875 16.166 18.8684C16.5404 19.3102 17.2102 19.3102 17.584 18.8684C19.4326 16.6875 24.2578 10.6793 24.2578 7.38281C24.2578 3.30527 20.9525 0 16.875 0ZM16.875 9.84375C15.5156 9.84375 14.4141 8.74219 14.4141 7.38281C14.4141 6.02344 15.5156 4.92188 16.875 4.92188C18.2344 4.92188 19.3359 6.02344 19.3359 7.38281C19.3359 8.74219 18.2344 9.84375 16.875 9.84375ZM1.17891 12.6533C0.830964 12.7925 0.532694 13.0327 0.322564 13.343C0.112435 13.6533 8.33325e-05 14.0194 0 14.3941L0 29.0613C0 29.7246 0.669727 30.1781 1.28555 29.932L9.375 26.25V12.593C8.85703 11.6566 8.4334 10.7449 8.12988 9.87305L1.17891 12.6533ZM16.875 21.0744C16.0506 21.0744 15.2707 20.7123 14.7357 20.0807C13.5838 18.7213 12.3586 17.1732 11.25 15.5854V26.2494L22.5 29.9994V15.5859C21.3914 17.1732 20.1668 18.7219 19.0143 20.0812C18.4793 20.7123 17.6994 21.0744 16.875 21.0744ZM32.4645 9.44297L24.375 13.125V30L32.5711 26.7217C32.9191 26.5826 33.2174 26.3424 33.4275 26.0321C33.6377 25.7218 33.75 25.3556 33.75 24.9809V10.3137C33.75 9.65039 33.0803 9.19688 32.4645 9.44297Z", fill: color }) }),
80
80
  /* @__PURE__ */ jsxRuntime.jsx("defs", { children: /* @__PURE__ */ jsxRuntime.jsx("clipPath", { id: "clip0_327_830", children: /* @__PURE__ */ jsxRuntime.jsx("rect", { width: "33.75", height: "30", fill: color }) }) })
81
81
  ] });
@@ -162,7 +162,7 @@ Icon.Minus = Minus;
162
162
  Icon.Dashboard = Dashboard;
163
163
  Icon.FleetIcon = FleetIcon;
164
164
  Icon.Performance = Performance;
165
- Icon.Map = Map;
165
+ Icon.Map = Map2;
166
166
  Icon.CharterParty = CharterParty;
167
167
  Icon.Compliance = Compliance;
168
168
  Icon.Applications = Applications;
@@ -2179,6 +2179,51 @@ function fieldShell({
2179
2179
  "placeholder:text-foreground-muted"
2180
2180
  ].filter(Boolean).join(" ");
2181
2181
  }
2182
+ function FieldHelpIcon({ text }) {
2183
+ return /* @__PURE__ */ jsxRuntime.jsx(Tooltip, { title: text, placement: "top", children: /* @__PURE__ */ jsxRuntime.jsx(
2184
+ "button",
2185
+ {
2186
+ type: "button",
2187
+ "aria-label": "More information",
2188
+ className: "inline-flex items-center justify-center rounded-full text-foreground-muted transition-colors hover:text-foreground focus:outline-none focus-visible:text-accent",
2189
+ children: /* @__PURE__ */ jsxRuntime.jsxs("svg", { viewBox: "0 0 16 16", className: "h-3.5 w-3.5", fill: "none", stroke: "currentColor", strokeWidth: 1.5, "aria-hidden": "true", children: [
2190
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "8", cy: "8", r: "6.25" }),
2191
+ /* @__PURE__ */ jsxRuntime.jsx("path", { strokeLinecap: "round", d: "M8 7.4v3.4" }),
2192
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "8", cy: "5.1", r: "0.65", fill: "currentColor", stroke: "none" })
2193
+ ] })
2194
+ }
2195
+ ) });
2196
+ }
2197
+ function FieldLabel({
2198
+ label,
2199
+ htmlFor,
2200
+ required,
2201
+ helperText,
2202
+ horizontal = false,
2203
+ style,
2204
+ width,
2205
+ className = ""
2206
+ }) {
2207
+ if (label == null && helperText == null) return null;
2208
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2209
+ "div",
2210
+ {
2211
+ style: { width: horizontal ? width : void 0, ...style },
2212
+ className: [
2213
+ "flex items-center gap-1",
2214
+ horizontal ? "mt-2 flex-shrink-0 whitespace-nowrap" : "",
2215
+ className
2216
+ ].filter(Boolean).join(" "),
2217
+ children: [
2218
+ label != null && /* @__PURE__ */ jsxRuntime.jsxs("label", { htmlFor, className: "text-sm font-medium text-foreground select-none", children: [
2219
+ label,
2220
+ required && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-status-error ml-0.5", "aria-hidden": "true", children: "*" })
2221
+ ] }),
2222
+ helperText != null && /* @__PURE__ */ jsxRuntime.jsx(FieldHelpIcon, { text: helperText })
2223
+ ]
2224
+ }
2225
+ );
2226
+ }
2182
2227
  function Field({
2183
2228
  label,
2184
2229
  htmlFor,
@@ -2186,6 +2231,7 @@ function Field({
2186
2231
  errorMessage,
2187
2232
  layout = "vertical",
2188
2233
  required,
2234
+ helperText,
2189
2235
  labelStyle,
2190
2236
  labelWidth,
2191
2237
  className = "",
@@ -2202,21 +2248,16 @@ function Field({
2202
2248
  className
2203
2249
  ].filter(Boolean).join(" "),
2204
2250
  children: [
2205
- label && /* @__PURE__ */ jsxRuntime.jsxs(
2206
- "label",
2251
+ /* @__PURE__ */ jsxRuntime.jsx(
2252
+ FieldLabel,
2207
2253
  {
2254
+ label,
2208
2255
  htmlFor,
2209
- style: { width: horizontal ? labelWidth : void 0, ...labelStyle },
2210
- className: [
2211
- "text-sm font-medium text-foreground select-none",
2212
- // In horizontal layout the label must not wrap onto
2213
- // multiple lines (e.g. "Report date", "Select option").
2214
- horizontal ? "mt-2 flex-shrink-0 whitespace-nowrap" : ""
2215
- ].filter(Boolean).join(" "),
2216
- children: [
2217
- label,
2218
- required && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-status-error ml-0.5", "aria-hidden": "true", children: "*" })
2219
- ]
2256
+ required,
2257
+ helperText,
2258
+ horizontal,
2259
+ style: labelStyle,
2260
+ width: labelWidth
2220
2261
  }
2221
2262
  ),
2222
2263
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col min-w-0 flex-1", children: [
@@ -2228,8 +2269,8 @@ function Field({
2228
2269
  );
2229
2270
  }
2230
2271
  var SearchIcon = /* @__PURE__ */ jsxRuntime.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__ */ 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" }) });
2231
- var SearchInput = React8__default.default.forwardRef(function SearchInput2({ value, onChange, disabled, label, htmlFor, placeholder, name, inputStyle, style, layout = "vertical", size = "md", icon }, ref) {
2232
- return /* @__PURE__ */ jsxRuntime.jsx(Field, { label, htmlFor, layout, children: /* @__PURE__ */ jsxRuntime.jsxs(
2272
+ var SearchInput = React8__default.default.forwardRef(function SearchInput2({ value, onChange, disabled, label, htmlFor, placeholder, name, inputStyle, style, layout = "vertical", size = "md", icon, helperText }, ref) {
2273
+ return /* @__PURE__ */ jsxRuntime.jsx(Field, { label, htmlFor, layout, helperText, children: /* @__PURE__ */ jsxRuntime.jsxs(
2233
2274
  "div",
2234
2275
  {
2235
2276
  className: `flex items-center ${fieldShell({ size, disabled, focusWithin: true })}`,
@@ -2276,6 +2317,90 @@ function Tag({ children, onRemove, removeLabel, disabled }) {
2276
2317
  )
2277
2318
  ] });
2278
2319
  }
2320
+ function MultiTagRow({
2321
+ values,
2322
+ disabled,
2323
+ labelFor,
2324
+ onRemove
2325
+ }) {
2326
+ const wrapRef = React8.useRef(null);
2327
+ const measureRef = React8.useRef(null);
2328
+ const [visibleCount, setVisibleCount] = React8.useState(values.length);
2329
+ const key = values.map(String).join("|");
2330
+ React8.useLayoutEffect(() => {
2331
+ const wrap = wrapRef.current;
2332
+ const measure = measureRef.current;
2333
+ if (!wrap || !measure) return;
2334
+ const GAP = 6;
2335
+ const recompute = () => {
2336
+ const avail = wrap.clientWidth;
2337
+ const tagEls = Array.from(measure.querySelectorAll("[data-mt]"));
2338
+ const moreEl = measure.querySelector("[data-mm]");
2339
+ const widths = tagEls.map((e) => e.offsetWidth);
2340
+ const moreW = moreEl ? moreEl.offsetWidth : 0;
2341
+ if (widths.length === 0) {
2342
+ setVisibleCount(0);
2343
+ return;
2344
+ }
2345
+ let used = 0;
2346
+ let count = 0;
2347
+ for (let i = 0; i < widths.length; i++) {
2348
+ const w = widths[i] + (i > 0 ? GAP : 0);
2349
+ if (used + w <= avail) {
2350
+ used += w;
2351
+ count++;
2352
+ } else break;
2353
+ }
2354
+ if (count < widths.length) {
2355
+ while (count > 0) {
2356
+ let t = 0;
2357
+ for (let i = 0; i < count; i++) t += widths[i] + (i > 0 ? GAP : 0);
2358
+ t += GAP + moreW;
2359
+ if (t <= avail) break;
2360
+ count--;
2361
+ }
2362
+ }
2363
+ setVisibleCount(count);
2364
+ };
2365
+ recompute();
2366
+ const ro = new ResizeObserver(recompute);
2367
+ ro.observe(wrap);
2368
+ return () => ro.disconnect();
2369
+ }, [key]);
2370
+ const hidden = values.length - visibleCount;
2371
+ const moreChip = (n) => /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-flex items-center flex-shrink-0 rounded-md border border-border bg-surface-raised text-foreground-secondary text-xs px-2 py-0.5", children: [
2372
+ "+",
2373
+ n,
2374
+ " more"
2375
+ ] });
2376
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: wrapRef, className: "relative flex-1 min-w-0 flex flex-nowrap items-center gap-1.5 overflow-hidden", children: [
2377
+ /* @__PURE__ */ jsxRuntime.jsxs(
2378
+ "div",
2379
+ {
2380
+ ref: measureRef,
2381
+ "aria-hidden": "true",
2382
+ className: "absolute invisible pointer-events-none flex flex-nowrap items-center gap-1.5",
2383
+ style: { left: -9999, top: -9999 },
2384
+ children: [
2385
+ values.map((val) => /* @__PURE__ */ jsxRuntime.jsx("span", { "data-mt": true, children: /* @__PURE__ */ jsxRuntime.jsx(Tag, { removeLabel: "x", onRemove: () => {
2386
+ }, children: labelFor(val) }) }, `m-${val}`)),
2387
+ /* @__PURE__ */ jsxRuntime.jsx("span", { "data-mm": true, children: moreChip(values.length) })
2388
+ ]
2389
+ }
2390
+ ),
2391
+ values.slice(0, visibleCount).map((val) => /* @__PURE__ */ jsxRuntime.jsx(
2392
+ Tag,
2393
+ {
2394
+ disabled,
2395
+ removeLabel: `Remove ${labelFor(val)}`,
2396
+ onRemove: () => onRemove(val),
2397
+ children: labelFor(val)
2398
+ },
2399
+ String(val)
2400
+ )),
2401
+ hidden > 0 && moreChip(hidden)
2402
+ ] });
2403
+ }
2279
2404
  function Dropdown({
2280
2405
  isMultiselect = false,
2281
2406
  hasSearch = true,
@@ -2285,6 +2410,8 @@ function Dropdown({
2285
2410
  onChange,
2286
2411
  disabled,
2287
2412
  layout = "horizontal",
2413
+ helperText,
2414
+ required,
2288
2415
  errorMessage,
2289
2416
  style = {},
2290
2417
  htmlFor,
@@ -2345,13 +2472,15 @@ function Dropdown({
2345
2472
  {
2346
2473
  className: `flex ${layout === "vertical" ? "flex-col gap-1.5" : "flex-row items-start gap-3"}`,
2347
2474
  children: [
2348
- label && /* @__PURE__ */ jsxRuntime.jsx(
2349
- "label",
2475
+ /* @__PURE__ */ jsxRuntime.jsx(
2476
+ FieldLabel,
2350
2477
  {
2351
- className: `text-sm font-medium select-none text-foreground ${layout === "horizontal" ? "mt-2 flex-shrink-0 whitespace-nowrap" : ""}`,
2478
+ label,
2352
2479
  htmlFor,
2353
- style: labelStyle,
2354
- children: label
2480
+ required,
2481
+ helperText,
2482
+ horizontal: layout === "horizontal",
2483
+ style: labelStyle
2355
2484
  }
2356
2485
  ),
2357
2486
  /* @__PURE__ */ jsxRuntime.jsxs(Popover__namespace.Root, { open: open && !disabled, onOpenChange: (o) => !disabled && setOpen(o), children: [
@@ -2364,8 +2493,8 @@ function Dropdown({
2364
2493
  "aria-haspopup": "listbox",
2365
2494
  "aria-invalid": hasError || void 0,
2366
2495
  "aria-describedby": hasError ? errorId : void 0,
2367
- style,
2368
- className: `flex items-center justify-between gap-2 cursor-pointer select-none min-h-[36px] px-3 py-1.5 ${!style?.width ? "min-w-[200px]" : ""} ${fieldShell({ size, hasError, disabled, sized: false })}`,
2496
+ style: { width: 240, ...style },
2497
+ className: `flex items-center justify-between gap-2 cursor-pointer select-none min-h-[36px] px-3 py-1.5 ${fieldShell({ size, hasError, disabled, sized: false })}`,
2369
2498
  tabIndex: disabled ? -1 : 0,
2370
2499
  onKeyDown: (e) => {
2371
2500
  if (disabled) return;
@@ -2375,16 +2504,15 @@ function Dropdown({
2375
2504
  }
2376
2505
  },
2377
2506
  children: [
2378
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 min-w-0 flex flex-wrap items-center gap-1.5", children: !value || Array.isArray(value) && value.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-foreground-muted text-sm", children: placeholder }) : Array.isArray(value) ? value.map((val) => /* @__PURE__ */ jsxRuntime.jsx(
2379
- Tag,
2507
+ !value || Array.isArray(value) && value.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1 min-w-0 truncate text-foreground-muted text-sm", children: placeholder }) : Array.isArray(value) ? /* @__PURE__ */ jsxRuntime.jsx(
2508
+ MultiTagRow,
2380
2509
  {
2510
+ values: value,
2381
2511
  disabled,
2382
- removeLabel: `Remove ${labelFor(val)}`,
2383
- onRemove: () => removeSelected(val),
2384
- children: labelFor(val)
2385
- },
2386
- String(val)
2387
- )) : /* @__PURE__ */ jsxRuntime.jsx(
2512
+ labelFor,
2513
+ onRemove: removeSelected
2514
+ }
2515
+ ) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 min-w-0 flex items-center overflow-hidden", children: /* @__PURE__ */ jsxRuntime.jsx(
2388
2516
  Tag,
2389
2517
  {
2390
2518
  disabled,
@@ -3324,6 +3452,7 @@ function TextInput({
3324
3452
  size = "md",
3325
3453
  onBlur,
3326
3454
  errorMessage,
3455
+ helperText,
3327
3456
  required,
3328
3457
  prefix,
3329
3458
  suffix
@@ -3356,9 +3485,9 @@ function TextInput({
3356
3485
  htmlFor,
3357
3486
  errorId,
3358
3487
  errorMessage,
3488
+ helperText,
3359
3489
  layout,
3360
3490
  required,
3361
- className: style ? void 0 : void 0,
3362
3491
  children: hasAdornment ? /* @__PURE__ */ jsxRuntime.jsxs(
3363
3492
  "div",
3364
3493
  {
@@ -3385,6 +3514,7 @@ function NumberInput({
3385
3514
  layout = "vertical",
3386
3515
  size = "md",
3387
3516
  errorMessage,
3517
+ helperText,
3388
3518
  required,
3389
3519
  inputStyle,
3390
3520
  labelStyle,
@@ -3433,6 +3563,7 @@ function NumberInput({
3433
3563
  htmlFor,
3434
3564
  errorId,
3435
3565
  errorMessage,
3566
+ helperText,
3436
3567
  layout,
3437
3568
  required,
3438
3569
  labelStyle,
@@ -3518,6 +3649,7 @@ function Password({
3518
3649
  size = "md",
3519
3650
  onBlur,
3520
3651
  errorMessage,
3652
+ helperText,
3521
3653
  required,
3522
3654
  showIcon,
3523
3655
  hideIcon
@@ -3532,6 +3664,7 @@ function Password({
3532
3664
  htmlFor,
3533
3665
  errorId,
3534
3666
  errorMessage,
3667
+ helperText,
3535
3668
  layout,
3536
3669
  required,
3537
3670
  children: /* @__PURE__ */ jsxRuntime.jsxs(
@@ -3586,10 +3719,14 @@ function Checkbox({
3586
3719
  errorMessage,
3587
3720
  disabled = false,
3588
3721
  layout = "horizontal",
3589
- labelPosition = "right"
3722
+ labelPosition = "right",
3723
+ helperText,
3724
+ required
3590
3725
  }) {
3591
3726
  const isChecked = checked ?? value ?? false;
3592
3727
  const labelFirst = labelPosition === "left";
3728
+ const errorId = React8.useId();
3729
+ const hasError = errorMessage != null;
3593
3730
  const box = /* @__PURE__ */ jsxRuntime.jsx(
3594
3731
  CheckboxPrimitive__namespace.Root,
3595
3732
  {
@@ -3608,30 +3745,38 @@ function Checkbox({
3608
3745
  "disabled:cursor-not-allowed"
3609
3746
  ].join(" "),
3610
3747
  "aria-label": typeof label === "string" ? label : void 0,
3748
+ "aria-invalid": hasError || void 0,
3749
+ "aria-describedby": hasError ? errorId : void 0,
3611
3750
  children: /* @__PURE__ */ jsxRuntime.jsx(CheckboxPrimitive__namespace.Indicator, { className: "flex items-center justify-center data-[state=checked]:animate-check-pop", children: /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "11", height: "9", viewBox: "0 0 11 9", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M1 4.5L4 7.5L10 1", stroke: "white", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round" }) }) })
3612
3751
  }
3613
3752
  );
3614
- const labelEl = label && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-foreground-secondary select-none leading-snug", children: label });
3753
+ const labelEl = label && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-sm text-foreground-secondary select-none leading-snug", children: [
3754
+ label,
3755
+ required && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-status-error ml-0.5", "aria-hidden": "true", children: "*" })
3756
+ ] });
3615
3757
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
3616
- /* @__PURE__ */ jsxRuntime.jsx(
3617
- "label",
3618
- {
3619
- htmlFor,
3620
- className: [
3621
- "inline-flex",
3622
- layout === "vertical" ? "flex-col items-start gap-1.5" : "flex-row items-center gap-2.5",
3623
- disabled ? "cursor-not-allowed opacity-50" : "cursor-pointer"
3624
- ].join(" "),
3625
- children: labelFirst ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3626
- labelEl,
3627
- box
3628
- ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3629
- box,
3630
- labelEl
3631
- ] })
3632
- }
3633
- ),
3634
- errorMessage && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-status-error mt-0.5", children: errorMessage })
3758
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
3759
+ /* @__PURE__ */ jsxRuntime.jsx(
3760
+ "label",
3761
+ {
3762
+ htmlFor,
3763
+ className: [
3764
+ "inline-flex",
3765
+ layout === "vertical" ? "flex-col items-start gap-1.5" : "flex-row items-center gap-2.5",
3766
+ disabled ? "cursor-not-allowed opacity-50" : "cursor-pointer"
3767
+ ].join(" "),
3768
+ children: labelFirst ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3769
+ labelEl,
3770
+ box
3771
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3772
+ box,
3773
+ labelEl
3774
+ ] })
3775
+ }
3776
+ ),
3777
+ helperText != null && /* @__PURE__ */ jsxRuntime.jsx(FieldHelpIcon, { text: helperText })
3778
+ ] }),
3779
+ errorMessage && /* @__PURE__ */ jsxRuntime.jsx("span", { id: errorId, className: "text-xs text-status-error mt-0.5", children: errorMessage })
3635
3780
  ] });
3636
3781
  }
3637
3782
  var DOT_SIZE = {
@@ -3656,6 +3801,7 @@ function RadioGroup({
3656
3801
  size = "md",
3657
3802
  disabled,
3658
3803
  required,
3804
+ helperText,
3659
3805
  errorMessage
3660
3806
  }) {
3661
3807
  const errorId = React8.useId();
@@ -3670,6 +3816,7 @@ function RadioGroup({
3670
3816
  errorId,
3671
3817
  errorMessage,
3672
3818
  required,
3819
+ helperText,
3673
3820
  children: /* @__PURE__ */ jsxRuntime.jsx(
3674
3821
  RadioGroupPrimitive__namespace.Root,
3675
3822
  {
@@ -3720,7 +3867,8 @@ function RadioGroup({
3720
3867
  ]
3721
3868
  }
3722
3869
  );
3723
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-start gap-2.5", children: labelFirst ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3870
+ const rowClass = labelFirst && layout === "vertical" ? "grid grid-cols-[1fr_auto] items-start gap-2.5" : "flex items-start gap-2.5";
3871
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: rowClass, children: labelFirst ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3724
3872
  labelEl,
3725
3873
  dot
3726
3874
  ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
@@ -3734,28 +3882,73 @@ function RadioGroup({
3734
3882
  );
3735
3883
  }
3736
3884
  function Switch({
3737
- checked = false,
3885
+ checked,
3886
+ defaultChecked = false,
3738
3887
  onChange,
3739
3888
  checkedIcon,
3740
- uncheckedIcon
3889
+ uncheckedIcon,
3890
+ label,
3891
+ layout = "horizontal",
3892
+ helperText,
3893
+ offLabel,
3894
+ onLabel,
3895
+ name,
3896
+ required,
3897
+ disabled,
3898
+ errorMessage
3741
3899
  }) {
3742
3900
  const id = React8.useId();
3743
- return /* @__PURE__ */ jsxRuntime.jsx("div", { children: /* @__PURE__ */ jsxRuntime.jsx("label", { htmlFor: id, className: "flex items-center cursor-pointer select-none", children: /* @__PURE__ */ jsxRuntime.jsx(
3744
- SwitchPrimitive__namespace.Root,
3901
+ const errorId = React8.useId();
3902
+ const hasError = errorMessage != null;
3903
+ const isControlled = checked !== void 0;
3904
+ const [internal, setInternal] = React8.useState(defaultChecked);
3905
+ const isOn = isControlled ? checked : internal;
3906
+ const handle = (c) => {
3907
+ if (!isControlled) setInternal(c);
3908
+ onChange?.({ target: { checked: c, name } });
3909
+ };
3910
+ const stateLabel = (active) => [
3911
+ "text-sm select-none transition-colors",
3912
+ active ? "text-foreground font-medium" : "text-foreground-muted",
3913
+ disabled ? "opacity-50" : "cursor-pointer"
3914
+ ].filter(Boolean).join(" ");
3915
+ return /* @__PURE__ */ jsxRuntime.jsx(
3916
+ Field,
3745
3917
  {
3746
- id,
3747
- checked,
3748
- onCheckedChange: (c) => onChange?.({ target: { checked: c } }),
3749
- className: "relative inline-flex h-6 w-11 flex-shrink-0 items-center rounded-full bg-foreground-secondary data-[state=checked]:bg-accent transition-colors focus:outline-none focus-visible:ring-[3px] focus-visible:ring-focus-ring",
3750
- children: /* @__PURE__ */ jsxRuntime.jsx(
3751
- SwitchPrimitive__namespace.Thumb,
3752
- {
3753
- className: "pointer-events-none flex h-5 w-5 items-center justify-center rounded-full bg-background text-foreground shadow transition-transform duration-200 data-[state=checked]:translate-x-[22px] data-[state=unchecked]:translate-x-[2px]",
3754
- children: checkedIcon && uncheckedIcon ? checked ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex items-center justify-center w-3 h-3", children: checkedIcon }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex items-center justify-center w-3 h-3", children: uncheckedIcon }) : null
3755
- }
3756
- )
3918
+ label,
3919
+ htmlFor: id,
3920
+ errorId,
3921
+ errorMessage,
3922
+ layout,
3923
+ required,
3924
+ helperText,
3925
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2.5", children: [
3926
+ offLabel != null && /* @__PURE__ */ jsxRuntime.jsx("label", { htmlFor: id, className: stateLabel(!isOn), children: offLabel }),
3927
+ /* @__PURE__ */ jsxRuntime.jsx(
3928
+ SwitchPrimitive__namespace.Root,
3929
+ {
3930
+ id,
3931
+ name,
3932
+ checked: isOn,
3933
+ onCheckedChange: handle,
3934
+ disabled,
3935
+ required,
3936
+ "aria-invalid": hasError || void 0,
3937
+ "aria-describedby": hasError ? errorId : void 0,
3938
+ className: "relative inline-flex h-6 w-11 flex-shrink-0 items-center rounded-full bg-foreground-secondary data-[state=checked]:bg-accent transition-colors focus:outline-none focus-visible:ring-[3px] focus-visible:ring-focus-ring disabled:opacity-50 disabled:cursor-not-allowed",
3939
+ children: /* @__PURE__ */ jsxRuntime.jsx(
3940
+ SwitchPrimitive__namespace.Thumb,
3941
+ {
3942
+ className: "pointer-events-none flex h-5 w-5 items-center justify-center rounded-full bg-background text-foreground shadow transition-transform duration-200 data-[state=checked]:translate-x-[22px] data-[state=unchecked]:translate-x-[2px]",
3943
+ children: checkedIcon && uncheckedIcon ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex items-center justify-center w-3 h-3", children: isOn ? checkedIcon : uncheckedIcon }) : null
3944
+ }
3945
+ )
3946
+ }
3947
+ ),
3948
+ onLabel != null && /* @__PURE__ */ jsxRuntime.jsx("label", { htmlFor: id, className: stateLabel(isOn), children: onLabel })
3949
+ ] })
3757
3950
  }
3758
- ) }) });
3951
+ );
3759
3952
  }
3760
3953
  function AutoComplete({
3761
3954
  disabled,
@@ -3774,6 +3967,7 @@ function AutoComplete({
3774
3967
  size = "md",
3775
3968
  icon,
3776
3969
  errorMessage,
3970
+ helperText,
3777
3971
  required,
3778
3972
  htmlFor
3779
3973
  }) {
@@ -3832,6 +4026,7 @@ function AutoComplete({
3832
4026
  htmlFor,
3833
4027
  errorId,
3834
4028
  errorMessage,
4029
+ helperText,
3835
4030
  layout,
3836
4031
  required,
3837
4032
  children: /* @__PURE__ */ jsxRuntime.jsxs(Popover__namespace.Root, { open: open && !disabled, onOpenChange: (o) => !disabled && setOpen(o), children: [
@@ -3931,6 +4126,8 @@ function TreeSelect({
3931
4126
  onChange,
3932
4127
  disabled,
3933
4128
  layout = "horizontal",
4129
+ helperText,
4130
+ required,
3934
4131
  errorMessage,
3935
4132
  style,
3936
4133
  htmlFor,
@@ -4016,12 +4213,14 @@ function TreeSelect({
4016
4213
  };
4017
4214
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
4018
4215
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `flex ${layout === "vertical" ? "flex-col gap-1" : "flex-row items-center gap-2"}`, children: [
4019
- label && /* @__PURE__ */ jsxRuntime.jsx(
4020
- "label",
4216
+ /* @__PURE__ */ jsxRuntime.jsx(
4217
+ FieldLabel,
4021
4218
  {
4022
- className: "text-sm font-medium ml-1 max-content select-none text-foreground",
4219
+ label,
4023
4220
  htmlFor,
4024
- children: label
4221
+ required,
4222
+ helperText,
4223
+ horizontal: layout === "horizontal"
4025
4224
  }
4026
4225
  ),
4027
4226
  /* @__PURE__ */ jsxRuntime.jsxs(Popover__namespace.Root, { open: open && !disabled, onOpenChange: (o) => !disabled && setOpen(o), children: [
@@ -4183,6 +4382,7 @@ function FileInput({
4183
4382
  hint,
4184
4383
  maxSize,
4185
4384
  errorMessage,
4385
+ helperText,
4186
4386
  disabled,
4187
4387
  required,
4188
4388
  icon
@@ -4229,6 +4429,7 @@ function FileInput({
4229
4429
  htmlFor,
4230
4430
  errorId,
4231
4431
  errorMessage: effectiveError,
4432
+ helperText,
4232
4433
  required,
4233
4434
  children: [
4234
4435
  /* @__PURE__ */ jsxRuntime.jsxs(
@@ -4367,6 +4568,8 @@ function DatePicker({
4367
4568
  htmlFor,
4368
4569
  name: _name,
4369
4570
  layout = "horizontal",
4571
+ helperText,
4572
+ required,
4370
4573
  disabled,
4371
4574
  errorMessage,
4372
4575
  min,
@@ -4458,12 +4661,14 @@ function DatePicker({
4458
4661
  const displayValue = value ? format(value) : "";
4459
4662
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
4460
4663
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `flex ${layout === "vertical" ? "flex-col gap-1.5" : "flex-row items-start gap-3"}`, children: [
4461
- label && /* @__PURE__ */ jsxRuntime.jsx(
4462
- "label",
4664
+ /* @__PURE__ */ jsxRuntime.jsx(
4665
+ FieldLabel,
4463
4666
  {
4464
- className: `text-sm font-medium select-none text-foreground ${layout === "horizontal" ? "mt-2 flex-shrink-0 whitespace-nowrap" : ""}`,
4667
+ label,
4465
4668
  htmlFor,
4466
- children: label
4669
+ required,
4670
+ helperText,
4671
+ horizontal: layout === "horizontal"
4467
4672
  }
4468
4673
  ),
4469
4674
  /* @__PURE__ */ jsxRuntime.jsxs(Popover__namespace.Root, { open: open && !disabled, onOpenChange: (o) => !disabled && setOpen(o), children: [
@@ -4682,6 +4887,7 @@ function TextArea({
4682
4887
  showCount = false,
4683
4888
  resize,
4684
4889
  errorMessage,
4890
+ helperText,
4685
4891
  required,
4686
4892
  style,
4687
4893
  inputStyle
@@ -4707,6 +4913,7 @@ function TextArea({
4707
4913
  htmlFor,
4708
4914
  errorId,
4709
4915
  errorMessage,
4916
+ helperText,
4710
4917
  layout,
4711
4918
  required,
4712
4919
  children: [
@@ -4747,51 +4954,84 @@ function SegmentedControl({
4747
4954
  size = "md",
4748
4955
  fullWidth = false,
4749
4956
  disabled,
4957
+ label,
4958
+ layout = "vertical",
4959
+ helperText,
4960
+ name,
4961
+ required,
4962
+ errorMessage,
4750
4963
  "aria-label": ariaLabel
4751
4964
  }) {
4752
4965
  const sz = SIZE[size];
4753
- return /* @__PURE__ */ jsxRuntime.jsx(
4754
- ToggleGroup__namespace.Root,
4966
+ const groupId = React8.useId();
4967
+ const errorId = React8.useId();
4968
+ const hasError = errorMessage != null;
4969
+ const isControlled = value !== void 0;
4970
+ const [internal, setInternal] = React8.useState(defaultValue);
4971
+ const current = isControlled ? value : internal;
4972
+ const handle = (v) => {
4973
+ if (!v) return;
4974
+ if (!isControlled) setInternal(v);
4975
+ onChange?.(v);
4976
+ };
4977
+ return /* @__PURE__ */ jsxRuntime.jsxs(
4978
+ Field,
4755
4979
  {
4756
- type: "single",
4757
- value,
4758
- defaultValue,
4759
- onValueChange: (v) => {
4760
- if (v) onChange?.(v);
4761
- },
4762
- disabled,
4763
- "aria-label": ariaLabel,
4764
- className: [
4765
- "inline-flex items-center gap-1 rounded-lg border border-border bg-surface-raised p-1",
4766
- sz.h,
4767
- fullWidth ? "flex w-full" : "",
4768
- disabled ? "opacity-60 cursor-not-allowed" : ""
4769
- ].filter(Boolean).join(" "),
4770
- children: options.map((opt) => /* @__PURE__ */ jsxRuntime.jsxs(
4771
- ToggleGroup__namespace.Item,
4772
- {
4773
- value: opt.value,
4774
- disabled: opt.disabled,
4775
- className: [
4776
- "inline-flex items-center justify-center gap-1.5 rounded-md select-none whitespace-nowrap",
4777
- "transition-colors duration-150 h-full",
4778
- sz.text,
4779
- sz.pad,
4780
- fullWidth ? "flex-1" : "",
4781
- // Resting: muted text, transparent. Hover lifts the text.
4782
- "text-foreground-secondary hover:text-foreground",
4783
- // Active: surface-white pill + accent text + subtle shadow.
4784
- "data-[state=on]:bg-surface data-[state=on]:text-accent data-[state=on]:shadow-sm",
4785
- "focus:outline-none focus-visible:ring-[3px] focus-visible:ring-focus-ring",
4786
- "disabled:opacity-40 disabled:cursor-not-allowed"
4787
- ].filter(Boolean).join(" "),
4788
- children: [
4789
- opt.icon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-shrink-0", children: opt.icon }),
4790
- opt.label
4791
- ]
4792
- },
4793
- opt.value
4794
- ))
4980
+ label,
4981
+ htmlFor: groupId,
4982
+ errorId,
4983
+ errorMessage,
4984
+ layout,
4985
+ required,
4986
+ helperText,
4987
+ children: [
4988
+ name && /* @__PURE__ */ jsxRuntime.jsx("input", { type: "hidden", name, value: current ?? "" }),
4989
+ /* @__PURE__ */ jsxRuntime.jsx(
4990
+ ToggleGroup__namespace.Root,
4991
+ {
4992
+ id: groupId,
4993
+ type: "single",
4994
+ value: current,
4995
+ onValueChange: handle,
4996
+ disabled,
4997
+ "aria-label": ariaLabel ?? (typeof label === "string" ? label : void 0),
4998
+ "aria-invalid": hasError || void 0,
4999
+ "aria-describedby": hasError ? errorId : void 0,
5000
+ className: [
5001
+ "inline-flex items-center gap-1 rounded-lg border bg-surface-raised p-1",
5002
+ hasError ? "border-status-error" : "border-border",
5003
+ sz.h,
5004
+ fullWidth ? "flex w-full" : "w-fit",
5005
+ disabled ? "opacity-60 cursor-not-allowed" : ""
5006
+ ].filter(Boolean).join(" "),
5007
+ children: options.map((opt) => /* @__PURE__ */ jsxRuntime.jsxs(
5008
+ ToggleGroup__namespace.Item,
5009
+ {
5010
+ value: opt.value,
5011
+ disabled: opt.disabled,
5012
+ className: [
5013
+ "inline-flex items-center justify-center gap-1.5 rounded-md select-none whitespace-nowrap",
5014
+ "transition-colors duration-150 h-full",
5015
+ sz.text,
5016
+ sz.pad,
5017
+ fullWidth ? "flex-1" : "",
5018
+ // Resting: muted text, transparent. Hover lifts the text.
5019
+ "text-foreground-secondary hover:text-foreground",
5020
+ // Active: surface-white pill + accent text + subtle shadow.
5021
+ "data-[state=on]:bg-surface data-[state=on]:text-accent data-[state=on]:shadow-sm",
5022
+ "focus:outline-none focus-visible:ring-[3px] focus-visible:ring-focus-ring",
5023
+ "disabled:opacity-40 disabled:cursor-not-allowed"
5024
+ ].filter(Boolean).join(" "),
5025
+ children: [
5026
+ opt.icon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-shrink-0", children: opt.icon }),
5027
+ opt.label
5028
+ ]
5029
+ },
5030
+ opt.value
5031
+ ))
5032
+ }
5033
+ )
5034
+ ]
4795
5035
  }
4796
5036
  );
4797
5037
  }
@@ -4814,6 +5054,8 @@ function Slider({
4814
5054
  size = "md",
4815
5055
  disabled,
4816
5056
  errorMessage,
5057
+ helperText,
5058
+ required,
4817
5059
  name,
4818
5060
  htmlFor
4819
5061
  }) {
@@ -4833,7 +5075,13 @@ function Slider({
4833
5075
  const valueText = current.map(formatValue).join(" \u2013 ");
4834
5076
  return /* @__PURE__ */ jsxRuntime.jsxs(Field, { label: void 0, errorId, errorMessage, children: [
4835
5077
  (label || showValue) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between mb-2", children: [
4836
- label && /* @__PURE__ */ jsxRuntime.jsx("label", { htmlFor, className: "text-sm font-medium text-foreground select-none", children: label }),
5078
+ label && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-1", children: [
5079
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { htmlFor, className: "text-sm font-medium text-foreground select-none", children: [
5080
+ label,
5081
+ required && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-status-error ml-0.5", "aria-hidden": "true", children: "*" })
5082
+ ] }),
5083
+ helperText != null && /* @__PURE__ */ jsxRuntime.jsx(FieldHelpIcon, { text: helperText })
5084
+ ] }),
4837
5085
  showValue && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-foreground-secondary tabular-nums", children: valueText })
4838
5086
  ] }),
4839
5087
  /* @__PURE__ */ jsxRuntime.jsxs(
@@ -4905,6 +5153,7 @@ function TagsInput({
4905
5153
  size = "md",
4906
5154
  disabled,
4907
5155
  errorMessage,
5156
+ helperText,
4908
5157
  required,
4909
5158
  maxTags,
4910
5159
  dedupe = true,
@@ -4972,6 +5221,7 @@ function TagsInput({
4972
5221
  htmlFor,
4973
5222
  errorId,
4974
5223
  errorMessage: errorText,
5224
+ helperText,
4975
5225
  layout,
4976
5226
  required,
4977
5227
  children: /* @__PURE__ */ jsxRuntime.jsxs(
@@ -5039,6 +5289,8 @@ function OtpInput({
5039
5289
  disabled,
5040
5290
  errorMessage,
5041
5291
  required,
5292
+ layout = "vertical",
5293
+ helperText,
5042
5294
  groupAfter
5043
5295
  }) {
5044
5296
  const errorId = React8.useId();
@@ -5092,7 +5344,7 @@ function OtpInput({
5092
5344
  emit(valid.join(""));
5093
5345
  focusBox(valid.length);
5094
5346
  };
5095
- return /* @__PURE__ */ jsxRuntime.jsx(Field, { label, htmlFor, errorId, errorMessage, required, children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-2", role: "group", "aria-label": typeof label === "string" ? label : "One-time code", children: chars.map((char, idx) => /* @__PURE__ */ jsxRuntime.jsxs(React8__default.default.Fragment, { children: [
5347
+ return /* @__PURE__ */ jsxRuntime.jsx(Field, { label, htmlFor, errorId, errorMessage, required, layout, helperText, children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-2", role: "group", "aria-label": typeof label === "string" ? label : "One-time code", children: chars.map((char, idx) => /* @__PURE__ */ jsxRuntime.jsxs(React8__default.default.Fragment, { children: [
5096
5348
  /* @__PURE__ */ jsxRuntime.jsx(
5097
5349
  "input",
5098
5350
  {
@@ -5144,7 +5396,10 @@ function Rating({
5144
5396
  disabled,
5145
5397
  icon = Star,
5146
5398
  errorMessage,
5147
- name
5399
+ name,
5400
+ layout = "vertical",
5401
+ helperText,
5402
+ required
5148
5403
  }) {
5149
5404
  const errorId = React8.useId();
5150
5405
  const [internal, setInternal] = React8.useState(defaultValue);
@@ -5174,7 +5429,7 @@ function Rating({
5174
5429
  commit(count);
5175
5430
  }
5176
5431
  };
5177
- return /* @__PURE__ */ jsxRuntime.jsx(Field, { label, errorId, errorMessage, children: /* @__PURE__ */ jsxRuntime.jsxs(
5432
+ return /* @__PURE__ */ jsxRuntime.jsx(Field, { label, errorId, errorMessage, layout, required, helperText, children: /* @__PURE__ */ jsxRuntime.jsxs(
5178
5433
  "div",
5179
5434
  {
5180
5435
  role: interactive ? "slider" : "img",
@@ -5266,6 +5521,7 @@ function TimePicker({
5266
5521
  minuteStep = 1,
5267
5522
  disabled,
5268
5523
  errorMessage,
5524
+ helperText,
5269
5525
  required,
5270
5526
  style
5271
5527
  }) {
@@ -5298,7 +5554,7 @@ function TimePicker({
5298
5554
  },
5299
5555
  n
5300
5556
  )) });
5301
- return /* @__PURE__ */ jsxRuntime.jsxs(Field, { label, htmlFor, errorId, errorMessage, layout, required, children: [
5557
+ return /* @__PURE__ */ jsxRuntime.jsxs(Field, { label, htmlFor, errorId, errorMessage, helperText, layout, required, children: [
5302
5558
  /* @__PURE__ */ jsxRuntime.jsxs(Popover__namespace.Root, { open: open && !disabled, onOpenChange: (o) => !disabled && setOpen(o), children: [
5303
5559
  /* @__PURE__ */ jsxRuntime.jsx(Popover__namespace.Trigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(
5304
5560
  "button",
@@ -5390,6 +5646,7 @@ function DateRangePicker({
5390
5646
  format = defaultFmt,
5391
5647
  disabled,
5392
5648
  errorMessage,
5649
+ helperText,
5393
5650
  required,
5394
5651
  style
5395
5652
  }) {
@@ -5462,7 +5719,7 @@ function DateRangePicker({
5462
5719
  ] })
5463
5720
  ] });
5464
5721
  };
5465
- return /* @__PURE__ */ jsxRuntime.jsx(Field, { label, htmlFor, errorId, errorMessage, layout, required, children: /* @__PURE__ */ jsxRuntime.jsxs(Popover__namespace.Root, { open: open && !disabled, onOpenChange: (o) => {
5722
+ return /* @__PURE__ */ jsxRuntime.jsx(Field, { label, htmlFor, errorId, errorMessage, helperText, layout, required, children: /* @__PURE__ */ jsxRuntime.jsxs(Popover__namespace.Root, { open: open && !disabled, onOpenChange: (o) => {
5466
5723
  if (!disabled) {
5467
5724
  setOpen(o);
5468
5725
  if (!o) {
@@ -5570,6 +5827,7 @@ function ColorPicker({
5570
5827
  allowCustom = true,
5571
5828
  disabled,
5572
5829
  errorMessage,
5830
+ helperText,
5573
5831
  required,
5574
5832
  placeholder = "Pick a colour\u2026"
5575
5833
  }) {
@@ -5587,7 +5845,7 @@ function ColorPicker({
5587
5845
  setDraft(hex);
5588
5846
  if (HEX_RE.test(hex)) onChange?.(hex);
5589
5847
  };
5590
- return /* @__PURE__ */ jsxRuntime.jsxs(Field, { label, htmlFor, errorId, errorMessage, layout, required, children: [
5848
+ return /* @__PURE__ */ jsxRuntime.jsxs(Field, { label, htmlFor, errorId, errorMessage, helperText, layout, required, children: [
5591
5849
  /* @__PURE__ */ jsxRuntime.jsxs(Popover__namespace.Root, { open: open && !disabled, onOpenChange: (o) => !disabled && setOpen(o), children: [
5592
5850
  /* @__PURE__ */ jsxRuntime.jsx(Popover__namespace.Trigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(
5593
5851
  "button",
@@ -5662,6 +5920,438 @@ function ColorPicker({
5662
5920
  ] });
5663
5921
  }
5664
5922
 
5923
+ // src/form/path.ts
5924
+ function deepClone(v) {
5925
+ if (v === null || typeof v !== "object") return v;
5926
+ if (v instanceof Date) return new Date(v.getTime());
5927
+ if (Array.isArray(v)) return v.map(deepClone);
5928
+ const out = {};
5929
+ for (const k in v) out[k] = deepClone(v[k]);
5930
+ return out;
5931
+ }
5932
+ function getPath(obj, path) {
5933
+ if (!path) return obj;
5934
+ const parts = path.split(".");
5935
+ let cur = obj;
5936
+ for (const p of parts) {
5937
+ if (cur == null || typeof cur !== "object") return void 0;
5938
+ cur = cur[p];
5939
+ }
5940
+ return cur;
5941
+ }
5942
+ function setPath(obj, path, value) {
5943
+ const parts = path.split(".");
5944
+ const root = Array.isArray(obj) ? [...obj] : { ...obj ?? {} };
5945
+ let cur = root;
5946
+ for (let i = 0; i < parts.length - 1; i++) {
5947
+ const p = parts[i];
5948
+ const nextIsIndex = /^\d+$/.test(parts[i + 1]);
5949
+ const child = cur[p];
5950
+ const next = Array.isArray(child) ? [...child] : child && typeof child === "object" ? { ...child } : nextIsIndex ? [] : {};
5951
+ cur[p] = next;
5952
+ cur = next;
5953
+ }
5954
+ cur[parts[parts.length - 1]] = value;
5955
+ return root;
5956
+ }
5957
+
5958
+ // src/form/validate.ts
5959
+ function isEmpty(v) {
5960
+ return v == null || v === "" || v === false || Array.isArray(v) && v.length === 0;
5961
+ }
5962
+ var boundValue = (b) => typeof b === "number" ? b : b.value;
5963
+ var boundMessage = (b, fallback) => typeof b === "number" ? fallback : b.message ?? fallback;
5964
+ function isRequired(rules) {
5965
+ if (!rules) return false;
5966
+ const list = Array.isArray(rules) ? rules : [rules];
5967
+ return list.some((r) => !!r.required);
5968
+ }
5969
+ async function runFieldRules(value, rules, values) {
5970
+ if (!rules) return void 0;
5971
+ const list = Array.isArray(rules) ? rules : [rules];
5972
+ for (const rule of list) {
5973
+ if (rule.required && isEmpty(value)) {
5974
+ return typeof rule.required === "string" ? rule.required : rule.message ?? "This field is required";
5975
+ }
5976
+ if (isEmpty(value)) {
5977
+ if (rule.validate) {
5978
+ const res = await rule.validate(value, values);
5979
+ if (res) return typeof res === "string" ? res : rule.message ?? "Invalid value";
5980
+ }
5981
+ continue;
5982
+ }
5983
+ if (rule.pattern) {
5984
+ const re = rule.pattern instanceof RegExp ? rule.pattern : rule.pattern.value;
5985
+ const msg = rule.pattern instanceof RegExp ? rule.message ?? "Invalid format" : rule.pattern.message ?? rule.message ?? "Invalid format";
5986
+ if (typeof value === "string" && !re.test(value)) return msg;
5987
+ }
5988
+ if (rule.min != null && typeof value === "number") {
5989
+ const m = boundValue(rule.min);
5990
+ if (value < m) return boundMessage(rule.min, rule.message ?? `Must be at least ${m}`);
5991
+ }
5992
+ if (rule.max != null && typeof value === "number") {
5993
+ const m = boundValue(rule.max);
5994
+ if (value > m) return boundMessage(rule.max, rule.message ?? `Must be at most ${m}`);
5995
+ }
5996
+ if (rule.minLength != null) {
5997
+ const len = value?.length;
5998
+ const m = boundValue(rule.minLength);
5999
+ if (typeof len === "number" && len < m)
6000
+ return boundMessage(rule.minLength, rule.message ?? `Must be at least ${m} characters`);
6001
+ }
6002
+ if (rule.maxLength != null) {
6003
+ const len = value?.length;
6004
+ const m = boundValue(rule.maxLength);
6005
+ if (typeof len === "number" && len > m)
6006
+ return boundMessage(rule.maxLength, rule.message ?? `Must be at most ${m} characters`);
6007
+ }
6008
+ if (rule.validate) {
6009
+ const res = await rule.validate(value, values);
6010
+ if (res) return typeof res === "string" ? res : rule.message ?? "Invalid value";
6011
+ }
6012
+ }
6013
+ return void 0;
6014
+ }
6015
+ var patterns = {
6016
+ email: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
6017
+ url: /^https?:\/\/[^\s/$.?#].[^\s]*$/i,
6018
+ // Loose international phone: + and 7-15 digits, spaces/dashes allowed.
6019
+ phone: /^\+?[\d\s-]{7,15}$/,
6020
+ // Digits only.
6021
+ digits: /^\d+$/
6022
+ };
6023
+
6024
+ // src/form/store.ts
6025
+ var FormStore = class {
6026
+ listeners = /* @__PURE__ */ new Set();
6027
+ fieldCache = /* @__PURE__ */ new Map();
6028
+ values;
6029
+ errors = {};
6030
+ touched = {};
6031
+ submitted = false;
6032
+ validating = false;
6033
+ initialValues;
6034
+ rules;
6035
+ validateOn;
6036
+ // Field-array key bookkeeping (stable React keys across reorder/removal).
6037
+ keys = {};
6038
+ keySeq = 1;
6039
+ // Root snapshot — a new ref on every change, for form-level subscribers.
6040
+ rootSnap = { v: 0 };
6041
+ constructor(opts = {}) {
6042
+ this.initialValues = deepClone(opts.initialValues ?? {});
6043
+ this.values = deepClone(opts.initialValues ?? {});
6044
+ this.rules = { ...opts.rules ?? {} };
6045
+ this.validateOn = opts.validateOn ?? ["onChange", "onBlur", "onSubmit"];
6046
+ }
6047
+ // ── subscription ────────────────────────────────────────────────────────
6048
+ subscribe = (l) => {
6049
+ this.listeners.add(l);
6050
+ return () => {
6051
+ this.listeners.delete(l);
6052
+ };
6053
+ };
6054
+ emit() {
6055
+ this.rootSnap = { v: this.rootSnap.v + 1 };
6056
+ this.listeners.forEach((l) => l());
6057
+ }
6058
+ getRootSnapshot = () => this.rootSnap;
6059
+ getFieldSnapshot = (name) => {
6060
+ const value = getPath(this.values, name);
6061
+ const error = this.errors[name];
6062
+ const showError = (!!this.touched[name] || this.submitted) && error != null;
6063
+ const prev = this.fieldCache.get(name);
6064
+ if (prev && Object.is(prev.value, value) && prev.error === error && prev.showError === showError) {
6065
+ return prev.snap;
6066
+ }
6067
+ const snap = { value, error, showError };
6068
+ this.fieldCache.set(name, { value, error, showError, snap });
6069
+ return snap;
6070
+ };
6071
+ // ── rule registry (for dynamic / array fields) ──────────────────────────
6072
+ setRule(name, rules) {
6073
+ if (rules == null) delete this.rules[name];
6074
+ else this.rules[name] = rules;
6075
+ }
6076
+ removeRule(name) {
6077
+ delete this.rules[name];
6078
+ }
6079
+ getRule(name) {
6080
+ return this.rules[name];
6081
+ }
6082
+ // ── reads ────────────────────────────────────────────────────────────────
6083
+ getValues = () => this.values;
6084
+ getValue = (name) => getPath(this.values, name);
6085
+ get isValid() {
6086
+ return Object.values(this.errors).every((e) => !e);
6087
+ }
6088
+ // ── writes ────────────────────────────────────────────────────────────────
6089
+ setValue = (name, value, opts = {}) => {
6090
+ this.values = setPath(this.values, name, value);
6091
+ if (opts.touch) this.touched = { ...this.touched, [name]: true };
6092
+ this.emit();
6093
+ const shouldValidate = opts.validate ?? this.validateOn.includes("onChange");
6094
+ if (shouldValidate) void this.validateField(name);
6095
+ };
6096
+ setValues = (patch, opts = {}) => {
6097
+ for (const k of Object.keys(patch)) this.values = setPath(this.values, k, patch[k]);
6098
+ this.emit();
6099
+ if (opts.validate) void this.validateAll();
6100
+ };
6101
+ setError = (name, error) => {
6102
+ if (this.errors[name] === error) return;
6103
+ this.errors = { ...this.errors, [name]: error || void 0 };
6104
+ this.emit();
6105
+ };
6106
+ touch = (name, opts = {}) => {
6107
+ if (!this.touched[name]) {
6108
+ this.touched = { ...this.touched, [name]: true };
6109
+ this.emit();
6110
+ }
6111
+ if (opts.validate ?? this.validateOn.includes("onBlur")) void this.validateField(name);
6112
+ };
6113
+ setSubmitted = (v) => {
6114
+ this.submitted = v;
6115
+ this.emit();
6116
+ };
6117
+ // ── validation ─────────────────────────────────────────────────────────────
6118
+ async validateField(name) {
6119
+ const err = await runFieldRules(getPath(this.values, name), this.rules[name], this.values);
6120
+ this.setError(name, err);
6121
+ return err;
6122
+ }
6123
+ async validateAll() {
6124
+ this.validating = true;
6125
+ this.emit();
6126
+ const names = Object.keys(this.rules);
6127
+ const entries = await Promise.all(
6128
+ names.map(async (n) => [n, await runFieldRules(getPath(this.values, n), this.rules[n], this.values)])
6129
+ );
6130
+ const errors = {};
6131
+ for (const [n, e] of entries) errors[n] = e;
6132
+ this.errors = errors;
6133
+ this.validating = false;
6134
+ this.emit();
6135
+ return errors;
6136
+ }
6137
+ reset = (values) => {
6138
+ this.values = deepClone(values ?? this.initialValues);
6139
+ this.errors = {};
6140
+ this.touched = {};
6141
+ this.submitted = false;
6142
+ this.keys = {};
6143
+ this.fieldCache.clear();
6144
+ this.emit();
6145
+ };
6146
+ // ── field arrays ───────────────────────────────────────────────────────────
6147
+ getKeys(name) {
6148
+ const arr = getPath(this.values, name) ?? [];
6149
+ let keys = this.keys[name];
6150
+ if (!keys || keys.length !== arr.length) {
6151
+ keys = arr.map((_, i) => keys && keys[i] != null ? keys[i] : this.keySeq++);
6152
+ this.keys[name] = keys;
6153
+ }
6154
+ return keys;
6155
+ }
6156
+ arrayAppend = (name, item = {}) => {
6157
+ const arr = [...getPath(this.values, name) ?? []];
6158
+ arr.push(item);
6159
+ this.keys[name] = [...this.getKeys(name), this.keySeq++];
6160
+ this.setValue(name, arr, { validate: false });
6161
+ };
6162
+ arrayRemove = (name, index) => {
6163
+ const arr = [...getPath(this.values, name) ?? []];
6164
+ arr.splice(index, 1);
6165
+ const k = [...this.getKeys(name)];
6166
+ k.splice(index, 1);
6167
+ this.keys[name] = k;
6168
+ this.clearBranch(name);
6169
+ this.setValue(name, arr, { validate: false });
6170
+ };
6171
+ arrayMove = (name, from, to) => {
6172
+ const arr = [...getPath(this.values, name) ?? []];
6173
+ if (from < 0 || to < 0 || from >= arr.length || to >= arr.length) return;
6174
+ const [moved] = arr.splice(from, 1);
6175
+ arr.splice(to, 0, moved);
6176
+ const k = [...this.getKeys(name)];
6177
+ const [mk] = k.splice(from, 1);
6178
+ k.splice(to, 0, mk);
6179
+ this.keys[name] = k;
6180
+ this.clearBranch(name);
6181
+ this.setValue(name, arr, { validate: false });
6182
+ };
6183
+ /** Drop any errors/touched flags under `name.` — used when an array shifts. */
6184
+ clearBranch(name) {
6185
+ const prefix = name + ".";
6186
+ const errors = {};
6187
+ for (const k of Object.keys(this.errors)) if (!k.startsWith(prefix)) errors[k] = this.errors[k];
6188
+ const touched = {};
6189
+ for (const k of Object.keys(this.touched)) if (!k.startsWith(prefix)) touched[k] = this.touched[k];
6190
+ this.errors = errors;
6191
+ this.touched = touched;
6192
+ }
6193
+ };
6194
+
6195
+ // src/form/bindings.ts
6196
+ var getTarget = (arg) => {
6197
+ const t = arg?.target;
6198
+ return t && typeof t === "object" ? t : void 0;
6199
+ };
6200
+ var ADAPTERS = {
6201
+ value: { prop: "value", toValue: (v) => v, applyEmpty: false, empty: void 0 },
6202
+ native: { prop: "value", toValue: (e) => getTarget(e)?.value, applyEmpty: true, empty: "" },
6203
+ checked: { prop: "checked", toValue: (e) => getTarget(e)?.checked, applyEmpty: true, empty: false },
6204
+ target: { prop: "value", toValue: (e) => getTarget(e)?.value, applyEmpty: false, empty: void 0 }
6205
+ };
6206
+ function buildBindings(store, name, kind, snap) {
6207
+ const a = ADAPTERS[kind];
6208
+ const raw = snap.value;
6209
+ const value = a.applyEmpty ? raw ?? a.empty : raw;
6210
+ return {
6211
+ name,
6212
+ id: name,
6213
+ htmlFor: name,
6214
+ required: isRequired(store.getRule(name)) || void 0,
6215
+ errorMessage: snap.showError ? snap.error : void 0,
6216
+ [a.prop]: value,
6217
+ onChange: (arg) => store.setValue(name, a.toValue(arg), { touch: true }),
6218
+ onBlur: () => store.touch(name)
6219
+ };
6220
+ }
6221
+
6222
+ // src/form/useForm.ts
6223
+ function useForm(options = {}) {
6224
+ const ref = React8.useRef(null);
6225
+ if (ref.current === null) ref.current = new FormStore(options);
6226
+ const store = ref.current;
6227
+ React8.useSyncExternalStore(store.subscribe, store.getRootSnapshot, store.getRootSnapshot);
6228
+ const make = React8.useCallback(
6229
+ (kind) => (name, rules) => {
6230
+ if (rules !== void 0) store.setRule(name, rules);
6231
+ return buildBindings(store, name, kind, store.getFieldSnapshot(name));
6232
+ },
6233
+ [store]
6234
+ );
6235
+ return {
6236
+ store,
6237
+ values: store.values,
6238
+ errors: store.errors,
6239
+ touched: store.touched,
6240
+ submitted: store.submitted,
6241
+ isSubmitting: store.validating,
6242
+ isValid: store.isValid,
6243
+ getValue: store.getValue,
6244
+ getValues: store.getValues,
6245
+ setValue: (name, value) => store.setValue(name, value),
6246
+ setValues: (patch) => store.setValues(patch),
6247
+ setError: store.setError,
6248
+ validateField: (name) => store.validateField(name),
6249
+ validateAll: () => store.validateAll(),
6250
+ reset: store.reset,
6251
+ field: make("value"),
6252
+ fieldNative: make("native"),
6253
+ fieldChecked: make("checked"),
6254
+ fieldTarget: make("target")
6255
+ };
6256
+ }
6257
+ var FormContext = React8.createContext(null);
6258
+ function useFormStore() {
6259
+ const store = React8.useContext(FormContext);
6260
+ if (!store) {
6261
+ throw new Error("useFormStore must be used within a <Form>. Did you forget to wrap your fields?");
6262
+ }
6263
+ return store;
6264
+ }
6265
+ function Form({
6266
+ form,
6267
+ onFinish,
6268
+ onFinishFailed,
6269
+ action,
6270
+ children,
6271
+ ...rest
6272
+ }) {
6273
+ const ref = React8.useRef(null);
6274
+ const bypass = React8.useRef(false);
6275
+ const handleSubmit = async (e) => {
6276
+ if (bypass.current) {
6277
+ bypass.current = false;
6278
+ return;
6279
+ }
6280
+ e.preventDefault();
6281
+ const store = form.store;
6282
+ store.setSubmitted(true);
6283
+ const errors = await store.validateAll();
6284
+ const hasError = Object.values(errors).some(Boolean);
6285
+ if (hasError) {
6286
+ onFinishFailed?.(errors, store.getValues());
6287
+ focusFirstError(ref.current, errors);
6288
+ return;
6289
+ }
6290
+ if (onFinish) {
6291
+ await onFinish(store.getValues());
6292
+ return;
6293
+ }
6294
+ if (typeof action === "function") {
6295
+ action(new FormData(ref.current));
6296
+ return;
6297
+ }
6298
+ if (typeof action === "string") {
6299
+ bypass.current = true;
6300
+ ref.current.requestSubmit();
6301
+ }
6302
+ };
6303
+ return /* @__PURE__ */ jsxRuntime.jsx(FormContext.Provider, { value: form.store, children: /* @__PURE__ */ jsxRuntime.jsx(
6304
+ "form",
6305
+ {
6306
+ ref,
6307
+ noValidate: true,
6308
+ action: typeof action === "string" ? action : void 0,
6309
+ onSubmit: handleSubmit,
6310
+ ...rest,
6311
+ children
6312
+ }
6313
+ ) });
6314
+ }
6315
+ function focusFirstError(formEl, errors) {
6316
+ if (!formEl) return;
6317
+ const firstName = Object.keys(errors).find((k) => errors[k]);
6318
+ if (!firstName) return;
6319
+ const el = formEl.querySelector(`[name="${CSS.escape(firstName)}"], #${CSS.escape(firstName)}`);
6320
+ el?.focus();
6321
+ }
6322
+ function useFormField(name, options = {}) {
6323
+ const store = useFormStore();
6324
+ const { kind = "value", rules } = options;
6325
+ if (rules !== void 0 && store.getRule(name) !== rules) store.setRule(name, rules);
6326
+ React8.useEffect(() => {
6327
+ return () => {
6328
+ if (rules !== void 0) store.removeRule(name);
6329
+ };
6330
+ }, [store, name]);
6331
+ const snap = React8.useSyncExternalStore(
6332
+ store.subscribe,
6333
+ () => store.getFieldSnapshot(name)
6334
+ );
6335
+ return buildBindings(store, name, kind, snap);
6336
+ }
6337
+ function FormField({ name, kind, rules, children }) {
6338
+ const field = useFormField(name, { kind, rules });
6339
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: children(field) });
6340
+ }
6341
+ function useFieldArray(name) {
6342
+ const store = useFormStore();
6343
+ React8.useSyncExternalStore(store.subscribe, store.getRootSnapshot, store.getRootSnapshot);
6344
+ const arr = store.getValue(name) ?? [];
6345
+ const keys = store.getKeys(name);
6346
+ return {
6347
+ fields: arr.map((_, i) => ({ key: keys[i], name: `${name}.${i}`, index: i })),
6348
+ append: (item = {}) => store.arrayAppend(name, item),
6349
+ remove: (index) => store.arrayRemove(name, index),
6350
+ move: (from, to) => store.arrayMove(name, from, to),
6351
+ replace: (items) => store.setValue(name, items, { validate: false })
6352
+ };
6353
+ }
6354
+
5665
6355
  Object.defineProperty(exports, "COLORS", {
5666
6356
  enumerable: true,
5667
6357
  get: function () { return chunk255PCZIW_cjs.colors_default; }
@@ -5694,8 +6384,14 @@ exports.Drawer = Drawer;
5694
6384
  exports.Dropdown = Dropdown;
5695
6385
  exports.FadingBase = FadingBase;
5696
6386
  exports.Field = Field;
6387
+ exports.FieldHelpIcon = FieldHelpIcon;
6388
+ exports.FieldLabel = FieldLabel;
5697
6389
  exports.FileInput = FileInput;
5698
6390
  exports.Flex = Flex;
6391
+ exports.Form = Form;
6392
+ exports.FormContext = FormContext;
6393
+ exports.FormField = FormField;
6394
+ exports.FormStore = FormStore;
5699
6395
  exports.Grid = Grid2;
5700
6396
  exports.GridCard = GridCard;
5701
6397
  exports.Icon = icons_default;
@@ -5738,6 +6434,13 @@ exports.TreeSelect = TreeSelect;
5738
6434
  exports.Typography = Typography;
5739
6435
  exports.Wizard = Wizard;
5740
6436
  exports.fieldShell = fieldShell;
6437
+ exports.isRequired = isRequired;
6438
+ exports.patterns = patterns;
6439
+ exports.runFieldRules = runFieldRules;
6440
+ exports.useFieldArray = useFieldArray;
6441
+ exports.useForm = useForm;
6442
+ exports.useFormField = useFormField;
6443
+ exports.useFormStore = useFormStore;
5741
6444
  exports.useNotification = useNotification;
5742
6445
  //# sourceMappingURL=index.cjs.map
5743
6446
  //# sourceMappingURL=index.cjs.map