@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.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { colors_default } from './chunk-GKXP6OJJ.js';
2
2
  export { colors_default as COLORS, PALETTE as palette, semanticTokens, vars } from './chunk-GKXP6OJJ.js';
3
3
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
- import React8, { createContext, useState, useEffect, useMemo, useCallback, useContext, useRef, useId, useLayoutEffect } from 'react';
4
+ import React8, { createContext, useState, useEffect, useMemo, useCallback, useContext, useRef, useId, useLayoutEffect, useSyncExternalStore } from 'react';
5
5
  import { createPortal } from 'react-dom';
6
6
  import * as AvatarPrimitive from '@radix-ui/react-avatar';
7
7
  import * as Dialog from '@radix-ui/react-dialog';
@@ -40,7 +40,7 @@ var FleetIcon = ({ color = "#fff", size = 30 }) => /* @__PURE__ */ jsxs("svg", {
40
40
  /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsx("clipPath", { id: "clip0_327_836", children: /* @__PURE__ */ jsx("rect", { width: "30", height: "30", fill: color }) }) })
41
41
  ] });
42
42
  var Performance = ({ color = "#fff", size = 30 }) => /* @__PURE__ */ jsx("svg", { width: size, height: size, viewBox: "0 0 30 30", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: /* @__PURE__ */ 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 }) });
43
- var Map = ({ color = "#fff", size = 30 }) => /* @__PURE__ */ jsxs("svg", { width: size, height: size, viewBox: "0 0 34 30", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
43
+ var Map2 = ({ color = "#fff", size = 30 }) => /* @__PURE__ */ jsxs("svg", { width: size, height: size, viewBox: "0 0 34 30", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
44
44
  /* @__PURE__ */ jsx("g", { clipPath: "url(#clip0_327_830)", children: /* @__PURE__ */ 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 }) }),
45
45
  /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsx("clipPath", { id: "clip0_327_830", children: /* @__PURE__ */ jsx("rect", { width: "33.75", height: "30", fill: color }) }) })
46
46
  ] });
@@ -127,7 +127,7 @@ Icon.Minus = Minus;
127
127
  Icon.Dashboard = Dashboard;
128
128
  Icon.FleetIcon = FleetIcon;
129
129
  Icon.Performance = Performance;
130
- Icon.Map = Map;
130
+ Icon.Map = Map2;
131
131
  Icon.CharterParty = CharterParty;
132
132
  Icon.Compliance = Compliance;
133
133
  Icon.Applications = Applications;
@@ -2144,6 +2144,51 @@ function fieldShell({
2144
2144
  "placeholder:text-foreground-muted"
2145
2145
  ].filter(Boolean).join(" ");
2146
2146
  }
2147
+ function FieldHelpIcon({ text }) {
2148
+ return /* @__PURE__ */ jsx(Tooltip, { title: text, placement: "top", children: /* @__PURE__ */ jsx(
2149
+ "button",
2150
+ {
2151
+ type: "button",
2152
+ "aria-label": "More information",
2153
+ className: "inline-flex items-center justify-center rounded-full text-foreground-muted transition-colors hover:text-foreground focus:outline-none focus-visible:text-accent",
2154
+ children: /* @__PURE__ */ 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: [
2155
+ /* @__PURE__ */ jsx("circle", { cx: "8", cy: "8", r: "6.25" }),
2156
+ /* @__PURE__ */ jsx("path", { strokeLinecap: "round", d: "M8 7.4v3.4" }),
2157
+ /* @__PURE__ */ jsx("circle", { cx: "8", cy: "5.1", r: "0.65", fill: "currentColor", stroke: "none" })
2158
+ ] })
2159
+ }
2160
+ ) });
2161
+ }
2162
+ function FieldLabel({
2163
+ label,
2164
+ htmlFor,
2165
+ required,
2166
+ helperText,
2167
+ horizontal = false,
2168
+ style,
2169
+ width,
2170
+ className = ""
2171
+ }) {
2172
+ if (label == null && helperText == null) return null;
2173
+ return /* @__PURE__ */ jsxs(
2174
+ "div",
2175
+ {
2176
+ style: { width: horizontal ? width : void 0, ...style },
2177
+ className: [
2178
+ "flex items-center gap-1",
2179
+ horizontal ? "mt-2 flex-shrink-0 whitespace-nowrap" : "",
2180
+ className
2181
+ ].filter(Boolean).join(" "),
2182
+ children: [
2183
+ label != null && /* @__PURE__ */ jsxs("label", { htmlFor, className: "text-sm font-medium text-foreground select-none", children: [
2184
+ label,
2185
+ required && /* @__PURE__ */ jsx("span", { className: "text-status-error ml-0.5", "aria-hidden": "true", children: "*" })
2186
+ ] }),
2187
+ helperText != null && /* @__PURE__ */ jsx(FieldHelpIcon, { text: helperText })
2188
+ ]
2189
+ }
2190
+ );
2191
+ }
2147
2192
  function Field({
2148
2193
  label,
2149
2194
  htmlFor,
@@ -2151,6 +2196,7 @@ function Field({
2151
2196
  errorMessage,
2152
2197
  layout = "vertical",
2153
2198
  required,
2199
+ helperText,
2154
2200
  labelStyle,
2155
2201
  labelWidth,
2156
2202
  className = "",
@@ -2167,21 +2213,16 @@ function Field({
2167
2213
  className
2168
2214
  ].filter(Boolean).join(" "),
2169
2215
  children: [
2170
- label && /* @__PURE__ */ jsxs(
2171
- "label",
2216
+ /* @__PURE__ */ jsx(
2217
+ FieldLabel,
2172
2218
  {
2219
+ label,
2173
2220
  htmlFor,
2174
- style: { width: horizontal ? labelWidth : void 0, ...labelStyle },
2175
- className: [
2176
- "text-sm font-medium text-foreground select-none",
2177
- // In horizontal layout the label must not wrap onto
2178
- // multiple lines (e.g. "Report date", "Select option").
2179
- horizontal ? "mt-2 flex-shrink-0 whitespace-nowrap" : ""
2180
- ].filter(Boolean).join(" "),
2181
- children: [
2182
- label,
2183
- required && /* @__PURE__ */ jsx("span", { className: "text-status-error ml-0.5", "aria-hidden": "true", children: "*" })
2184
- ]
2221
+ required,
2222
+ helperText,
2223
+ horizontal,
2224
+ style: labelStyle,
2225
+ width: labelWidth
2185
2226
  }
2186
2227
  ),
2187
2228
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col min-w-0 flex-1", children: [
@@ -2193,8 +2234,8 @@ function Field({
2193
2234
  );
2194
2235
  }
2195
2236
  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" }) });
2196
- var SearchInput = React8.forwardRef(function SearchInput2({ value, onChange, disabled, label, htmlFor, placeholder, name, inputStyle, style, layout = "vertical", size = "md", icon }, ref) {
2197
- return /* @__PURE__ */ jsx(Field, { label, htmlFor, layout, children: /* @__PURE__ */ jsxs(
2237
+ var SearchInput = React8.forwardRef(function SearchInput2({ value, onChange, disabled, label, htmlFor, placeholder, name, inputStyle, style, layout = "vertical", size = "md", icon, helperText }, ref) {
2238
+ return /* @__PURE__ */ jsx(Field, { label, htmlFor, layout, helperText, children: /* @__PURE__ */ jsxs(
2198
2239
  "div",
2199
2240
  {
2200
2241
  className: `flex items-center ${fieldShell({ size, disabled, focusWithin: true })}`,
@@ -2241,6 +2282,90 @@ function Tag({ children, onRemove, removeLabel, disabled }) {
2241
2282
  )
2242
2283
  ] });
2243
2284
  }
2285
+ function MultiTagRow({
2286
+ values,
2287
+ disabled,
2288
+ labelFor,
2289
+ onRemove
2290
+ }) {
2291
+ const wrapRef = useRef(null);
2292
+ const measureRef = useRef(null);
2293
+ const [visibleCount, setVisibleCount] = useState(values.length);
2294
+ const key = values.map(String).join("|");
2295
+ useLayoutEffect(() => {
2296
+ const wrap = wrapRef.current;
2297
+ const measure = measureRef.current;
2298
+ if (!wrap || !measure) return;
2299
+ const GAP = 6;
2300
+ const recompute = () => {
2301
+ const avail = wrap.clientWidth;
2302
+ const tagEls = Array.from(measure.querySelectorAll("[data-mt]"));
2303
+ const moreEl = measure.querySelector("[data-mm]");
2304
+ const widths = tagEls.map((e) => e.offsetWidth);
2305
+ const moreW = moreEl ? moreEl.offsetWidth : 0;
2306
+ if (widths.length === 0) {
2307
+ setVisibleCount(0);
2308
+ return;
2309
+ }
2310
+ let used = 0;
2311
+ let count = 0;
2312
+ for (let i = 0; i < widths.length; i++) {
2313
+ const w = widths[i] + (i > 0 ? GAP : 0);
2314
+ if (used + w <= avail) {
2315
+ used += w;
2316
+ count++;
2317
+ } else break;
2318
+ }
2319
+ if (count < widths.length) {
2320
+ while (count > 0) {
2321
+ let t = 0;
2322
+ for (let i = 0; i < count; i++) t += widths[i] + (i > 0 ? GAP : 0);
2323
+ t += GAP + moreW;
2324
+ if (t <= avail) break;
2325
+ count--;
2326
+ }
2327
+ }
2328
+ setVisibleCount(count);
2329
+ };
2330
+ recompute();
2331
+ const ro = new ResizeObserver(recompute);
2332
+ ro.observe(wrap);
2333
+ return () => ro.disconnect();
2334
+ }, [key]);
2335
+ const hidden = values.length - visibleCount;
2336
+ const moreChip = (n) => /* @__PURE__ */ 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: [
2337
+ "+",
2338
+ n,
2339
+ " more"
2340
+ ] });
2341
+ return /* @__PURE__ */ jsxs("div", { ref: wrapRef, className: "relative flex-1 min-w-0 flex flex-nowrap items-center gap-1.5 overflow-hidden", children: [
2342
+ /* @__PURE__ */ jsxs(
2343
+ "div",
2344
+ {
2345
+ ref: measureRef,
2346
+ "aria-hidden": "true",
2347
+ className: "absolute invisible pointer-events-none flex flex-nowrap items-center gap-1.5",
2348
+ style: { left: -9999, top: -9999 },
2349
+ children: [
2350
+ values.map((val) => /* @__PURE__ */ jsx("span", { "data-mt": true, children: /* @__PURE__ */ jsx(Tag, { removeLabel: "x", onRemove: () => {
2351
+ }, children: labelFor(val) }) }, `m-${val}`)),
2352
+ /* @__PURE__ */ jsx("span", { "data-mm": true, children: moreChip(values.length) })
2353
+ ]
2354
+ }
2355
+ ),
2356
+ values.slice(0, visibleCount).map((val) => /* @__PURE__ */ jsx(
2357
+ Tag,
2358
+ {
2359
+ disabled,
2360
+ removeLabel: `Remove ${labelFor(val)}`,
2361
+ onRemove: () => onRemove(val),
2362
+ children: labelFor(val)
2363
+ },
2364
+ String(val)
2365
+ )),
2366
+ hidden > 0 && moreChip(hidden)
2367
+ ] });
2368
+ }
2244
2369
  function Dropdown({
2245
2370
  isMultiselect = false,
2246
2371
  hasSearch = true,
@@ -2250,6 +2375,8 @@ function Dropdown({
2250
2375
  onChange,
2251
2376
  disabled,
2252
2377
  layout = "horizontal",
2378
+ helperText,
2379
+ required,
2253
2380
  errorMessage,
2254
2381
  style = {},
2255
2382
  htmlFor,
@@ -2310,13 +2437,15 @@ function Dropdown({
2310
2437
  {
2311
2438
  className: `flex ${layout === "vertical" ? "flex-col gap-1.5" : "flex-row items-start gap-3"}`,
2312
2439
  children: [
2313
- label && /* @__PURE__ */ jsx(
2314
- "label",
2440
+ /* @__PURE__ */ jsx(
2441
+ FieldLabel,
2315
2442
  {
2316
- className: `text-sm font-medium select-none text-foreground ${layout === "horizontal" ? "mt-2 flex-shrink-0 whitespace-nowrap" : ""}`,
2443
+ label,
2317
2444
  htmlFor,
2318
- style: labelStyle,
2319
- children: label
2445
+ required,
2446
+ helperText,
2447
+ horizontal: layout === "horizontal",
2448
+ style: labelStyle
2320
2449
  }
2321
2450
  ),
2322
2451
  /* @__PURE__ */ jsxs(Popover.Root, { open: open && !disabled, onOpenChange: (o) => !disabled && setOpen(o), children: [
@@ -2329,8 +2458,8 @@ function Dropdown({
2329
2458
  "aria-haspopup": "listbox",
2330
2459
  "aria-invalid": hasError || void 0,
2331
2460
  "aria-describedby": hasError ? errorId : void 0,
2332
- style,
2333
- 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 })}`,
2461
+ style: { width: 240, ...style },
2462
+ 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 })}`,
2334
2463
  tabIndex: disabled ? -1 : 0,
2335
2464
  onKeyDown: (e) => {
2336
2465
  if (disabled) return;
@@ -2340,16 +2469,15 @@ function Dropdown({
2340
2469
  }
2341
2470
  },
2342
2471
  children: [
2343
- /* @__PURE__ */ 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__ */ jsx("span", { className: "text-foreground-muted text-sm", children: placeholder }) : Array.isArray(value) ? value.map((val) => /* @__PURE__ */ jsx(
2344
- Tag,
2472
+ !value || Array.isArray(value) && value.length === 0 ? /* @__PURE__ */ jsx("span", { className: "flex-1 min-w-0 truncate text-foreground-muted text-sm", children: placeholder }) : Array.isArray(value) ? /* @__PURE__ */ jsx(
2473
+ MultiTagRow,
2345
2474
  {
2475
+ values: value,
2346
2476
  disabled,
2347
- removeLabel: `Remove ${labelFor(val)}`,
2348
- onRemove: () => removeSelected(val),
2349
- children: labelFor(val)
2350
- },
2351
- String(val)
2352
- )) : /* @__PURE__ */ jsx(
2477
+ labelFor,
2478
+ onRemove: removeSelected
2479
+ }
2480
+ ) : /* @__PURE__ */ jsx("div", { className: "flex-1 min-w-0 flex items-center overflow-hidden", children: /* @__PURE__ */ jsx(
2353
2481
  Tag,
2354
2482
  {
2355
2483
  disabled,
@@ -3289,6 +3417,7 @@ function TextInput({
3289
3417
  size = "md",
3290
3418
  onBlur,
3291
3419
  errorMessage,
3420
+ helperText,
3292
3421
  required,
3293
3422
  prefix,
3294
3423
  suffix
@@ -3321,9 +3450,9 @@ function TextInput({
3321
3450
  htmlFor,
3322
3451
  errorId,
3323
3452
  errorMessage,
3453
+ helperText,
3324
3454
  layout,
3325
3455
  required,
3326
- className: style ? void 0 : void 0,
3327
3456
  children: hasAdornment ? /* @__PURE__ */ jsxs(
3328
3457
  "div",
3329
3458
  {
@@ -3350,6 +3479,7 @@ function NumberInput({
3350
3479
  layout = "vertical",
3351
3480
  size = "md",
3352
3481
  errorMessage,
3482
+ helperText,
3353
3483
  required,
3354
3484
  inputStyle,
3355
3485
  labelStyle,
@@ -3398,6 +3528,7 @@ function NumberInput({
3398
3528
  htmlFor,
3399
3529
  errorId,
3400
3530
  errorMessage,
3531
+ helperText,
3401
3532
  layout,
3402
3533
  required,
3403
3534
  labelStyle,
@@ -3483,6 +3614,7 @@ function Password({
3483
3614
  size = "md",
3484
3615
  onBlur,
3485
3616
  errorMessage,
3617
+ helperText,
3486
3618
  required,
3487
3619
  showIcon,
3488
3620
  hideIcon
@@ -3497,6 +3629,7 @@ function Password({
3497
3629
  htmlFor,
3498
3630
  errorId,
3499
3631
  errorMessage,
3632
+ helperText,
3500
3633
  layout,
3501
3634
  required,
3502
3635
  children: /* @__PURE__ */ jsxs(
@@ -3551,10 +3684,14 @@ function Checkbox({
3551
3684
  errorMessage,
3552
3685
  disabled = false,
3553
3686
  layout = "horizontal",
3554
- labelPosition = "right"
3687
+ labelPosition = "right",
3688
+ helperText,
3689
+ required
3555
3690
  }) {
3556
3691
  const isChecked = checked ?? value ?? false;
3557
3692
  const labelFirst = labelPosition === "left";
3693
+ const errorId = useId();
3694
+ const hasError = errorMessage != null;
3558
3695
  const box = /* @__PURE__ */ jsx(
3559
3696
  CheckboxPrimitive.Root,
3560
3697
  {
@@ -3573,30 +3710,38 @@ function Checkbox({
3573
3710
  "disabled:cursor-not-allowed"
3574
3711
  ].join(" "),
3575
3712
  "aria-label": typeof label === "string" ? label : void 0,
3713
+ "aria-invalid": hasError || void 0,
3714
+ "aria-describedby": hasError ? errorId : void 0,
3576
3715
  children: /* @__PURE__ */ jsx(CheckboxPrimitive.Indicator, { className: "flex items-center justify-center data-[state=checked]:animate-check-pop", children: /* @__PURE__ */ jsx("svg", { width: "11", height: "9", viewBox: "0 0 11 9", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M1 4.5L4 7.5L10 1", stroke: "white", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round" }) }) })
3577
3716
  }
3578
3717
  );
3579
- const labelEl = label && /* @__PURE__ */ jsx("span", { className: "text-sm text-foreground-secondary select-none leading-snug", children: label });
3718
+ const labelEl = label && /* @__PURE__ */ jsxs("span", { className: "text-sm text-foreground-secondary select-none leading-snug", children: [
3719
+ label,
3720
+ required && /* @__PURE__ */ jsx("span", { className: "text-status-error ml-0.5", "aria-hidden": "true", children: "*" })
3721
+ ] });
3580
3722
  return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
3581
- /* @__PURE__ */ jsx(
3582
- "label",
3583
- {
3584
- htmlFor,
3585
- className: [
3586
- "inline-flex",
3587
- layout === "vertical" ? "flex-col items-start gap-1.5" : "flex-row items-center gap-2.5",
3588
- disabled ? "cursor-not-allowed opacity-50" : "cursor-pointer"
3589
- ].join(" "),
3590
- children: labelFirst ? /* @__PURE__ */ jsxs(Fragment, { children: [
3591
- labelEl,
3592
- box
3593
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
3594
- box,
3595
- labelEl
3596
- ] })
3597
- }
3598
- ),
3599
- errorMessage && /* @__PURE__ */ jsx("span", { className: "text-xs text-status-error mt-0.5", children: errorMessage })
3723
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
3724
+ /* @__PURE__ */ jsx(
3725
+ "label",
3726
+ {
3727
+ htmlFor,
3728
+ className: [
3729
+ "inline-flex",
3730
+ layout === "vertical" ? "flex-col items-start gap-1.5" : "flex-row items-center gap-2.5",
3731
+ disabled ? "cursor-not-allowed opacity-50" : "cursor-pointer"
3732
+ ].join(" "),
3733
+ children: labelFirst ? /* @__PURE__ */ jsxs(Fragment, { children: [
3734
+ labelEl,
3735
+ box
3736
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
3737
+ box,
3738
+ labelEl
3739
+ ] })
3740
+ }
3741
+ ),
3742
+ helperText != null && /* @__PURE__ */ jsx(FieldHelpIcon, { text: helperText })
3743
+ ] }),
3744
+ errorMessage && /* @__PURE__ */ jsx("span", { id: errorId, className: "text-xs text-status-error mt-0.5", children: errorMessage })
3600
3745
  ] });
3601
3746
  }
3602
3747
  var DOT_SIZE = {
@@ -3621,6 +3766,7 @@ function RadioGroup({
3621
3766
  size = "md",
3622
3767
  disabled,
3623
3768
  required,
3769
+ helperText,
3624
3770
  errorMessage
3625
3771
  }) {
3626
3772
  const errorId = useId();
@@ -3635,6 +3781,7 @@ function RadioGroup({
3635
3781
  errorId,
3636
3782
  errorMessage,
3637
3783
  required,
3784
+ helperText,
3638
3785
  children: /* @__PURE__ */ jsx(
3639
3786
  RadioGroupPrimitive.Root,
3640
3787
  {
@@ -3685,7 +3832,8 @@ function RadioGroup({
3685
3832
  ]
3686
3833
  }
3687
3834
  );
3688
- return /* @__PURE__ */ jsx("div", { className: "flex items-start gap-2.5", children: labelFirst ? /* @__PURE__ */ jsxs(Fragment, { children: [
3835
+ const rowClass = labelFirst && layout === "vertical" ? "grid grid-cols-[1fr_auto] items-start gap-2.5" : "flex items-start gap-2.5";
3836
+ return /* @__PURE__ */ jsx("div", { className: rowClass, children: labelFirst ? /* @__PURE__ */ jsxs(Fragment, { children: [
3689
3837
  labelEl,
3690
3838
  dot
3691
3839
  ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -3699,28 +3847,73 @@ function RadioGroup({
3699
3847
  );
3700
3848
  }
3701
3849
  function Switch({
3702
- checked = false,
3850
+ checked,
3851
+ defaultChecked = false,
3703
3852
  onChange,
3704
3853
  checkedIcon,
3705
- uncheckedIcon
3854
+ uncheckedIcon,
3855
+ label,
3856
+ layout = "horizontal",
3857
+ helperText,
3858
+ offLabel,
3859
+ onLabel,
3860
+ name,
3861
+ required,
3862
+ disabled,
3863
+ errorMessage
3706
3864
  }) {
3707
3865
  const id = useId();
3708
- return /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx("label", { htmlFor: id, className: "flex items-center cursor-pointer select-none", children: /* @__PURE__ */ jsx(
3709
- SwitchPrimitive.Root,
3866
+ const errorId = useId();
3867
+ const hasError = errorMessage != null;
3868
+ const isControlled = checked !== void 0;
3869
+ const [internal, setInternal] = useState(defaultChecked);
3870
+ const isOn = isControlled ? checked : internal;
3871
+ const handle = (c) => {
3872
+ if (!isControlled) setInternal(c);
3873
+ onChange?.({ target: { checked: c, name } });
3874
+ };
3875
+ const stateLabel = (active) => [
3876
+ "text-sm select-none transition-colors",
3877
+ active ? "text-foreground font-medium" : "text-foreground-muted",
3878
+ disabled ? "opacity-50" : "cursor-pointer"
3879
+ ].filter(Boolean).join(" ");
3880
+ return /* @__PURE__ */ jsx(
3881
+ Field,
3710
3882
  {
3711
- id,
3712
- checked,
3713
- onCheckedChange: (c) => onChange?.({ target: { checked: c } }),
3714
- 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",
3715
- children: /* @__PURE__ */ jsx(
3716
- SwitchPrimitive.Thumb,
3717
- {
3718
- 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]",
3719
- children: checkedIcon && uncheckedIcon ? checked ? /* @__PURE__ */ jsx("span", { className: "flex items-center justify-center w-3 h-3", children: checkedIcon }) : /* @__PURE__ */ jsx("span", { className: "flex items-center justify-center w-3 h-3", children: uncheckedIcon }) : null
3720
- }
3721
- )
3883
+ label,
3884
+ htmlFor: id,
3885
+ errorId,
3886
+ errorMessage,
3887
+ layout,
3888
+ required,
3889
+ helperText,
3890
+ children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2.5", children: [
3891
+ offLabel != null && /* @__PURE__ */ jsx("label", { htmlFor: id, className: stateLabel(!isOn), children: offLabel }),
3892
+ /* @__PURE__ */ jsx(
3893
+ SwitchPrimitive.Root,
3894
+ {
3895
+ id,
3896
+ name,
3897
+ checked: isOn,
3898
+ onCheckedChange: handle,
3899
+ disabled,
3900
+ required,
3901
+ "aria-invalid": hasError || void 0,
3902
+ "aria-describedby": hasError ? errorId : void 0,
3903
+ 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",
3904
+ children: /* @__PURE__ */ jsx(
3905
+ SwitchPrimitive.Thumb,
3906
+ {
3907
+ 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]",
3908
+ children: checkedIcon && uncheckedIcon ? /* @__PURE__ */ jsx("span", { className: "flex items-center justify-center w-3 h-3", children: isOn ? checkedIcon : uncheckedIcon }) : null
3909
+ }
3910
+ )
3911
+ }
3912
+ ),
3913
+ onLabel != null && /* @__PURE__ */ jsx("label", { htmlFor: id, className: stateLabel(isOn), children: onLabel })
3914
+ ] })
3722
3915
  }
3723
- ) }) });
3916
+ );
3724
3917
  }
3725
3918
  function AutoComplete({
3726
3919
  disabled,
@@ -3739,6 +3932,7 @@ function AutoComplete({
3739
3932
  size = "md",
3740
3933
  icon,
3741
3934
  errorMessage,
3935
+ helperText,
3742
3936
  required,
3743
3937
  htmlFor
3744
3938
  }) {
@@ -3797,6 +3991,7 @@ function AutoComplete({
3797
3991
  htmlFor,
3798
3992
  errorId,
3799
3993
  errorMessage,
3994
+ helperText,
3800
3995
  layout,
3801
3996
  required,
3802
3997
  children: /* @__PURE__ */ jsxs(Popover.Root, { open: open && !disabled, onOpenChange: (o) => !disabled && setOpen(o), children: [
@@ -3896,6 +4091,8 @@ function TreeSelect({
3896
4091
  onChange,
3897
4092
  disabled,
3898
4093
  layout = "horizontal",
4094
+ helperText,
4095
+ required,
3899
4096
  errorMessage,
3900
4097
  style,
3901
4098
  htmlFor,
@@ -3981,12 +4178,14 @@ function TreeSelect({
3981
4178
  };
3982
4179
  return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
3983
4180
  /* @__PURE__ */ jsxs("div", { className: `flex ${layout === "vertical" ? "flex-col gap-1" : "flex-row items-center gap-2"}`, children: [
3984
- label && /* @__PURE__ */ jsx(
3985
- "label",
4181
+ /* @__PURE__ */ jsx(
4182
+ FieldLabel,
3986
4183
  {
3987
- className: "text-sm font-medium ml-1 max-content select-none text-foreground",
4184
+ label,
3988
4185
  htmlFor,
3989
- children: label
4186
+ required,
4187
+ helperText,
4188
+ horizontal: layout === "horizontal"
3990
4189
  }
3991
4190
  ),
3992
4191
  /* @__PURE__ */ jsxs(Popover.Root, { open: open && !disabled, onOpenChange: (o) => !disabled && setOpen(o), children: [
@@ -4148,6 +4347,7 @@ function FileInput({
4148
4347
  hint,
4149
4348
  maxSize,
4150
4349
  errorMessage,
4350
+ helperText,
4151
4351
  disabled,
4152
4352
  required,
4153
4353
  icon
@@ -4194,6 +4394,7 @@ function FileInput({
4194
4394
  htmlFor,
4195
4395
  errorId,
4196
4396
  errorMessage: effectiveError,
4397
+ helperText,
4197
4398
  required,
4198
4399
  children: [
4199
4400
  /* @__PURE__ */ jsxs(
@@ -4332,6 +4533,8 @@ function DatePicker({
4332
4533
  htmlFor,
4333
4534
  name: _name,
4334
4535
  layout = "horizontal",
4536
+ helperText,
4537
+ required,
4335
4538
  disabled,
4336
4539
  errorMessage,
4337
4540
  min,
@@ -4423,12 +4626,14 @@ function DatePicker({
4423
4626
  const displayValue = value ? format(value) : "";
4424
4627
  return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
4425
4628
  /* @__PURE__ */ jsxs("div", { className: `flex ${layout === "vertical" ? "flex-col gap-1.5" : "flex-row items-start gap-3"}`, children: [
4426
- label && /* @__PURE__ */ jsx(
4427
- "label",
4629
+ /* @__PURE__ */ jsx(
4630
+ FieldLabel,
4428
4631
  {
4429
- className: `text-sm font-medium select-none text-foreground ${layout === "horizontal" ? "mt-2 flex-shrink-0 whitespace-nowrap" : ""}`,
4632
+ label,
4430
4633
  htmlFor,
4431
- children: label
4634
+ required,
4635
+ helperText,
4636
+ horizontal: layout === "horizontal"
4432
4637
  }
4433
4638
  ),
4434
4639
  /* @__PURE__ */ jsxs(Popover.Root, { open: open && !disabled, onOpenChange: (o) => !disabled && setOpen(o), children: [
@@ -4647,6 +4852,7 @@ function TextArea({
4647
4852
  showCount = false,
4648
4853
  resize,
4649
4854
  errorMessage,
4855
+ helperText,
4650
4856
  required,
4651
4857
  style,
4652
4858
  inputStyle
@@ -4672,6 +4878,7 @@ function TextArea({
4672
4878
  htmlFor,
4673
4879
  errorId,
4674
4880
  errorMessage,
4881
+ helperText,
4675
4882
  layout,
4676
4883
  required,
4677
4884
  children: [
@@ -4712,51 +4919,84 @@ function SegmentedControl({
4712
4919
  size = "md",
4713
4920
  fullWidth = false,
4714
4921
  disabled,
4922
+ label,
4923
+ layout = "vertical",
4924
+ helperText,
4925
+ name,
4926
+ required,
4927
+ errorMessage,
4715
4928
  "aria-label": ariaLabel
4716
4929
  }) {
4717
4930
  const sz = SIZE[size];
4718
- return /* @__PURE__ */ jsx(
4719
- ToggleGroup.Root,
4931
+ const groupId = useId();
4932
+ const errorId = useId();
4933
+ const hasError = errorMessage != null;
4934
+ const isControlled = value !== void 0;
4935
+ const [internal, setInternal] = useState(defaultValue);
4936
+ const current = isControlled ? value : internal;
4937
+ const handle = (v) => {
4938
+ if (!v) return;
4939
+ if (!isControlled) setInternal(v);
4940
+ onChange?.(v);
4941
+ };
4942
+ return /* @__PURE__ */ jsxs(
4943
+ Field,
4720
4944
  {
4721
- type: "single",
4722
- value,
4723
- defaultValue,
4724
- onValueChange: (v) => {
4725
- if (v) onChange?.(v);
4726
- },
4727
- disabled,
4728
- "aria-label": ariaLabel,
4729
- className: [
4730
- "inline-flex items-center gap-1 rounded-lg border border-border bg-surface-raised p-1",
4731
- sz.h,
4732
- fullWidth ? "flex w-full" : "",
4733
- disabled ? "opacity-60 cursor-not-allowed" : ""
4734
- ].filter(Boolean).join(" "),
4735
- children: options.map((opt) => /* @__PURE__ */ jsxs(
4736
- ToggleGroup.Item,
4737
- {
4738
- value: opt.value,
4739
- disabled: opt.disabled,
4740
- className: [
4741
- "inline-flex items-center justify-center gap-1.5 rounded-md select-none whitespace-nowrap",
4742
- "transition-colors duration-150 h-full",
4743
- sz.text,
4744
- sz.pad,
4745
- fullWidth ? "flex-1" : "",
4746
- // Resting: muted text, transparent. Hover lifts the text.
4747
- "text-foreground-secondary hover:text-foreground",
4748
- // Active: surface-white pill + accent text + subtle shadow.
4749
- "data-[state=on]:bg-surface data-[state=on]:text-accent data-[state=on]:shadow-sm",
4750
- "focus:outline-none focus-visible:ring-[3px] focus-visible:ring-focus-ring",
4751
- "disabled:opacity-40 disabled:cursor-not-allowed"
4752
- ].filter(Boolean).join(" "),
4753
- children: [
4754
- opt.icon && /* @__PURE__ */ jsx("span", { className: "flex-shrink-0", children: opt.icon }),
4755
- opt.label
4756
- ]
4757
- },
4758
- opt.value
4759
- ))
4945
+ label,
4946
+ htmlFor: groupId,
4947
+ errorId,
4948
+ errorMessage,
4949
+ layout,
4950
+ required,
4951
+ helperText,
4952
+ children: [
4953
+ name && /* @__PURE__ */ jsx("input", { type: "hidden", name, value: current ?? "" }),
4954
+ /* @__PURE__ */ jsx(
4955
+ ToggleGroup.Root,
4956
+ {
4957
+ id: groupId,
4958
+ type: "single",
4959
+ value: current,
4960
+ onValueChange: handle,
4961
+ disabled,
4962
+ "aria-label": ariaLabel ?? (typeof label === "string" ? label : void 0),
4963
+ "aria-invalid": hasError || void 0,
4964
+ "aria-describedby": hasError ? errorId : void 0,
4965
+ className: [
4966
+ "inline-flex items-center gap-1 rounded-lg border bg-surface-raised p-1",
4967
+ hasError ? "border-status-error" : "border-border",
4968
+ sz.h,
4969
+ fullWidth ? "flex w-full" : "w-fit",
4970
+ disabled ? "opacity-60 cursor-not-allowed" : ""
4971
+ ].filter(Boolean).join(" "),
4972
+ children: options.map((opt) => /* @__PURE__ */ jsxs(
4973
+ ToggleGroup.Item,
4974
+ {
4975
+ value: opt.value,
4976
+ disabled: opt.disabled,
4977
+ className: [
4978
+ "inline-flex items-center justify-center gap-1.5 rounded-md select-none whitespace-nowrap",
4979
+ "transition-colors duration-150 h-full",
4980
+ sz.text,
4981
+ sz.pad,
4982
+ fullWidth ? "flex-1" : "",
4983
+ // Resting: muted text, transparent. Hover lifts the text.
4984
+ "text-foreground-secondary hover:text-foreground",
4985
+ // Active: surface-white pill + accent text + subtle shadow.
4986
+ "data-[state=on]:bg-surface data-[state=on]:text-accent data-[state=on]:shadow-sm",
4987
+ "focus:outline-none focus-visible:ring-[3px] focus-visible:ring-focus-ring",
4988
+ "disabled:opacity-40 disabled:cursor-not-allowed"
4989
+ ].filter(Boolean).join(" "),
4990
+ children: [
4991
+ opt.icon && /* @__PURE__ */ jsx("span", { className: "flex-shrink-0", children: opt.icon }),
4992
+ opt.label
4993
+ ]
4994
+ },
4995
+ opt.value
4996
+ ))
4997
+ }
4998
+ )
4999
+ ]
4760
5000
  }
4761
5001
  );
4762
5002
  }
@@ -4779,6 +5019,8 @@ function Slider({
4779
5019
  size = "md",
4780
5020
  disabled,
4781
5021
  errorMessage,
5022
+ helperText,
5023
+ required,
4782
5024
  name,
4783
5025
  htmlFor
4784
5026
  }) {
@@ -4798,7 +5040,13 @@ function Slider({
4798
5040
  const valueText = current.map(formatValue).join(" \u2013 ");
4799
5041
  return /* @__PURE__ */ jsxs(Field, { label: void 0, errorId, errorMessage, children: [
4800
5042
  (label || showValue) && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-2", children: [
4801
- label && /* @__PURE__ */ jsx("label", { htmlFor, className: "text-sm font-medium text-foreground select-none", children: label }),
5043
+ label && /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1", children: [
5044
+ /* @__PURE__ */ jsxs("label", { htmlFor, className: "text-sm font-medium text-foreground select-none", children: [
5045
+ label,
5046
+ required && /* @__PURE__ */ jsx("span", { className: "text-status-error ml-0.5", "aria-hidden": "true", children: "*" })
5047
+ ] }),
5048
+ helperText != null && /* @__PURE__ */ jsx(FieldHelpIcon, { text: helperText })
5049
+ ] }),
4802
5050
  showValue && /* @__PURE__ */ jsx("span", { className: "text-sm text-foreground-secondary tabular-nums", children: valueText })
4803
5051
  ] }),
4804
5052
  /* @__PURE__ */ jsxs(
@@ -4870,6 +5118,7 @@ function TagsInput({
4870
5118
  size = "md",
4871
5119
  disabled,
4872
5120
  errorMessage,
5121
+ helperText,
4873
5122
  required,
4874
5123
  maxTags,
4875
5124
  dedupe = true,
@@ -4937,6 +5186,7 @@ function TagsInput({
4937
5186
  htmlFor,
4938
5187
  errorId,
4939
5188
  errorMessage: errorText,
5189
+ helperText,
4940
5190
  layout,
4941
5191
  required,
4942
5192
  children: /* @__PURE__ */ jsxs(
@@ -5004,6 +5254,8 @@ function OtpInput({
5004
5254
  disabled,
5005
5255
  errorMessage,
5006
5256
  required,
5257
+ layout = "vertical",
5258
+ helperText,
5007
5259
  groupAfter
5008
5260
  }) {
5009
5261
  const errorId = useId();
@@ -5057,7 +5309,7 @@ function OtpInput({
5057
5309
  emit(valid.join(""));
5058
5310
  focusBox(valid.length);
5059
5311
  };
5060
- return /* @__PURE__ */ jsx(Field, { label, htmlFor, errorId, errorMessage, required, children: /* @__PURE__ */ 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__ */ jsxs(React8.Fragment, { children: [
5312
+ return /* @__PURE__ */ jsx(Field, { label, htmlFor, errorId, errorMessage, required, layout, helperText, children: /* @__PURE__ */ 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__ */ jsxs(React8.Fragment, { children: [
5061
5313
  /* @__PURE__ */ jsx(
5062
5314
  "input",
5063
5315
  {
@@ -5109,7 +5361,10 @@ function Rating({
5109
5361
  disabled,
5110
5362
  icon = Star,
5111
5363
  errorMessage,
5112
- name
5364
+ name,
5365
+ layout = "vertical",
5366
+ helperText,
5367
+ required
5113
5368
  }) {
5114
5369
  const errorId = useId();
5115
5370
  const [internal, setInternal] = useState(defaultValue);
@@ -5139,7 +5394,7 @@ function Rating({
5139
5394
  commit(count);
5140
5395
  }
5141
5396
  };
5142
- return /* @__PURE__ */ jsx(Field, { label, errorId, errorMessage, children: /* @__PURE__ */ jsxs(
5397
+ return /* @__PURE__ */ jsx(Field, { label, errorId, errorMessage, layout, required, helperText, children: /* @__PURE__ */ jsxs(
5143
5398
  "div",
5144
5399
  {
5145
5400
  role: interactive ? "slider" : "img",
@@ -5231,6 +5486,7 @@ function TimePicker({
5231
5486
  minuteStep = 1,
5232
5487
  disabled,
5233
5488
  errorMessage,
5489
+ helperText,
5234
5490
  required,
5235
5491
  style
5236
5492
  }) {
@@ -5263,7 +5519,7 @@ function TimePicker({
5263
5519
  },
5264
5520
  n
5265
5521
  )) });
5266
- return /* @__PURE__ */ jsxs(Field, { label, htmlFor, errorId, errorMessage, layout, required, children: [
5522
+ return /* @__PURE__ */ jsxs(Field, { label, htmlFor, errorId, errorMessage, helperText, layout, required, children: [
5267
5523
  /* @__PURE__ */ jsxs(Popover.Root, { open: open && !disabled, onOpenChange: (o) => !disabled && setOpen(o), children: [
5268
5524
  /* @__PURE__ */ jsx(Popover.Trigger, { asChild: true, children: /* @__PURE__ */ jsxs(
5269
5525
  "button",
@@ -5355,6 +5611,7 @@ function DateRangePicker({
5355
5611
  format = defaultFmt,
5356
5612
  disabled,
5357
5613
  errorMessage,
5614
+ helperText,
5358
5615
  required,
5359
5616
  style
5360
5617
  }) {
@@ -5427,7 +5684,7 @@ function DateRangePicker({
5427
5684
  ] })
5428
5685
  ] });
5429
5686
  };
5430
- return /* @__PURE__ */ jsx(Field, { label, htmlFor, errorId, errorMessage, layout, required, children: /* @__PURE__ */ jsxs(Popover.Root, { open: open && !disabled, onOpenChange: (o) => {
5687
+ return /* @__PURE__ */ jsx(Field, { label, htmlFor, errorId, errorMessage, helperText, layout, required, children: /* @__PURE__ */ jsxs(Popover.Root, { open: open && !disabled, onOpenChange: (o) => {
5431
5688
  if (!disabled) {
5432
5689
  setOpen(o);
5433
5690
  if (!o) {
@@ -5535,6 +5792,7 @@ function ColorPicker({
5535
5792
  allowCustom = true,
5536
5793
  disabled,
5537
5794
  errorMessage,
5795
+ helperText,
5538
5796
  required,
5539
5797
  placeholder = "Pick a colour\u2026"
5540
5798
  }) {
@@ -5552,7 +5810,7 @@ function ColorPicker({
5552
5810
  setDraft(hex);
5553
5811
  if (HEX_RE.test(hex)) onChange?.(hex);
5554
5812
  };
5555
- return /* @__PURE__ */ jsxs(Field, { label, htmlFor, errorId, errorMessage, layout, required, children: [
5813
+ return /* @__PURE__ */ jsxs(Field, { label, htmlFor, errorId, errorMessage, helperText, layout, required, children: [
5556
5814
  /* @__PURE__ */ jsxs(Popover.Root, { open: open && !disabled, onOpenChange: (o) => !disabled && setOpen(o), children: [
5557
5815
  /* @__PURE__ */ jsx(Popover.Trigger, { asChild: true, children: /* @__PURE__ */ jsxs(
5558
5816
  "button",
@@ -5627,6 +5885,438 @@ function ColorPicker({
5627
5885
  ] });
5628
5886
  }
5629
5887
 
5630
- export { AppShell, AutoComplete, Avatar, Box, Button, Catalog, CatalogCarousel, CatalogGrid, Checkbox, ColorPicker, ContextMenu, DateRangePicker, Drawer, Dropdown, FadingBase, Field, FileInput, Flex, Grid2 as Grid, GridCard, icons_default as Icon, IconButton, List2 as List, LoadingSpinner, Modal, NotificationProvider, NumberInput, OpaqueGridCard, OtpInput, Password, Portal, RadioGroup, Rating, ScalableContainer, SearchInput_default as SearchInput, SegmentedControl, Sidebar, SkeletonBox, SkeletonCard, SkeletonCircle, SkeletonText, Slider, Switch, Table, Tabs, TagsInput, DatePicker as Temporal, TextArea, TextInput, ThemeProvider, ThemeSwitch, TimePicker, Tooltip, TooltipProvider, TopBar, Tree, TreeSelect, Typography, Wizard, fieldShell, useNotification };
5888
+ // src/form/path.ts
5889
+ function deepClone(v) {
5890
+ if (v === null || typeof v !== "object") return v;
5891
+ if (v instanceof Date) return new Date(v.getTime());
5892
+ if (Array.isArray(v)) return v.map(deepClone);
5893
+ const out = {};
5894
+ for (const k in v) out[k] = deepClone(v[k]);
5895
+ return out;
5896
+ }
5897
+ function getPath(obj, path) {
5898
+ if (!path) return obj;
5899
+ const parts = path.split(".");
5900
+ let cur = obj;
5901
+ for (const p of parts) {
5902
+ if (cur == null || typeof cur !== "object") return void 0;
5903
+ cur = cur[p];
5904
+ }
5905
+ return cur;
5906
+ }
5907
+ function setPath(obj, path, value) {
5908
+ const parts = path.split(".");
5909
+ const root = Array.isArray(obj) ? [...obj] : { ...obj ?? {} };
5910
+ let cur = root;
5911
+ for (let i = 0; i < parts.length - 1; i++) {
5912
+ const p = parts[i];
5913
+ const nextIsIndex = /^\d+$/.test(parts[i + 1]);
5914
+ const child = cur[p];
5915
+ const next = Array.isArray(child) ? [...child] : child && typeof child === "object" ? { ...child } : nextIsIndex ? [] : {};
5916
+ cur[p] = next;
5917
+ cur = next;
5918
+ }
5919
+ cur[parts[parts.length - 1]] = value;
5920
+ return root;
5921
+ }
5922
+
5923
+ // src/form/validate.ts
5924
+ function isEmpty(v) {
5925
+ return v == null || v === "" || v === false || Array.isArray(v) && v.length === 0;
5926
+ }
5927
+ var boundValue = (b) => typeof b === "number" ? b : b.value;
5928
+ var boundMessage = (b, fallback) => typeof b === "number" ? fallback : b.message ?? fallback;
5929
+ function isRequired(rules) {
5930
+ if (!rules) return false;
5931
+ const list = Array.isArray(rules) ? rules : [rules];
5932
+ return list.some((r) => !!r.required);
5933
+ }
5934
+ async function runFieldRules(value, rules, values) {
5935
+ if (!rules) return void 0;
5936
+ const list = Array.isArray(rules) ? rules : [rules];
5937
+ for (const rule of list) {
5938
+ if (rule.required && isEmpty(value)) {
5939
+ return typeof rule.required === "string" ? rule.required : rule.message ?? "This field is required";
5940
+ }
5941
+ if (isEmpty(value)) {
5942
+ if (rule.validate) {
5943
+ const res = await rule.validate(value, values);
5944
+ if (res) return typeof res === "string" ? res : rule.message ?? "Invalid value";
5945
+ }
5946
+ continue;
5947
+ }
5948
+ if (rule.pattern) {
5949
+ const re = rule.pattern instanceof RegExp ? rule.pattern : rule.pattern.value;
5950
+ const msg = rule.pattern instanceof RegExp ? rule.message ?? "Invalid format" : rule.pattern.message ?? rule.message ?? "Invalid format";
5951
+ if (typeof value === "string" && !re.test(value)) return msg;
5952
+ }
5953
+ if (rule.min != null && typeof value === "number") {
5954
+ const m = boundValue(rule.min);
5955
+ if (value < m) return boundMessage(rule.min, rule.message ?? `Must be at least ${m}`);
5956
+ }
5957
+ if (rule.max != null && typeof value === "number") {
5958
+ const m = boundValue(rule.max);
5959
+ if (value > m) return boundMessage(rule.max, rule.message ?? `Must be at most ${m}`);
5960
+ }
5961
+ if (rule.minLength != null) {
5962
+ const len = value?.length;
5963
+ const m = boundValue(rule.minLength);
5964
+ if (typeof len === "number" && len < m)
5965
+ return boundMessage(rule.minLength, rule.message ?? `Must be at least ${m} characters`);
5966
+ }
5967
+ if (rule.maxLength != null) {
5968
+ const len = value?.length;
5969
+ const m = boundValue(rule.maxLength);
5970
+ if (typeof len === "number" && len > m)
5971
+ return boundMessage(rule.maxLength, rule.message ?? `Must be at most ${m} characters`);
5972
+ }
5973
+ if (rule.validate) {
5974
+ const res = await rule.validate(value, values);
5975
+ if (res) return typeof res === "string" ? res : rule.message ?? "Invalid value";
5976
+ }
5977
+ }
5978
+ return void 0;
5979
+ }
5980
+ var patterns = {
5981
+ email: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
5982
+ url: /^https?:\/\/[^\s/$.?#].[^\s]*$/i,
5983
+ // Loose international phone: + and 7-15 digits, spaces/dashes allowed.
5984
+ phone: /^\+?[\d\s-]{7,15}$/,
5985
+ // Digits only.
5986
+ digits: /^\d+$/
5987
+ };
5988
+
5989
+ // src/form/store.ts
5990
+ var FormStore = class {
5991
+ listeners = /* @__PURE__ */ new Set();
5992
+ fieldCache = /* @__PURE__ */ new Map();
5993
+ values;
5994
+ errors = {};
5995
+ touched = {};
5996
+ submitted = false;
5997
+ validating = false;
5998
+ initialValues;
5999
+ rules;
6000
+ validateOn;
6001
+ // Field-array key bookkeeping (stable React keys across reorder/removal).
6002
+ keys = {};
6003
+ keySeq = 1;
6004
+ // Root snapshot — a new ref on every change, for form-level subscribers.
6005
+ rootSnap = { v: 0 };
6006
+ constructor(opts = {}) {
6007
+ this.initialValues = deepClone(opts.initialValues ?? {});
6008
+ this.values = deepClone(opts.initialValues ?? {});
6009
+ this.rules = { ...opts.rules ?? {} };
6010
+ this.validateOn = opts.validateOn ?? ["onChange", "onBlur", "onSubmit"];
6011
+ }
6012
+ // ── subscription ────────────────────────────────────────────────────────
6013
+ subscribe = (l) => {
6014
+ this.listeners.add(l);
6015
+ return () => {
6016
+ this.listeners.delete(l);
6017
+ };
6018
+ };
6019
+ emit() {
6020
+ this.rootSnap = { v: this.rootSnap.v + 1 };
6021
+ this.listeners.forEach((l) => l());
6022
+ }
6023
+ getRootSnapshot = () => this.rootSnap;
6024
+ getFieldSnapshot = (name) => {
6025
+ const value = getPath(this.values, name);
6026
+ const error = this.errors[name];
6027
+ const showError = (!!this.touched[name] || this.submitted) && error != null;
6028
+ const prev = this.fieldCache.get(name);
6029
+ if (prev && Object.is(prev.value, value) && prev.error === error && prev.showError === showError) {
6030
+ return prev.snap;
6031
+ }
6032
+ const snap = { value, error, showError };
6033
+ this.fieldCache.set(name, { value, error, showError, snap });
6034
+ return snap;
6035
+ };
6036
+ // ── rule registry (for dynamic / array fields) ──────────────────────────
6037
+ setRule(name, rules) {
6038
+ if (rules == null) delete this.rules[name];
6039
+ else this.rules[name] = rules;
6040
+ }
6041
+ removeRule(name) {
6042
+ delete this.rules[name];
6043
+ }
6044
+ getRule(name) {
6045
+ return this.rules[name];
6046
+ }
6047
+ // ── reads ────────────────────────────────────────────────────────────────
6048
+ getValues = () => this.values;
6049
+ getValue = (name) => getPath(this.values, name);
6050
+ get isValid() {
6051
+ return Object.values(this.errors).every((e) => !e);
6052
+ }
6053
+ // ── writes ────────────────────────────────────────────────────────────────
6054
+ setValue = (name, value, opts = {}) => {
6055
+ this.values = setPath(this.values, name, value);
6056
+ if (opts.touch) this.touched = { ...this.touched, [name]: true };
6057
+ this.emit();
6058
+ const shouldValidate = opts.validate ?? this.validateOn.includes("onChange");
6059
+ if (shouldValidate) void this.validateField(name);
6060
+ };
6061
+ setValues = (patch, opts = {}) => {
6062
+ for (const k of Object.keys(patch)) this.values = setPath(this.values, k, patch[k]);
6063
+ this.emit();
6064
+ if (opts.validate) void this.validateAll();
6065
+ };
6066
+ setError = (name, error) => {
6067
+ if (this.errors[name] === error) return;
6068
+ this.errors = { ...this.errors, [name]: error || void 0 };
6069
+ this.emit();
6070
+ };
6071
+ touch = (name, opts = {}) => {
6072
+ if (!this.touched[name]) {
6073
+ this.touched = { ...this.touched, [name]: true };
6074
+ this.emit();
6075
+ }
6076
+ if (opts.validate ?? this.validateOn.includes("onBlur")) void this.validateField(name);
6077
+ };
6078
+ setSubmitted = (v) => {
6079
+ this.submitted = v;
6080
+ this.emit();
6081
+ };
6082
+ // ── validation ─────────────────────────────────────────────────────────────
6083
+ async validateField(name) {
6084
+ const err = await runFieldRules(getPath(this.values, name), this.rules[name], this.values);
6085
+ this.setError(name, err);
6086
+ return err;
6087
+ }
6088
+ async validateAll() {
6089
+ this.validating = true;
6090
+ this.emit();
6091
+ const names = Object.keys(this.rules);
6092
+ const entries = await Promise.all(
6093
+ names.map(async (n) => [n, await runFieldRules(getPath(this.values, n), this.rules[n], this.values)])
6094
+ );
6095
+ const errors = {};
6096
+ for (const [n, e] of entries) errors[n] = e;
6097
+ this.errors = errors;
6098
+ this.validating = false;
6099
+ this.emit();
6100
+ return errors;
6101
+ }
6102
+ reset = (values) => {
6103
+ this.values = deepClone(values ?? this.initialValues);
6104
+ this.errors = {};
6105
+ this.touched = {};
6106
+ this.submitted = false;
6107
+ this.keys = {};
6108
+ this.fieldCache.clear();
6109
+ this.emit();
6110
+ };
6111
+ // ── field arrays ───────────────────────────────────────────────────────────
6112
+ getKeys(name) {
6113
+ const arr = getPath(this.values, name) ?? [];
6114
+ let keys = this.keys[name];
6115
+ if (!keys || keys.length !== arr.length) {
6116
+ keys = arr.map((_, i) => keys && keys[i] != null ? keys[i] : this.keySeq++);
6117
+ this.keys[name] = keys;
6118
+ }
6119
+ return keys;
6120
+ }
6121
+ arrayAppend = (name, item = {}) => {
6122
+ const arr = [...getPath(this.values, name) ?? []];
6123
+ arr.push(item);
6124
+ this.keys[name] = [...this.getKeys(name), this.keySeq++];
6125
+ this.setValue(name, arr, { validate: false });
6126
+ };
6127
+ arrayRemove = (name, index) => {
6128
+ const arr = [...getPath(this.values, name) ?? []];
6129
+ arr.splice(index, 1);
6130
+ const k = [...this.getKeys(name)];
6131
+ k.splice(index, 1);
6132
+ this.keys[name] = k;
6133
+ this.clearBranch(name);
6134
+ this.setValue(name, arr, { validate: false });
6135
+ };
6136
+ arrayMove = (name, from, to) => {
6137
+ const arr = [...getPath(this.values, name) ?? []];
6138
+ if (from < 0 || to < 0 || from >= arr.length || to >= arr.length) return;
6139
+ const [moved] = arr.splice(from, 1);
6140
+ arr.splice(to, 0, moved);
6141
+ const k = [...this.getKeys(name)];
6142
+ const [mk] = k.splice(from, 1);
6143
+ k.splice(to, 0, mk);
6144
+ this.keys[name] = k;
6145
+ this.clearBranch(name);
6146
+ this.setValue(name, arr, { validate: false });
6147
+ };
6148
+ /** Drop any errors/touched flags under `name.` — used when an array shifts. */
6149
+ clearBranch(name) {
6150
+ const prefix = name + ".";
6151
+ const errors = {};
6152
+ for (const k of Object.keys(this.errors)) if (!k.startsWith(prefix)) errors[k] = this.errors[k];
6153
+ const touched = {};
6154
+ for (const k of Object.keys(this.touched)) if (!k.startsWith(prefix)) touched[k] = this.touched[k];
6155
+ this.errors = errors;
6156
+ this.touched = touched;
6157
+ }
6158
+ };
6159
+
6160
+ // src/form/bindings.ts
6161
+ var getTarget = (arg) => {
6162
+ const t = arg?.target;
6163
+ return t && typeof t === "object" ? t : void 0;
6164
+ };
6165
+ var ADAPTERS = {
6166
+ value: { prop: "value", toValue: (v) => v, applyEmpty: false, empty: void 0 },
6167
+ native: { prop: "value", toValue: (e) => getTarget(e)?.value, applyEmpty: true, empty: "" },
6168
+ checked: { prop: "checked", toValue: (e) => getTarget(e)?.checked, applyEmpty: true, empty: false },
6169
+ target: { prop: "value", toValue: (e) => getTarget(e)?.value, applyEmpty: false, empty: void 0 }
6170
+ };
6171
+ function buildBindings(store, name, kind, snap) {
6172
+ const a = ADAPTERS[kind];
6173
+ const raw = snap.value;
6174
+ const value = a.applyEmpty ? raw ?? a.empty : raw;
6175
+ return {
6176
+ name,
6177
+ id: name,
6178
+ htmlFor: name,
6179
+ required: isRequired(store.getRule(name)) || void 0,
6180
+ errorMessage: snap.showError ? snap.error : void 0,
6181
+ [a.prop]: value,
6182
+ onChange: (arg) => store.setValue(name, a.toValue(arg), { touch: true }),
6183
+ onBlur: () => store.touch(name)
6184
+ };
6185
+ }
6186
+
6187
+ // src/form/useForm.ts
6188
+ function useForm(options = {}) {
6189
+ const ref = useRef(null);
6190
+ if (ref.current === null) ref.current = new FormStore(options);
6191
+ const store = ref.current;
6192
+ useSyncExternalStore(store.subscribe, store.getRootSnapshot, store.getRootSnapshot);
6193
+ const make = useCallback(
6194
+ (kind) => (name, rules) => {
6195
+ if (rules !== void 0) store.setRule(name, rules);
6196
+ return buildBindings(store, name, kind, store.getFieldSnapshot(name));
6197
+ },
6198
+ [store]
6199
+ );
6200
+ return {
6201
+ store,
6202
+ values: store.values,
6203
+ errors: store.errors,
6204
+ touched: store.touched,
6205
+ submitted: store.submitted,
6206
+ isSubmitting: store.validating,
6207
+ isValid: store.isValid,
6208
+ getValue: store.getValue,
6209
+ getValues: store.getValues,
6210
+ setValue: (name, value) => store.setValue(name, value),
6211
+ setValues: (patch) => store.setValues(patch),
6212
+ setError: store.setError,
6213
+ validateField: (name) => store.validateField(name),
6214
+ validateAll: () => store.validateAll(),
6215
+ reset: store.reset,
6216
+ field: make("value"),
6217
+ fieldNative: make("native"),
6218
+ fieldChecked: make("checked"),
6219
+ fieldTarget: make("target")
6220
+ };
6221
+ }
6222
+ var FormContext = createContext(null);
6223
+ function useFormStore() {
6224
+ const store = useContext(FormContext);
6225
+ if (!store) {
6226
+ throw new Error("useFormStore must be used within a <Form>. Did you forget to wrap your fields?");
6227
+ }
6228
+ return store;
6229
+ }
6230
+ function Form({
6231
+ form,
6232
+ onFinish,
6233
+ onFinishFailed,
6234
+ action,
6235
+ children,
6236
+ ...rest
6237
+ }) {
6238
+ const ref = useRef(null);
6239
+ const bypass = useRef(false);
6240
+ const handleSubmit = async (e) => {
6241
+ if (bypass.current) {
6242
+ bypass.current = false;
6243
+ return;
6244
+ }
6245
+ e.preventDefault();
6246
+ const store = form.store;
6247
+ store.setSubmitted(true);
6248
+ const errors = await store.validateAll();
6249
+ const hasError = Object.values(errors).some(Boolean);
6250
+ if (hasError) {
6251
+ onFinishFailed?.(errors, store.getValues());
6252
+ focusFirstError(ref.current, errors);
6253
+ return;
6254
+ }
6255
+ if (onFinish) {
6256
+ await onFinish(store.getValues());
6257
+ return;
6258
+ }
6259
+ if (typeof action === "function") {
6260
+ action(new FormData(ref.current));
6261
+ return;
6262
+ }
6263
+ if (typeof action === "string") {
6264
+ bypass.current = true;
6265
+ ref.current.requestSubmit();
6266
+ }
6267
+ };
6268
+ return /* @__PURE__ */ jsx(FormContext.Provider, { value: form.store, children: /* @__PURE__ */ jsx(
6269
+ "form",
6270
+ {
6271
+ ref,
6272
+ noValidate: true,
6273
+ action: typeof action === "string" ? action : void 0,
6274
+ onSubmit: handleSubmit,
6275
+ ...rest,
6276
+ children
6277
+ }
6278
+ ) });
6279
+ }
6280
+ function focusFirstError(formEl, errors) {
6281
+ if (!formEl) return;
6282
+ const firstName = Object.keys(errors).find((k) => errors[k]);
6283
+ if (!firstName) return;
6284
+ const el = formEl.querySelector(`[name="${CSS.escape(firstName)}"], #${CSS.escape(firstName)}`);
6285
+ el?.focus();
6286
+ }
6287
+ function useFormField(name, options = {}) {
6288
+ const store = useFormStore();
6289
+ const { kind = "value", rules } = options;
6290
+ if (rules !== void 0 && store.getRule(name) !== rules) store.setRule(name, rules);
6291
+ useEffect(() => {
6292
+ return () => {
6293
+ if (rules !== void 0) store.removeRule(name);
6294
+ };
6295
+ }, [store, name]);
6296
+ const snap = useSyncExternalStore(
6297
+ store.subscribe,
6298
+ () => store.getFieldSnapshot(name)
6299
+ );
6300
+ return buildBindings(store, name, kind, snap);
6301
+ }
6302
+ function FormField({ name, kind, rules, children }) {
6303
+ const field = useFormField(name, { kind, rules });
6304
+ return /* @__PURE__ */ jsx(Fragment, { children: children(field) });
6305
+ }
6306
+ function useFieldArray(name) {
6307
+ const store = useFormStore();
6308
+ useSyncExternalStore(store.subscribe, store.getRootSnapshot, store.getRootSnapshot);
6309
+ const arr = store.getValue(name) ?? [];
6310
+ const keys = store.getKeys(name);
6311
+ return {
6312
+ fields: arr.map((_, i) => ({ key: keys[i], name: `${name}.${i}`, index: i })),
6313
+ append: (item = {}) => store.arrayAppend(name, item),
6314
+ remove: (index) => store.arrayRemove(name, index),
6315
+ move: (from, to) => store.arrayMove(name, from, to),
6316
+ replace: (items) => store.setValue(name, items, { validate: false })
6317
+ };
6318
+ }
6319
+
6320
+ export { AppShell, AutoComplete, Avatar, Box, Button, Catalog, CatalogCarousel, CatalogGrid, Checkbox, ColorPicker, ContextMenu, DateRangePicker, Drawer, Dropdown, FadingBase, Field, FieldHelpIcon, FieldLabel, FileInput, Flex, Form, FormContext, FormField, FormStore, Grid2 as Grid, GridCard, icons_default as Icon, IconButton, List2 as List, LoadingSpinner, Modal, NotificationProvider, NumberInput, OpaqueGridCard, OtpInput, Password, Portal, RadioGroup, Rating, ScalableContainer, SearchInput_default as SearchInput, SegmentedControl, Sidebar, SkeletonBox, SkeletonCard, SkeletonCircle, SkeletonText, Slider, Switch, Table, Tabs, TagsInput, DatePicker as Temporal, TextArea, TextInput, ThemeProvider, ThemeSwitch, TimePicker, Tooltip, TooltipProvider, TopBar, Tree, TreeSelect, Typography, Wizard, fieldShell, isRequired, patterns, runFieldRules, useFieldArray, useForm, useFormField, useFormStore, useNotification };
5631
6321
  //# sourceMappingURL=index.js.map
5632
6322
  //# sourceMappingURL=index.js.map