@geomak/ui 5.5.0 → 5.5.1

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.d.cts CHANGED
@@ -2102,6 +2102,18 @@ interface CheckboxProps {
2102
2102
  htmlFor?: string;
2103
2103
  errorMessage?: React$1.ReactNode;
2104
2104
  disabled?: boolean;
2105
+ /**
2106
+ * Box + label arrangement.
2107
+ * - `'horizontal'` (default): box and label on one row.
2108
+ * - `'vertical'`: box and label stacked.
2109
+ */
2110
+ layout?: 'horizontal' | 'vertical';
2111
+ /**
2112
+ * Where the label sits relative to the box.
2113
+ * - In horizontal: `'right'` (default) → box then label; `'left'` → label then box.
2114
+ * - In vertical: `'right'` (default) → box then label below; `'left'` → label above then box.
2115
+ */
2116
+ labelPosition?: 'left' | 'right';
2105
2117
  /** @deprecated Use `checked` */
2106
2118
  value?: boolean;
2107
2119
  }
@@ -2120,7 +2132,7 @@ interface CheckboxProps {
2120
2132
  * />
2121
2133
  */
2122
2134
  declare function Checkbox({ checked, value, // legacy alias
2123
- onChange, label, name, htmlFor, errorMessage, disabled, }: CheckboxProps): react_jsx_runtime.JSX.Element;
2135
+ onChange, label, name, htmlFor, errorMessage, disabled, layout, labelPosition, }: CheckboxProps): react_jsx_runtime.JSX.Element;
2124
2136
 
2125
2137
  interface RadioOption {
2126
2138
  /** Stable value submitted / reported on change. */
@@ -2232,13 +2244,6 @@ interface DropdownProps {
2232
2244
  disabled?: boolean;
2233
2245
  /** Label/input orientation. Defaults to `'vertical'`. */
2234
2246
  layout?: 'horizontal' | 'vertical';
2235
- /**
2236
- * Show a "+N more" pill alongside the first selected item in multiselect
2237
- * mode. Defaults to `false` — a single pill is shown with the first
2238
- * selection and consumers typically open the dropdown to see the rest.
2239
- * Set `true` if you want the count visible on the trigger.
2240
- */
2241
- showSelectedCount?: boolean;
2242
2247
  errorMessage?: React$1.ReactNode;
2243
2248
  style?: React$1.CSSProperties;
2244
2249
  htmlFor?: string;
@@ -2263,7 +2268,7 @@ interface DropdownProps {
2263
2268
  * // Multi-select
2264
2269
  * <Dropdown isMultiselect label="Fuels" items={fuels} value={form.fuels} onChange={handleChange} />
2265
2270
  */
2266
- declare function Dropdown({ isMultiselect, hasSearch, label, name, value, onChange, disabled, layout, errorMessage, style, htmlFor, items, labelStyle, placeholder, showSelectedCount, size, }: DropdownProps): react_jsx_runtime.JSX.Element;
2271
+ declare function Dropdown({ isMultiselect, hasSearch, label, name, value, onChange, disabled, layout, errorMessage, style, htmlFor, items, labelStyle, placeholder, size, }: DropdownProps): react_jsx_runtime.JSX.Element;
2267
2272
 
2268
2273
  interface AutoCompleteItem {
2269
2274
  key: string;
@@ -2307,6 +2312,11 @@ interface AutoCompleteProps {
2307
2312
  size?: FieldSize;
2308
2313
  /** Override the leading search icon (hidden while loading). */
2309
2314
  icon?: React$1.ReactNode;
2315
+ /** Validation message — turns the field red and links via aria-describedby. */
2316
+ errorMessage?: React$1.ReactNode;
2317
+ /** Mark required (asterisk after the label). */
2318
+ required?: boolean;
2319
+ htmlFor?: string;
2310
2320
  }
2311
2321
  /**
2312
2322
  * Search-as-you-type autocomplete powered by Radix Popover. Supports two
@@ -2344,7 +2354,7 @@ interface AutoCompleteProps {
2344
2354
  * />
2345
2355
  * ```
2346
2356
  */
2347
- declare function AutoComplete({ disabled, label, placeholder, name, inputStyle, style, layout, items, onSearch, debounce, onItemClick, emptyText, loadingText, size, icon, }: AutoCompleteProps): react_jsx_runtime.JSX.Element;
2357
+ declare function AutoComplete({ disabled, label, placeholder, name, inputStyle, style, layout, items, onSearch, debounce, onItemClick, emptyText, loadingText, size, icon, errorMessage, required, htmlFor, }: AutoCompleteProps): react_jsx_runtime.JSX.Element;
2348
2358
 
2349
2359
  interface TreeSelectNode {
2350
2360
  key: string | number;
package/dist/index.d.ts CHANGED
@@ -2102,6 +2102,18 @@ interface CheckboxProps {
2102
2102
  htmlFor?: string;
2103
2103
  errorMessage?: React$1.ReactNode;
2104
2104
  disabled?: boolean;
2105
+ /**
2106
+ * Box + label arrangement.
2107
+ * - `'horizontal'` (default): box and label on one row.
2108
+ * - `'vertical'`: box and label stacked.
2109
+ */
2110
+ layout?: 'horizontal' | 'vertical';
2111
+ /**
2112
+ * Where the label sits relative to the box.
2113
+ * - In horizontal: `'right'` (default) → box then label; `'left'` → label then box.
2114
+ * - In vertical: `'right'` (default) → box then label below; `'left'` → label above then box.
2115
+ */
2116
+ labelPosition?: 'left' | 'right';
2105
2117
  /** @deprecated Use `checked` */
2106
2118
  value?: boolean;
2107
2119
  }
@@ -2120,7 +2132,7 @@ interface CheckboxProps {
2120
2132
  * />
2121
2133
  */
2122
2134
  declare function Checkbox({ checked, value, // legacy alias
2123
- onChange, label, name, htmlFor, errorMessage, disabled, }: CheckboxProps): react_jsx_runtime.JSX.Element;
2135
+ onChange, label, name, htmlFor, errorMessage, disabled, layout, labelPosition, }: CheckboxProps): react_jsx_runtime.JSX.Element;
2124
2136
 
2125
2137
  interface RadioOption {
2126
2138
  /** Stable value submitted / reported on change. */
@@ -2232,13 +2244,6 @@ interface DropdownProps {
2232
2244
  disabled?: boolean;
2233
2245
  /** Label/input orientation. Defaults to `'vertical'`. */
2234
2246
  layout?: 'horizontal' | 'vertical';
2235
- /**
2236
- * Show a "+N more" pill alongside the first selected item in multiselect
2237
- * mode. Defaults to `false` — a single pill is shown with the first
2238
- * selection and consumers typically open the dropdown to see the rest.
2239
- * Set `true` if you want the count visible on the trigger.
2240
- */
2241
- showSelectedCount?: boolean;
2242
2247
  errorMessage?: React$1.ReactNode;
2243
2248
  style?: React$1.CSSProperties;
2244
2249
  htmlFor?: string;
@@ -2263,7 +2268,7 @@ interface DropdownProps {
2263
2268
  * // Multi-select
2264
2269
  * <Dropdown isMultiselect label="Fuels" items={fuels} value={form.fuels} onChange={handleChange} />
2265
2270
  */
2266
- declare function Dropdown({ isMultiselect, hasSearch, label, name, value, onChange, disabled, layout, errorMessage, style, htmlFor, items, labelStyle, placeholder, showSelectedCount, size, }: DropdownProps): react_jsx_runtime.JSX.Element;
2271
+ declare function Dropdown({ isMultiselect, hasSearch, label, name, value, onChange, disabled, layout, errorMessage, style, htmlFor, items, labelStyle, placeholder, size, }: DropdownProps): react_jsx_runtime.JSX.Element;
2267
2272
 
2268
2273
  interface AutoCompleteItem {
2269
2274
  key: string;
@@ -2307,6 +2312,11 @@ interface AutoCompleteProps {
2307
2312
  size?: FieldSize;
2308
2313
  /** Override the leading search icon (hidden while loading). */
2309
2314
  icon?: React$1.ReactNode;
2315
+ /** Validation message — turns the field red and links via aria-describedby. */
2316
+ errorMessage?: React$1.ReactNode;
2317
+ /** Mark required (asterisk after the label). */
2318
+ required?: boolean;
2319
+ htmlFor?: string;
2310
2320
  }
2311
2321
  /**
2312
2322
  * Search-as-you-type autocomplete powered by Radix Popover. Supports two
@@ -2344,7 +2354,7 @@ interface AutoCompleteProps {
2344
2354
  * />
2345
2355
  * ```
2346
2356
  */
2347
- declare function AutoComplete({ disabled, label, placeholder, name, inputStyle, style, layout, items, onSearch, debounce, onItemClick, emptyText, loadingText, size, icon, }: AutoCompleteProps): react_jsx_runtime.JSX.Element;
2357
+ declare function AutoComplete({ disabled, label, placeholder, name, inputStyle, style, layout, items, onSearch, debounce, onItemClick, emptyText, loadingText, size, icon, errorMessage, required, htmlFor, }: AutoCompleteProps): react_jsx_runtime.JSX.Element;
2348
2358
 
2349
2359
  interface TreeSelectNode {
2350
2360
  key: string | number;
package/dist/index.js CHANGED
@@ -2140,9 +2140,9 @@ var FIELD_SIZE = {
2140
2140
  lg: { control: "h-control-lg", text: "text-sm", padX: "px-3.5", gap: "gap-2.5" }
2141
2141
  };
2142
2142
  var FOCUS_WITHIN = "focus-within:outline-none focus-within:border-accent focus-within:ring-[3px] focus-within:ring-focus-ring";
2143
- var FOCUS_VISIBLE = "focus-visible:outline-none focus-visible:border-accent focus-visible:ring-[3px] focus-visible:ring-focus-ring";
2143
+ var FOCUS_ELEMENT = "focus:outline-none focus:border-accent focus:ring-[3px] focus:ring-focus-ring data-[state=open]:border-accent data-[state=open]:ring-[3px] data-[state=open]:ring-focus-ring";
2144
2144
  var FOCUS_WITHIN_ERROR = "focus-within:border-status-error focus-within:ring-focus-ring-error";
2145
- var FOCUS_VISIBLE_ERROR = "focus-visible:border-status-error focus-visible:ring-focus-ring-error";
2145
+ var FOCUS_ELEMENT_ERROR = "focus:border-status-error focus:ring-focus-ring-error data-[state=open]:border-status-error data-[state=open]:ring-focus-ring-error";
2146
2146
  function fieldShell({
2147
2147
  size = "md",
2148
2148
  hasError = false,
@@ -2161,8 +2161,8 @@ function fieldShell({
2161
2161
  // hover (only when interactive + no error)
2162
2162
  disabled ? "bg-surface-raised text-foreground-muted cursor-not-allowed" : hasError ? "" : "hover:border-border-strong",
2163
2163
  // focus
2164
- focusWithin ? FOCUS_WITHIN : FOCUS_VISIBLE,
2165
- hasError ? focusWithin ? FOCUS_WITHIN_ERROR : FOCUS_VISIBLE_ERROR : "",
2164
+ focusWithin ? FOCUS_WITHIN : FOCUS_ELEMENT,
2165
+ hasError ? focusWithin ? FOCUS_WITHIN_ERROR : FOCUS_ELEMENT_ERROR : "",
2166
2166
  // placeholder colour for native inputs
2167
2167
  "placeholder:text-foreground-muted"
2168
2168
  ].filter(Boolean).join(" ");
@@ -2197,7 +2197,9 @@ function Field({
2197
2197
  style: { width: horizontal ? labelWidth : void 0, ...labelStyle },
2198
2198
  className: [
2199
2199
  "text-sm font-medium text-foreground select-none",
2200
- horizontal ? "mt-2 flex-shrink-0" : ""
2200
+ // In horizontal layout the label must not wrap onto
2201
+ // multiple lines (e.g. "Report date", "Select option").
2202
+ horizontal ? "mt-2 flex-shrink-0 whitespace-nowrap" : ""
2201
2203
  ].filter(Boolean).join(" "),
2202
2204
  children: [
2203
2205
  label,
@@ -2243,14 +2245,24 @@ var SearchInput = React8.forwardRef(function SearchInput2({ value, onChange, dis
2243
2245
  ) });
2244
2246
  });
2245
2247
  var SearchInput_default = SearchInput;
2246
- function DropdownPill({ value, hasSiblings = false }) {
2247
- return /* @__PURE__ */ jsx(
2248
- "div",
2249
- {
2250
- className: `bg-accent text-accent-fg text-sm text-ellipsis ${hasSiblings ? "w-24" : "w-max"} p-1 px-2 rounded-lg whitespace-nowrap overflow-hidden`,
2251
- children: value
2252
- }
2253
- );
2248
+ function Tag({ children, onRemove, removeLabel, disabled }) {
2249
+ return /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1 rounded-md border border-border bg-surface-raised text-foreground text-xs pl-2 pr-1 py-0.5 max-w-full", children: [
2250
+ /* @__PURE__ */ jsx("span", { className: "truncate", children }),
2251
+ onRemove && /* @__PURE__ */ jsx(
2252
+ "button",
2253
+ {
2254
+ type: "button",
2255
+ disabled,
2256
+ onClick: (e) => {
2257
+ e.stopPropagation();
2258
+ onRemove();
2259
+ },
2260
+ "aria-label": removeLabel ?? "Remove",
2261
+ className: "inline-flex items-center justify-center w-4 h-4 flex-shrink-0 rounded text-foreground-muted hover:text-status-error hover:bg-surface transition-colors focus:outline-none focus-visible:ring-1 focus-visible:ring-accent disabled:cursor-not-allowed",
2262
+ children: /* @__PURE__ */ jsx("svg", { width: "10", height: "10", viewBox: "0 0 20 20", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M15 5L5 15M5 5l10 10", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round" }) })
2263
+ }
2264
+ )
2265
+ ] });
2254
2266
  }
2255
2267
  function Dropdown({
2256
2268
  isMultiselect = false,
@@ -2267,7 +2279,6 @@ function Dropdown({
2267
2279
  items = [],
2268
2280
  labelStyle = {},
2269
2281
  placeholder,
2270
- showSelectedCount = false,
2271
2282
  size = "md"
2272
2283
  }) {
2273
2284
  const [open, setOpen] = useState(false);
@@ -2295,6 +2306,17 @@ function Dropdown({
2295
2306
  setOpen(false);
2296
2307
  }
2297
2308
  };
2309
+ const removeSelected = (key) => {
2310
+ if (isMultiselect) {
2311
+ const next = selectedItems.filter((it) => it !== key);
2312
+ setSelectedItems(next);
2313
+ onChange?.({ target: { value: next, id: htmlFor, name } });
2314
+ } else {
2315
+ setSelectedItems([]);
2316
+ onChange?.({ target: { value: "", id: htmlFor, name } });
2317
+ }
2318
+ };
2319
+ const labelFor = (key) => innerItems.find((it) => it.key === key)?.label ?? String(key);
2298
2320
  const onSearchChange = (e) => {
2299
2321
  const term = e.target.value;
2300
2322
  setSearchTerm(term);
@@ -2305,16 +2327,16 @@ function Dropdown({
2305
2327
  );
2306
2328
  };
2307
2329
  const isSelected = (key) => Array.isArray(value) ? value.includes(key) : value === key;
2308
- return /* @__PURE__ */ jsxs("div", { className: "mt-2", children: [
2330
+ return /* @__PURE__ */ jsxs("div", { children: [
2309
2331
  /* @__PURE__ */ jsxs(
2310
2332
  "div",
2311
2333
  {
2312
- className: `flex ${layout === "vertical" ? "flex-col" : "flex-row items-center gap-2"}`,
2334
+ className: `flex ${layout === "vertical" ? "flex-col gap-1.5" : "flex-row items-start gap-3"}`,
2313
2335
  children: [
2314
2336
  label && /* @__PURE__ */ jsx(
2315
2337
  "label",
2316
2338
  {
2317
- className: "text-sm font-medium ml-1 max-content select-none text-foreground",
2339
+ className: `text-sm font-medium select-none text-foreground ${layout === "horizontal" ? "mt-2 flex-shrink-0 whitespace-nowrap" : ""}`,
2318
2340
  htmlFor,
2319
2341
  style: labelStyle,
2320
2342
  children: label
@@ -2331,7 +2353,7 @@ function Dropdown({
2331
2353
  "aria-invalid": hasError || void 0,
2332
2354
  "aria-describedby": hasError ? errorId : void 0,
2333
2355
  style,
2334
- className: `flex items-center justify-between cursor-pointer select-none ${fieldShell({ size, hasError, disabled })}`,
2356
+ className: `flex items-center justify-between gap-1 cursor-pointer select-none min-h-[36px] px-3 py-1.5 ${fieldShell({ size, hasError, disabled, sized: false })}`,
2335
2357
  tabIndex: disabled ? -1 : 0,
2336
2358
  onKeyDown: (e) => {
2337
2359
  if (disabled) return;
@@ -2344,18 +2366,25 @@ function Dropdown({
2344
2366
  /* @__PURE__ */ jsx(
2345
2367
  "div",
2346
2368
  {
2347
- className: `${!style?.width ? "min-w-[200px]" : ""} flex items-center gap-1 overflow-hidden`,
2348
- children: !value || Array.isArray(value) && value.length === 0 ? /* @__PURE__ */ jsx("span", { className: "text-foreground-muted text-sm", children: placeholder }) : Array.isArray(value) ? /* @__PURE__ */ jsxs(Fragment, { children: [
2349
- value.slice(0, 1).map((val) => /* @__PURE__ */ jsx(
2350
- DropdownPill,
2351
- {
2352
- hasSiblings: value.length > 1,
2353
- value: innerItems.find((it) => it.key === val)?.label
2354
- },
2355
- String(val)
2356
- )),
2357
- showSelectedCount && value.length > 1 && /* @__PURE__ */ jsx(DropdownPill, { value: `+${value.length - 1} more` })
2358
- ] }) : /* @__PURE__ */ jsx(DropdownPill, { value: innerItems.find((it) => it.key === value)?.label })
2369
+ className: `${!style?.width ? "min-w-[200px]" : ""} flex flex-wrap items-center gap-1.5`,
2370
+ 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(
2371
+ Tag,
2372
+ {
2373
+ disabled,
2374
+ removeLabel: `Remove ${labelFor(val)}`,
2375
+ onRemove: () => removeSelected(val),
2376
+ children: labelFor(val)
2377
+ },
2378
+ String(val)
2379
+ )) : /* @__PURE__ */ jsx(
2380
+ Tag,
2381
+ {
2382
+ disabled,
2383
+ removeLabel: `Remove ${labelFor(value)}`,
2384
+ onRemove: () => removeSelected(value),
2385
+ children: labelFor(value)
2386
+ }
2387
+ )
2359
2388
  }
2360
2389
  ),
2361
2390
  /* @__PURE__ */ jsx("div", { className: `flex-shrink-0 ml-2 text-foreground-muted transition-transform duration-200 ${open ? "rotate-180" : "rotate-0"}`, "aria-hidden": "true", children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, className: "h-4 w-4", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M19 9l-7 7-7-7" }) }) })
@@ -3549,74 +3578,54 @@ function Checkbox({
3549
3578
  name,
3550
3579
  htmlFor,
3551
3580
  errorMessage,
3552
- disabled = false
3581
+ disabled = false,
3582
+ layout = "horizontal",
3583
+ labelPosition = "right"
3553
3584
  }) {
3554
3585
  const isChecked = checked ?? value ?? false;
3586
+ const labelFirst = labelPosition === "left";
3587
+ const box = /* @__PURE__ */ jsx(
3588
+ CheckboxPrimitive.Root,
3589
+ {
3590
+ id: htmlFor,
3591
+ name,
3592
+ checked: isChecked,
3593
+ disabled,
3594
+ onCheckedChange: (c) => onChange?.({ target: { checked: !!c, id: htmlFor, name } }),
3595
+ className: [
3596
+ "relative flex h-[18px] w-[18px] flex-shrink-0 items-center justify-center",
3597
+ "rounded-sm border transition-colors duration-150",
3598
+ "border-border-strong bg-surface",
3599
+ "data-[state=checked]:bg-accent data-[state=checked]:border-accent",
3600
+ // Focus halo matches the field tokens for a consistent look.
3601
+ "focus:outline-none focus-visible:ring-[3px] focus-visible:ring-focus-ring",
3602
+ "disabled:cursor-not-allowed"
3603
+ ].join(" "),
3604
+ "aria-label": typeof label === "string" ? label : void 0,
3605
+ 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" }) }) })
3606
+ }
3607
+ );
3608
+ const labelEl = label && /* @__PURE__ */ jsx("span", { className: "text-sm text-foreground-secondary select-none leading-snug", children: label });
3555
3609
  return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
3556
- /* @__PURE__ */ jsxs(
3610
+ /* @__PURE__ */ jsx(
3557
3611
  "label",
3558
3612
  {
3559
3613
  htmlFor,
3560
3614
  className: [
3561
- "inline-flex items-center gap-2.5",
3615
+ "inline-flex",
3616
+ layout === "vertical" ? "flex-col items-start gap-1.5" : "flex-row items-center gap-2.5",
3562
3617
  disabled ? "cursor-not-allowed opacity-50" : "cursor-pointer"
3563
3618
  ].join(" "),
3564
- children: [
3565
- /* @__PURE__ */ jsx(
3566
- CheckboxPrimitive.Root,
3567
- {
3568
- id: htmlFor,
3569
- name,
3570
- checked: isChecked,
3571
- disabled,
3572
- onCheckedChange: (c) => onChange?.({ target: { checked: !!c, id: htmlFor, name } }),
3573
- className: [
3574
- // Box
3575
- "relative flex h-[18px] w-[18px] flex-shrink-0 items-center justify-center",
3576
- "rounded-sm border transition-colors duration-150",
3577
- // Unchecked
3578
- "border-border-strong bg-surface",
3579
- // Checked
3580
- "data-[state=checked]:bg-accent data-[state=checked]:border-accent",
3581
- // Focus
3582
- "focus:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-1",
3583
- // Disabled
3584
- "disabled:cursor-not-allowed"
3585
- ].join(" "),
3586
- "aria-label": typeof label === "string" ? label : void 0,
3587
- children: /* @__PURE__ */ jsx(
3588
- CheckboxPrimitive.Indicator,
3589
- {
3590
- className: "flex items-center justify-center data-[state=checked]:animate-check-pop",
3591
- children: /* @__PURE__ */ jsx(
3592
- "svg",
3593
- {
3594
- width: "11",
3595
- height: "9",
3596
- viewBox: "0 0 11 9",
3597
- fill: "none",
3598
- "aria-hidden": "true",
3599
- children: /* @__PURE__ */ jsx(
3600
- "path",
3601
- {
3602
- d: "M1 4.5L4 7.5L10 1",
3603
- stroke: "white",
3604
- strokeWidth: "1.8",
3605
- strokeLinecap: "round",
3606
- strokeLinejoin: "round"
3607
- }
3608
- )
3609
- }
3610
- )
3611
- }
3612
- )
3613
- }
3614
- ),
3615
- label && /* @__PURE__ */ jsx("span", { className: "text-sm text-foreground-secondary select-none leading-snug", children: label })
3616
- ]
3619
+ children: labelFirst ? /* @__PURE__ */ jsxs(Fragment, { children: [
3620
+ labelEl,
3621
+ box
3622
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
3623
+ box,
3624
+ labelEl
3625
+ ] })
3617
3626
  }
3618
3627
  ),
3619
- errorMessage && /* @__PURE__ */ jsx("span", { className: "text-xs text-status-error pl-[26px]", children: errorMessage })
3628
+ errorMessage && /* @__PURE__ */ jsx("span", { className: "text-xs text-status-error mt-0.5", children: errorMessage })
3620
3629
  ] });
3621
3630
  }
3622
3631
  var DOT_SIZE = {
@@ -3722,12 +3731,12 @@ function Switch({
3722
3731
  id,
3723
3732
  checked,
3724
3733
  onCheckedChange: (c) => onChange?.({ target: { checked: c } }),
3725
- className: "relative inline-flex h-6 w-14 items-center rounded-full bg-foreground-secondary data-[state=checked]:bg-accent transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2",
3734
+ 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",
3726
3735
  children: /* @__PURE__ */ jsx(
3727
3736
  SwitchPrimitive.Thumb,
3728
3737
  {
3729
- className: "pointer-events-none inline-flex h-8 w-8 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-[-4px]",
3730
- children: checkedIcon && uncheckedIcon ? checked ? checkedIcon : uncheckedIcon : null
3738
+ 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]",
3739
+ 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
3731
3740
  }
3732
3741
  )
3733
3742
  }
@@ -3748,8 +3757,13 @@ function AutoComplete({
3748
3757
  emptyText = "No results found",
3749
3758
  loadingText = "Searching\u2026",
3750
3759
  size = "md",
3751
- icon
3760
+ icon,
3761
+ errorMessage,
3762
+ required,
3763
+ htmlFor
3752
3764
  }) {
3765
+ const errorId = useId();
3766
+ const hasError = errorMessage != null;
3753
3767
  const [term, setTerm] = useState("");
3754
3768
  const [open, setOpen] = useState(false);
3755
3769
  const [asyncItems, setAsyncItems] = useState([]);
@@ -3796,78 +3810,82 @@ function AutoComplete({
3796
3810
  onItemClick?.(item.value);
3797
3811
  setOpen(false);
3798
3812
  };
3799
- return /* @__PURE__ */ jsxs(
3800
- "div",
3813
+ return /* @__PURE__ */ jsx(
3814
+ Field,
3801
3815
  {
3802
- className: `flex ${layout === "vertical" ? "flex-col gap-1.5" : "flex-row items-start gap-3"}`,
3803
- style,
3804
- children: [
3805
- label && /* @__PURE__ */ jsx("label", { className: `text-sm font-medium text-foreground select-none ${layout === "horizontal" ? "mt-2 flex-shrink-0" : ""}`, children: label }),
3806
- /* @__PURE__ */ jsx("div", { className: "flex flex-col min-w-0 flex-1", children: /* @__PURE__ */ jsxs(Popover.Root, { open: open && !disabled, onOpenChange: (o) => !disabled && setOpen(o), children: [
3807
- /* @__PURE__ */ jsx(Popover.Anchor, { asChild: true, children: /* @__PURE__ */ jsxs("div", { className: `flex items-center ${fieldShell({ size, disabled, focusWithin: true })}`, children: [
3808
- /* @__PURE__ */ jsx(
3809
- "input",
3810
- {
3811
- disabled,
3812
- value: term,
3813
- onChange: (e) => {
3814
- setTerm(e.target.value);
3815
- setOpen(true);
3816
- },
3817
- onFocus: () => setOpen(true),
3818
- type: "text",
3819
- name,
3820
- className: "min-w-0 flex-1 bg-transparent outline-none disabled:cursor-not-allowed placeholder:text-foreground-muted",
3821
- style: inputStyle,
3822
- placeholder: placeholder ?? "",
3823
- autoComplete: "off",
3824
- "aria-haspopup": "listbox",
3825
- "aria-expanded": open,
3826
- "aria-autocomplete": "list",
3827
- "aria-busy": loading || void 0
3828
- }
3829
- ),
3830
- loading ? /* @__PURE__ */ jsx("span", { className: "ml-2 w-4 h-4 flex-shrink-0 flex items-center justify-center text-accent", "aria-hidden": "true", children: /* @__PURE__ */ jsx(LoadingSpinner, { inline: true, size: "xs", spinnerColor: "currentColor" }) }) : /* @__PURE__ */ jsx("span", { className: "ml-2 flex-shrink-0 text-foreground-muted", children: icon ?? /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "currentColor", className: "w-4 h-4", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { fillRule: "evenodd", d: "M10.5 3.75a6.75 6.75 0 100 13.5 6.75 6.75 0 000-13.5zM2.25 10.5a8.25 8.25 0 1114.59 5.28l4.69 4.69a.75.75 0 11-1.06 1.06l-4.69-4.69A8.25 8.25 0 012.25 10.5z", clipRule: "evenodd" }) }) })
3831
- ] }) }),
3832
- /* @__PURE__ */ jsx(Popover.Portal, { children: /* @__PURE__ */ jsx(
3833
- Popover.Content,
3816
+ label,
3817
+ htmlFor,
3818
+ errorId,
3819
+ errorMessage,
3820
+ layout,
3821
+ required,
3822
+ children: /* @__PURE__ */ jsxs(Popover.Root, { open: open && !disabled, onOpenChange: (o) => !disabled && setOpen(o), children: [
3823
+ /* @__PURE__ */ jsx(Popover.Anchor, { asChild: true, children: /* @__PURE__ */ jsxs("div", { className: `flex items-center ${fieldShell({ size, hasError, disabled, focusWithin: true })}`, style, children: [
3824
+ /* @__PURE__ */ jsx(
3825
+ "input",
3834
3826
  {
3835
- align: "start",
3836
- sideOffset: 4,
3837
- onOpenAutoFocus: (e) => e.preventDefault(),
3838
- className: "w-64 bg-surface border border-border rounded-lg mt-1 shadow-md z-50 overflow-y-auto max-h-36 animate-in fade-in-0 zoom-in-95",
3839
- children: loading ? /* @__PURE__ */ jsxs("div", { className: "h-full w-full flex items-center justify-center gap-2 py-4 text-sm text-foreground-secondary", children: [
3840
- /* @__PURE__ */ jsx(LoadingSpinner, { inline: true, size: "xs" }),
3841
- /* @__PURE__ */ jsx("span", { children: loadingText })
3842
- ] }) : foundItems.length === 0 ? /* @__PURE__ */ jsx("div", { className: "h-full w-full flex flex-col items-center justify-center py-4 text-sm text-foreground-secondary", children: emptyText }) : /* @__PURE__ */ jsx("div", { role: "listbox", children: foundItems.map((item) => /* @__PURE__ */ jsxs(
3843
- "div",
3844
- {
3845
- role: "option",
3846
- tabIndex: 0,
3847
- className: "text-sm flex items-center gap-2 p-2 transition-colors duration-150 hover:bg-surface-raised cursor-pointer text-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-accent",
3848
- onClick: () => handleSelect(item),
3849
- onKeyDown: (e) => {
3850
- if (e.key === "Enter" || e.key === " ") {
3851
- e.preventDefault();
3852
- handleSelect(item);
3853
- }
3854
- },
3855
- children: [
3856
- item.icon,
3857
- /* @__PURE__ */ jsxs("span", { children: [
3858
- item.label,
3859
- " (",
3860
- item.value,
3861
- ")"
3862
- ] })
3863
- ]
3864
- },
3865
- item.key
3866
- )) })
3827
+ id: htmlFor,
3828
+ disabled,
3829
+ value: term,
3830
+ onChange: (e) => {
3831
+ setTerm(e.target.value);
3832
+ setOpen(true);
3833
+ },
3834
+ onFocus: () => setOpen(true),
3835
+ type: "text",
3836
+ name,
3837
+ className: "min-w-0 flex-1 bg-transparent outline-none disabled:cursor-not-allowed placeholder:text-foreground-muted",
3838
+ style: inputStyle,
3839
+ placeholder: placeholder ?? "",
3840
+ autoComplete: "off",
3841
+ "aria-haspopup": "listbox",
3842
+ "aria-expanded": open,
3843
+ "aria-autocomplete": "list",
3844
+ "aria-busy": loading || void 0,
3845
+ "aria-invalid": hasError || void 0,
3846
+ "aria-describedby": hasError ? errorId : void 0
3867
3847
  }
3868
- ) })
3869
- ] }) })
3870
- ]
3848
+ ),
3849
+ loading ? /* @__PURE__ */ jsx("span", { className: "ml-2 w-4 h-4 flex-shrink-0 flex items-center justify-center text-accent", "aria-hidden": "true", children: /* @__PURE__ */ jsx(LoadingSpinner, { inline: true, size: "xs", spinnerColor: "currentColor" }) }) : /* @__PURE__ */ jsx("span", { className: "ml-2 flex-shrink-0 text-foreground-muted", children: icon ?? /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "currentColor", className: "w-4 h-4", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { fillRule: "evenodd", d: "M10.5 3.75a6.75 6.75 0 100 13.5 6.75 6.75 0 000-13.5zM2.25 10.5a8.25 8.25 0 1114.59 5.28l4.69 4.69a.75.75 0 11-1.06 1.06l-4.69-4.69A8.25 8.25 0 012.25 10.5z", clipRule: "evenodd" }) }) })
3850
+ ] }) }),
3851
+ /* @__PURE__ */ jsx(Popover.Portal, { children: /* @__PURE__ */ jsx(
3852
+ Popover.Content,
3853
+ {
3854
+ align: "start",
3855
+ sideOffset: 4,
3856
+ onOpenAutoFocus: (e) => e.preventDefault(),
3857
+ className: "w-64 bg-surface border border-border rounded-lg mt-1 shadow-md z-50 overflow-y-auto max-h-36 animate-in fade-in-0 zoom-in-95",
3858
+ children: loading ? /* @__PURE__ */ jsxs("div", { className: "h-full w-full flex items-center justify-center gap-2 py-4 text-sm text-foreground-secondary", children: [
3859
+ /* @__PURE__ */ jsx(LoadingSpinner, { inline: true, size: "xs" }),
3860
+ /* @__PURE__ */ jsx("span", { children: loadingText })
3861
+ ] }) : foundItems.length === 0 ? /* @__PURE__ */ jsx("div", { className: "h-full w-full flex flex-col items-center justify-center py-4 text-sm text-foreground-secondary", children: emptyText }) : /* @__PURE__ */ jsx("div", { role: "listbox", children: foundItems.map((item) => /* @__PURE__ */ jsxs(
3862
+ "div",
3863
+ {
3864
+ role: "option",
3865
+ tabIndex: 0,
3866
+ className: "text-sm flex items-center gap-2 p-2 transition-colors duration-150 hover:bg-surface-raised cursor-pointer text-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-accent",
3867
+ onClick: () => handleSelect(item),
3868
+ onKeyDown: (e) => {
3869
+ if (e.key === "Enter" || e.key === " ") {
3870
+ e.preventDefault();
3871
+ handleSelect(item);
3872
+ }
3873
+ },
3874
+ children: [
3875
+ item.icon,
3876
+ /* @__PURE__ */ jsxs("span", { children: [
3877
+ item.label,
3878
+ " (",
3879
+ item.value,
3880
+ ")"
3881
+ ] })
3882
+ ]
3883
+ },
3884
+ item.key
3885
+ )) })
3886
+ }
3887
+ ) })
3888
+ ] })
3871
3889
  }
3872
3890
  );
3873
3891
  }
@@ -4947,27 +4965,13 @@ function TagsInput({
4947
4965
  className: `flex flex-wrap items-center gap-1.5 min-h-[36px] ${fieldShell({ size, hasError, disabled, focusWithin: true, sized: false })} px-2 py-1.5`,
4948
4966
  onClick: () => inputRef.current?.focus(),
4949
4967
  children: [
4950
- tags.map((tag, idx) => /* @__PURE__ */ jsxs(
4951
- "span",
4968
+ tags.map((tag, idx) => /* @__PURE__ */ jsx(
4969
+ Tag,
4952
4970
  {
4953
- className: "inline-flex items-center gap-1 rounded-md bg-surface-raised text-foreground text-xs pl-2 pr-1 py-0.5 border border-border",
4954
- children: [
4955
- tag,
4956
- /* @__PURE__ */ jsx(
4957
- "button",
4958
- {
4959
- type: "button",
4960
- disabled,
4961
- onClick: (e) => {
4962
- e.stopPropagation();
4963
- removeTag(idx);
4964
- },
4965
- "aria-label": `Remove ${tag}`,
4966
- className: "inline-flex items-center justify-center w-4 h-4 rounded text-foreground-muted hover:text-status-error hover:bg-surface transition-colors focus:outline-none focus-visible:ring-1 focus-visible:ring-accent",
4967
- children: /* @__PURE__ */ jsx("svg", { width: "10", height: "10", viewBox: "0 0 20 20", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M15 5L5 15M5 5l10 10", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round" }) })
4968
- }
4969
- )
4970
- ]
4971
+ disabled,
4972
+ removeLabel: `Remove ${tag}`,
4973
+ onRemove: () => removeTag(idx),
4974
+ children: tag
4971
4975
  },
4972
4976
  `${tag}-${idx}`
4973
4977
  )),
@@ -5201,9 +5205,9 @@ function Rating({
5201
5205
  /* @__PURE__ */ jsx(
5202
5206
  "span",
5203
5207
  {
5204
- className: "absolute inset-0 overflow-hidden",
5208
+ className: "absolute inset-y-0 left-0 overflow-hidden",
5205
5209
  style: { width: `${fillFraction * 100}%` },
5206
- children: icon(true)
5210
+ children: /* @__PURE__ */ jsx("span", { className: `block absolute inset-y-0 left-0 ${ICON_SIZE[size]} max-w-none`, children: icon(true) })
5207
5211
  }
5208
5212
  )
5209
5213
  ]