@geomak/ui 6.27.0 → 6.27.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.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { colors_default } from './chunk-I2P4JJDB.js';
2
2
  export { colors_default as COLORS, PALETTE as palette, semanticTokens, vars } from './chunk-I2P4JJDB.js';
3
3
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
4
- import React29, { createContext, useState, useEffect, useMemo, useId, useCallback, useRef, useContext, useSyncExternalStore, useLayoutEffect } from 'react';
4
+ import React28, { createContext, useState, useEffect, useMemo, useId, useCallback, useRef, useContext, useSyncExternalStore, useLayoutEffect } from 'react';
5
5
  import { createPortal } from 'react-dom';
6
6
  import * as AvatarPrimitive from '@radix-ui/react-avatar';
7
7
  import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
@@ -693,7 +693,7 @@ var SIZE_CLASSES = {
693
693
  md: "h-9 px-4 text-sm gap-1.5 rounded-lg",
694
694
  lg: "h-11 px-5 text-sm gap-2 rounded-xl"
695
695
  };
696
- var Button = React29.forwardRef(function Button2({
696
+ var Button = React28.forwardRef(function Button2({
697
697
  content,
698
698
  variant = "primary",
699
699
  size = "md",
@@ -801,7 +801,7 @@ function MenuButton({
801
801
  "data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
802
802
  "data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95"
803
803
  ].join(" "),
804
- children: items.map((item) => /* @__PURE__ */ jsxs(React29.Fragment, { children: [
804
+ children: items.map((item) => /* @__PURE__ */ jsxs(React28.Fragment, { children: [
805
805
  item.separatorBefore && /* @__PURE__ */ jsx(DropdownMenu.Separator, { className: "my-1 h-px bg-border" }),
806
806
  /* @__PURE__ */ jsxs(
807
807
  DropdownMenu.Item,
@@ -1900,7 +1900,7 @@ function Kbd({
1900
1900
  style
1901
1901
  }) {
1902
1902
  if (keys && keys.length > 0) {
1903
- return /* @__PURE__ */ jsx("span", { className: ["inline-flex items-center gap-1", className].filter(Boolean).join(" "), style, children: keys.map((k, i) => /* @__PURE__ */ jsxs(React29.Fragment, { children: [
1903
+ return /* @__PURE__ */ jsx("span", { className: ["inline-flex items-center gap-1", className].filter(Boolean).join(" "), style, children: keys.map((k, i) => /* @__PURE__ */ jsxs(React28.Fragment, { children: [
1904
1904
  i > 0 && /* @__PURE__ */ jsx("span", { className: "text-foreground-muted text-xs select-none", children: separator }),
1905
1905
  /* @__PURE__ */ jsx("kbd", { className: [cap, SIZE3[size]].join(" "), children: k })
1906
1906
  ] }, `${k}-${i}`)) });
@@ -1992,7 +1992,7 @@ function FlatCarousel({
1992
1992
  style
1993
1993
  }) {
1994
1994
  const scrollerRef = useRef(null);
1995
- const slides = React29.Children.toArray(children);
1995
+ const slides = React28.Children.toArray(children);
1996
1996
  const [active, setActive] = useState(0);
1997
1997
  const [atStart, setAtStart] = useState(true);
1998
1998
  const [atEnd, setAtEnd] = useState(false);
@@ -2047,7 +2047,7 @@ function RotatingCarousel({
2047
2047
  className = "",
2048
2048
  style
2049
2049
  }) {
2050
- const slides = React29.Children.toArray(children);
2050
+ const slides = React28.Children.toArray(children);
2051
2051
  const count = slides.length;
2052
2052
  const [active, setActive] = useState(0);
2053
2053
  const reduced = useReducedMotion();
@@ -5202,7 +5202,7 @@ function Wizard({
5202
5202
  ] });
5203
5203
  }
5204
5204
  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" }) });
5205
- var SearchInput = React29.forwardRef(function SearchInput2({ value, onChange, disabled, label, htmlFor, placeholder, name, inputStyle, style, layout = "vertical", size = "md", icon, helperText, className }, ref) {
5205
+ var SearchInput = React28.forwardRef(function SearchInput2({ value, onChange, disabled, label, htmlFor, placeholder, name, inputStyle, style, layout = "vertical", size = "md", icon, helperText, className }, ref) {
5206
5206
  return /* @__PURE__ */ jsx(Field, { className, label, htmlFor, layout, helperText, children: /* @__PURE__ */ jsxs(
5207
5207
  "div",
5208
5208
  {
@@ -5231,308 +5231,6 @@ var SearchInput = React29.forwardRef(function SearchInput2({ value, onChange, di
5231
5231
  ) });
5232
5232
  });
5233
5233
  var SearchInput_default = SearchInput;
5234
- function Tag({ children, onRemove, removeLabel, disabled }) {
5235
- 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: [
5236
- /* @__PURE__ */ jsx("span", { className: "truncate", children }),
5237
- onRemove && /* @__PURE__ */ jsx(
5238
- "button",
5239
- {
5240
- type: "button",
5241
- disabled,
5242
- onClick: (e) => {
5243
- e.stopPropagation();
5244
- onRemove();
5245
- },
5246
- "aria-label": removeLabel ?? "Remove",
5247
- 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",
5248
- 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" }) })
5249
- }
5250
- )
5251
- ] });
5252
- }
5253
- function MultiTagRow({
5254
- values,
5255
- disabled,
5256
- labelFor,
5257
- onRemove
5258
- }) {
5259
- const wrapRef = useRef(null);
5260
- const measureRef = useRef(null);
5261
- const [visibleCount, setVisibleCount] = useState(values.length);
5262
- const key = values.map(String).join("|");
5263
- useLayoutEffect(() => {
5264
- const wrap = wrapRef.current;
5265
- const measure = measureRef.current;
5266
- if (!wrap || !measure) return;
5267
- const GAP = 6;
5268
- const recompute = () => {
5269
- const avail = wrap.clientWidth;
5270
- const tagEls = Array.from(measure.querySelectorAll("[data-mt]"));
5271
- const moreEl = measure.querySelector("[data-mm]");
5272
- const widths = tagEls.map((e) => e.offsetWidth);
5273
- const moreW = moreEl ? moreEl.offsetWidth : 0;
5274
- if (widths.length === 0) {
5275
- setVisibleCount(0);
5276
- return;
5277
- }
5278
- let used = 0;
5279
- let count = 0;
5280
- for (let i = 0; i < widths.length; i++) {
5281
- const w = widths[i] + (i > 0 ? GAP : 0);
5282
- if (used + w <= avail) {
5283
- used += w;
5284
- count++;
5285
- } else break;
5286
- }
5287
- if (count < widths.length) {
5288
- while (count > 0) {
5289
- let t = 0;
5290
- for (let i = 0; i < count; i++) t += widths[i] + (i > 0 ? GAP : 0);
5291
- t += GAP + moreW;
5292
- if (t <= avail) break;
5293
- count--;
5294
- }
5295
- }
5296
- setVisibleCount(count);
5297
- };
5298
- recompute();
5299
- const ro = new ResizeObserver(recompute);
5300
- ro.observe(wrap);
5301
- return () => ro.disconnect();
5302
- }, [key]);
5303
- const hidden = values.length - visibleCount;
5304
- 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: [
5305
- "+",
5306
- n,
5307
- " more"
5308
- ] });
5309
- return /* @__PURE__ */ jsxs("div", { ref: wrapRef, className: "relative flex-1 min-w-0 flex flex-nowrap items-center gap-1.5 overflow-hidden", children: [
5310
- /* @__PURE__ */ jsxs(
5311
- "div",
5312
- {
5313
- ref: measureRef,
5314
- "aria-hidden": "true",
5315
- className: "absolute invisible pointer-events-none flex flex-nowrap items-center gap-1.5",
5316
- style: { left: -9999, top: -9999 },
5317
- children: [
5318
- values.map((val) => /* @__PURE__ */ jsx("span", { "data-mt": true, children: /* @__PURE__ */ jsx(Tag, { removeLabel: "x", onRemove: () => {
5319
- }, children: labelFor(val) }) }, `m-${val}`)),
5320
- /* @__PURE__ */ jsx("span", { "data-mm": true, children: moreChip(values.length) })
5321
- ]
5322
- }
5323
- ),
5324
- values.slice(0, visibleCount).map((val) => /* @__PURE__ */ jsx(
5325
- Tag,
5326
- {
5327
- disabled,
5328
- removeLabel: `Remove ${labelFor(val)}`,
5329
- onRemove: () => onRemove(val),
5330
- children: labelFor(val)
5331
- },
5332
- String(val)
5333
- )),
5334
- hidden > 0 && moreChip(hidden)
5335
- ] });
5336
- }
5337
- function Dropdown({
5338
- isMultiselect = false,
5339
- hasSearch = true,
5340
- label,
5341
- name,
5342
- value,
5343
- onChange,
5344
- disabled,
5345
- layout = "horizontal",
5346
- helperText,
5347
- required,
5348
- errorMessage,
5349
- style = {},
5350
- htmlFor,
5351
- items = [],
5352
- labelStyle = {},
5353
- placeholder,
5354
- size = "md",
5355
- className = ""
5356
- }) {
5357
- const [open, setOpen] = useState(false);
5358
- const [selectedItems, setSelectedItems] = useState([]);
5359
- const [searchTerm, setSearchTerm] = useState("");
5360
- const [innerItems, setInnerItems] = useState([]);
5361
- const errorId = useId();
5362
- const hasError = errorMessage != null;
5363
- useEffect(() => {
5364
- setInnerItems(items);
5365
- }, [items]);
5366
- useEffect(() => {
5367
- if (isMultiselect && Array.isArray(value)) {
5368
- setSelectedItems(value);
5369
- }
5370
- }, [isMultiselect, value]);
5371
- const selectItem = (key) => {
5372
- if (isMultiselect) {
5373
- const next = selectedItems.includes(key) ? selectedItems.filter((it) => it !== key) : [...selectedItems, key];
5374
- setSelectedItems(next);
5375
- onChange?.({ target: { value: next, id: htmlFor, name } });
5376
- } else {
5377
- setSelectedItems([key]);
5378
- onChange?.({ target: { value: key, id: htmlFor, name } });
5379
- setOpen(false);
5380
- }
5381
- };
5382
- const removeSelected = (key) => {
5383
- if (isMultiselect) {
5384
- const next = selectedItems.filter((it) => it !== key);
5385
- setSelectedItems(next);
5386
- onChange?.({ target: { value: next, id: htmlFor, name } });
5387
- } else {
5388
- setSelectedItems([]);
5389
- onChange?.({ target: { value: "", id: htmlFor, name } });
5390
- }
5391
- };
5392
- const labelFor = (key) => innerItems.find((it) => it.key === key)?.label ?? String(key);
5393
- const onSearchChange = (e) => {
5394
- const term = e.target.value;
5395
- setSearchTerm(term);
5396
- setInnerItems(
5397
- term.trim() === "" ? items : items.filter(
5398
- (it) => String(it.label).toLowerCase().includes(term.toLowerCase())
5399
- )
5400
- );
5401
- };
5402
- const isSelected = (key) => Array.isArray(value) ? value.includes(key) : value === key;
5403
- return /* @__PURE__ */ jsxs("div", { className: className || void 0, children: [
5404
- /* @__PURE__ */ jsxs(
5405
- "div",
5406
- {
5407
- className: `flex ${layout === "vertical" ? "flex-col gap-1.5" : "flex-row items-start gap-3"}`,
5408
- children: [
5409
- /* @__PURE__ */ jsx(
5410
- FieldLabel,
5411
- {
5412
- label,
5413
- htmlFor,
5414
- required,
5415
- helperText,
5416
- horizontal: layout === "horizontal",
5417
- style: labelStyle
5418
- }
5419
- ),
5420
- /* @__PURE__ */ jsxs(Popover.Root, { open: open && !disabled, onOpenChange: (o) => !disabled && setOpen(o), children: [
5421
- /* @__PURE__ */ jsx(Popover.Trigger, { asChild: true, children: /* @__PURE__ */ jsxs(
5422
- "div",
5423
- {
5424
- id: htmlFor,
5425
- role: "combobox",
5426
- "aria-expanded": open,
5427
- "aria-haspopup": "listbox",
5428
- "aria-invalid": hasError || void 0,
5429
- "aria-describedby": hasError ? errorId : void 0,
5430
- style: { width: 240, ...style },
5431
- 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 })}`,
5432
- tabIndex: disabled ? -1 : 0,
5433
- onKeyDown: (e) => {
5434
- if (disabled) return;
5435
- if (e.key === "Enter" || e.key === " " || e.key === "ArrowDown" || e.key === "ArrowUp") {
5436
- e.preventDefault();
5437
- setOpen(true);
5438
- }
5439
- },
5440
- children: [
5441
- !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(
5442
- MultiTagRow,
5443
- {
5444
- values: value,
5445
- disabled,
5446
- labelFor,
5447
- onRemove: removeSelected
5448
- }
5449
- ) : /* @__PURE__ */ jsx("div", { className: "flex-1 min-w-0 flex items-center overflow-hidden", children: /* @__PURE__ */ jsx(
5450
- Tag,
5451
- {
5452
- disabled,
5453
- removeLabel: `Remove ${labelFor(value)}`,
5454
- onRemove: () => removeSelected(value),
5455
- children: labelFor(value)
5456
- }
5457
- ) }),
5458
- /* @__PURE__ */ jsx("div", { className: `flex-shrink-0 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" }) }) })
5459
- ]
5460
- }
5461
- ) }),
5462
- /* @__PURE__ */ jsx(Popover.Portal, { children: /* @__PURE__ */ jsxs(
5463
- Popover.Content,
5464
- {
5465
- align: "start",
5466
- sideOffset: 4,
5467
- style: { width: style?.width || 240 },
5468
- className: "bg-surface text-foreground border border-border rounded-lg shadow-md z-50 p-2 animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
5469
- onInteractOutside: () => setOpen(false),
5470
- children: [
5471
- hasSearch && /* @__PURE__ */ jsx("div", { className: "mb-2", children: /* @__PURE__ */ jsx(
5472
- SearchInput_default,
5473
- {
5474
- style: { width: "100%" },
5475
- inputStyle: { width: "100%" },
5476
- value: searchTerm,
5477
- onChange: onSearchChange,
5478
- placeholder: "Search..."
5479
- }
5480
- ) }),
5481
- /* @__PURE__ */ jsx("div", { role: "listbox", "aria-multiselectable": isMultiselect, className: "max-h-40 overflow-y-auto", children: innerItems.map((item) => (
5482
- // aria-rowindex was previously set here but
5483
- // it's invalid ARIA on role="option" (it
5484
- // belongs on rows of a grid/treegrid). Dropped.
5485
- // tabIndex={0} + Enter/Space handler makes the
5486
- // option keyboard-activatable; the full
5487
- // combobox roving-tabindex pattern is deferred
5488
- // until the planned Phase-5 rewrite.
5489
- /* @__PURE__ */ jsxs(
5490
- "div",
5491
- {
5492
- role: "option",
5493
- "aria-selected": isSelected(item.key),
5494
- tabIndex: 0,
5495
- className: `flex items-center justify-between p-2 hover:bg-accent hover:text-accent-fg transition-colors duration-150 text-sm rounded-lg cursor-pointer focus:outline-none focus-visible:ring-2 focus-visible:ring-accent ${selectedItems.includes(item.key) ? "bg-surface-raised text-foreground" : "text-foreground"}`,
5496
- onClick: () => selectItem(item.key),
5497
- onKeyDown: (e) => {
5498
- if (e.key === "Enter" || e.key === " ") {
5499
- e.preventDefault();
5500
- selectItem(item.key);
5501
- }
5502
- },
5503
- children: [
5504
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-xs", children: [
5505
- item.icon && /* @__PURE__ */ jsx("div", { children: item.icon }),
5506
- item.label
5507
- ] }),
5508
- isSelected(item.key) && // currentColor — checkmark follows
5509
- // the item's text colour, which
5510
- // flips automatically on hover.
5511
- /* @__PURE__ */ jsx("svg", { width: "16", height: "16", viewBox: "0 0 20 20", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx(
5512
- "path",
5513
- {
5514
- d: "M4 10l4.5 4.5L16 6",
5515
- stroke: "currentColor",
5516
- strokeWidth: "2",
5517
- strokeLinecap: "round",
5518
- strokeLinejoin: "round"
5519
- }
5520
- ) })
5521
- ]
5522
- },
5523
- item.key
5524
- )
5525
- )) })
5526
- ]
5527
- }
5528
- ) })
5529
- ] })
5530
- ]
5531
- }
5532
- ),
5533
- hasError && /* @__PURE__ */ jsx("div", { id: errorId, className: "text-status-error text-xs mt-1", children: errorMessage })
5534
- ] });
5535
- }
5536
5234
  var SHIMMER = "oxy-skeleton rounded-sm bg-surface-raised";
5537
5235
  function SkeletonBox({ width, height = 16, radius, className = "", style }) {
5538
5236
  return /* @__PURE__ */ jsx(
@@ -5703,7 +5401,7 @@ function TableBody({
5703
5401
  return /* @__PURE__ */ jsx("tbody", { children: rows.map((row, i) => {
5704
5402
  const rowKey = getRowKey(row, i);
5705
5403
  const isExpanded = expanded.has(rowKey);
5706
- return /* @__PURE__ */ jsxs(React29.Fragment, { children: [
5404
+ return /* @__PURE__ */ jsxs(React28.Fragment, { children: [
5707
5405
  /* @__PURE__ */ jsxs(
5708
5406
  "tr",
5709
5407
  {
@@ -5767,6 +5465,8 @@ function Pagination({
5767
5465
  if (next) setPerPageKey(next.key);
5768
5466
  }
5769
5467
  }, [serverSide, options.perPage, picker]);
5468
+ const currentOpt = picker.find((o) => o.key === displayPerPageKey);
5469
+ const currentPerPageLabel = currentOpt?.label ?? currentOpt?.value ?? options.perPage ?? "";
5770
5470
  const navBtn = (icon, disabled, onClick, title) => /* @__PURE__ */ jsx(IconButton, { type: "bordered", size: "sm", disabled, onClick, icon, title });
5771
5471
  const chevronRight = /* @__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: "M9 5l7 7-7 7" }) });
5772
5472
  const doubleChevronRight = /* @__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: "M13 5l7 7-7 7M5 5l7 7-7 7" }) });
@@ -5774,21 +5474,20 @@ function Pagination({
5774
5474
  options.withPicker && /* @__PURE__ */ jsxs("div", { className: "mr-auto flex items-center gap-2", children: [
5775
5475
  /* @__PURE__ */ jsx("span", { className: "whitespace-nowrap text-xs text-foreground-muted", children: "Rows per page" }),
5776
5476
  /* @__PURE__ */ jsx(
5777
- Dropdown,
5477
+ MenuButton,
5778
5478
  {
5479
+ variant: "secondary",
5779
5480
  size: "sm",
5780
- style: { width: 76 },
5781
- hasSearch: false,
5782
- items: picker,
5783
- isMultiselect: false,
5784
- value: displayPerPageKey,
5785
- onChange: ({ target: { value } }) => {
5786
- if (Array.isArray(value)) return;
5787
- const numKey = typeof value === "number" ? value : Number(value);
5788
- if (!serverSide) setPerPageKey(numKey);
5789
- const opt = picker.find((o) => o.key === numKey);
5790
- onPerPageChange(opt?.label ?? opt?.value ?? numKey);
5791
- }
5481
+ side: "top",
5482
+ label: String(currentPerPageLabel),
5483
+ items: picker.map((o) => ({
5484
+ key: o.key,
5485
+ label: String(o.label ?? o.value ?? o.key),
5486
+ onSelect: () => {
5487
+ if (!serverSide) setPerPageKey(o.key);
5488
+ onPerPageChange(o.label ?? o.value ?? o.key);
5489
+ }
5490
+ }))
5792
5491
  }
5793
5492
  )
5794
5493
  ] }),
@@ -6244,8 +5943,8 @@ function MegaMenuLink({ href, icon, description, active, onClick, children, clas
6244
5943
  function MegaMenuFeatured({ children, className = "" }) {
6245
5944
  return /* @__PURE__ */ jsx("div", { className: ["min-w-0 rounded-lg bg-surface-raised border border-border p-4 flex flex-col", className].filter(Boolean).join(" "), children });
6246
5945
  }
6247
- var elementsOfType = (children, type) => React29.Children.toArray(children).filter(
6248
- (c) => React29.isValidElement(c) && c.type === type
5946
+ var elementsOfType = (children, type) => React28.Children.toArray(children).filter(
5947
+ (c) => React28.isValidElement(c) && c.type === type
6249
5948
  );
6250
5949
  var MOBILE_CHEVRON = /* @__PURE__ */ jsx(
6251
5950
  "svg",
@@ -6282,9 +5981,9 @@ function MobileLinkRow({ link, onNavigate }) {
6282
5981
  );
6283
5982
  }
6284
5983
  function MobilePanel({ panel, onNavigate }) {
6285
- const nodes = React29.Children.toArray(panel.props.children);
5984
+ const nodes = React28.Children.toArray(panel.props.children);
6286
5985
  return /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-4 px-2 pb-3 pt-1", children: nodes.map((node, i) => {
6287
- if (!React29.isValidElement(node)) return null;
5986
+ if (!React28.isValidElement(node)) return null;
6288
5987
  const el = node;
6289
5988
  if (el.type === MegaMenuSection) {
6290
5989
  const { title, children } = el.props;
@@ -6693,7 +6392,7 @@ function ThemeProvider({
6693
6392
  className = "",
6694
6393
  style
6695
6394
  }) {
6696
- const id = React29.useId().replace(/:/g, "");
6395
+ const id = React28.useId().replace(/:/g, "");
6697
6396
  const scopeClass = `geo-th-${id}`;
6698
6397
  const divRef = useRef(null);
6699
6398
  useEffect(() => {
@@ -7151,77 +6850,379 @@ function RadioGroup({
7151
6850
  }
7152
6851
  );
7153
6852
  }
7154
- function Switch({
7155
- checked,
7156
- defaultChecked = false,
7157
- onChange,
7158
- checkedIcon,
7159
- uncheckedIcon,
6853
+ function Switch({
6854
+ checked,
6855
+ defaultChecked = false,
6856
+ onChange,
6857
+ checkedIcon,
6858
+ uncheckedIcon,
6859
+ label,
6860
+ layout = "horizontal",
6861
+ helperText,
6862
+ className,
6863
+ offLabel,
6864
+ onLabel,
6865
+ name,
6866
+ required,
6867
+ disabled,
6868
+ errorMessage
6869
+ }) {
6870
+ const id = useId();
6871
+ const errorId = useId();
6872
+ const hasError = errorMessage != null;
6873
+ const isControlled = checked !== void 0;
6874
+ const [internal, setInternal] = useState(defaultChecked);
6875
+ const isOn = isControlled ? checked : internal;
6876
+ const handle = (c) => {
6877
+ if (!isControlled) setInternal(c);
6878
+ onChange?.({ target: { checked: c, name } });
6879
+ };
6880
+ const stateLabel = (active) => [
6881
+ "text-sm select-none transition-colors",
6882
+ active ? "text-foreground font-medium" : "text-foreground-muted",
6883
+ disabled ? "opacity-50" : "cursor-pointer"
6884
+ ].filter(Boolean).join(" ");
6885
+ return /* @__PURE__ */ jsx(
6886
+ Field,
6887
+ {
6888
+ className,
6889
+ label,
6890
+ htmlFor: id,
6891
+ errorId,
6892
+ errorMessage,
6893
+ layout,
6894
+ required,
6895
+ helperText,
6896
+ labelAlign: "center",
6897
+ children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2.5", children: [
6898
+ offLabel != null && /* @__PURE__ */ jsx("label", { htmlFor: id, className: stateLabel(!isOn), children: offLabel }),
6899
+ /* @__PURE__ */ jsx(
6900
+ SwitchPrimitive.Root,
6901
+ {
6902
+ id,
6903
+ name,
6904
+ checked: isOn,
6905
+ onCheckedChange: handle,
6906
+ disabled,
6907
+ required,
6908
+ "aria-invalid": hasError || void 0,
6909
+ "aria-describedby": hasError ? errorId : void 0,
6910
+ 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",
6911
+ children: /* @__PURE__ */ jsx(
6912
+ SwitchPrimitive.Thumb,
6913
+ {
6914
+ 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]",
6915
+ children: checkedIcon && uncheckedIcon ? /* @__PURE__ */ jsx("span", { className: "flex items-center justify-center w-3 h-3", children: isOn ? checkedIcon : uncheckedIcon }) : null
6916
+ }
6917
+ )
6918
+ }
6919
+ ),
6920
+ onLabel != null && /* @__PURE__ */ jsx("label", { htmlFor: id, className: stateLabel(isOn), children: onLabel })
6921
+ ] })
6922
+ }
6923
+ );
6924
+ }
6925
+ function Tag({ children, onRemove, removeLabel, disabled }) {
6926
+ 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: [
6927
+ /* @__PURE__ */ jsx("span", { className: "truncate", children }),
6928
+ onRemove && /* @__PURE__ */ jsx(
6929
+ "button",
6930
+ {
6931
+ type: "button",
6932
+ disabled,
6933
+ onClick: (e) => {
6934
+ e.stopPropagation();
6935
+ onRemove();
6936
+ },
6937
+ "aria-label": removeLabel ?? "Remove",
6938
+ 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",
6939
+ 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" }) })
6940
+ }
6941
+ )
6942
+ ] });
6943
+ }
6944
+ function MultiTagRow({
6945
+ values,
6946
+ disabled,
6947
+ labelFor,
6948
+ onRemove
6949
+ }) {
6950
+ const wrapRef = useRef(null);
6951
+ const measureRef = useRef(null);
6952
+ const [visibleCount, setVisibleCount] = useState(values.length);
6953
+ const key = values.map(String).join("|");
6954
+ useLayoutEffect(() => {
6955
+ const wrap = wrapRef.current;
6956
+ const measure = measureRef.current;
6957
+ if (!wrap || !measure) return;
6958
+ const GAP = 6;
6959
+ const recompute = () => {
6960
+ const avail = wrap.clientWidth;
6961
+ const tagEls = Array.from(measure.querySelectorAll("[data-mt]"));
6962
+ const moreEl = measure.querySelector("[data-mm]");
6963
+ const widths = tagEls.map((e) => e.offsetWidth);
6964
+ const moreW = moreEl ? moreEl.offsetWidth : 0;
6965
+ if (widths.length === 0) {
6966
+ setVisibleCount(0);
6967
+ return;
6968
+ }
6969
+ let used = 0;
6970
+ let count = 0;
6971
+ for (let i = 0; i < widths.length; i++) {
6972
+ const w = widths[i] + (i > 0 ? GAP : 0);
6973
+ if (used + w <= avail) {
6974
+ used += w;
6975
+ count++;
6976
+ } else break;
6977
+ }
6978
+ if (count < widths.length) {
6979
+ while (count > 0) {
6980
+ let t = 0;
6981
+ for (let i = 0; i < count; i++) t += widths[i] + (i > 0 ? GAP : 0);
6982
+ t += GAP + moreW;
6983
+ if (t <= avail) break;
6984
+ count--;
6985
+ }
6986
+ }
6987
+ setVisibleCount(count);
6988
+ };
6989
+ recompute();
6990
+ const ro = new ResizeObserver(recompute);
6991
+ ro.observe(wrap);
6992
+ return () => ro.disconnect();
6993
+ }, [key]);
6994
+ const hidden = values.length - visibleCount;
6995
+ 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: [
6996
+ "+",
6997
+ n,
6998
+ " more"
6999
+ ] });
7000
+ return /* @__PURE__ */ jsxs("div", { ref: wrapRef, className: "relative flex-1 min-w-0 flex flex-nowrap items-center gap-1.5 overflow-hidden", children: [
7001
+ /* @__PURE__ */ jsxs(
7002
+ "div",
7003
+ {
7004
+ ref: measureRef,
7005
+ "aria-hidden": "true",
7006
+ className: "absolute invisible pointer-events-none flex flex-nowrap items-center gap-1.5",
7007
+ style: { left: -9999, top: -9999 },
7008
+ children: [
7009
+ values.map((val) => /* @__PURE__ */ jsx("span", { "data-mt": true, children: /* @__PURE__ */ jsx(Tag, { removeLabel: "x", onRemove: () => {
7010
+ }, children: labelFor(val) }) }, `m-${val}`)),
7011
+ /* @__PURE__ */ jsx("span", { "data-mm": true, children: moreChip(values.length) })
7012
+ ]
7013
+ }
7014
+ ),
7015
+ values.slice(0, visibleCount).map((val) => /* @__PURE__ */ jsx(
7016
+ Tag,
7017
+ {
7018
+ disabled,
7019
+ removeLabel: `Remove ${labelFor(val)}`,
7020
+ onRemove: () => onRemove(val),
7021
+ children: labelFor(val)
7022
+ },
7023
+ String(val)
7024
+ )),
7025
+ hidden > 0 && moreChip(hidden)
7026
+ ] });
7027
+ }
7028
+ function Dropdown({
7029
+ isMultiselect = false,
7030
+ hasSearch = true,
7160
7031
  label,
7032
+ name,
7033
+ value,
7034
+ onChange,
7035
+ disabled,
7161
7036
  layout = "horizontal",
7162
7037
  helperText,
7163
- className,
7164
- offLabel,
7165
- onLabel,
7166
- name,
7167
7038
  required,
7168
- disabled,
7169
- errorMessage
7039
+ errorMessage,
7040
+ style = {},
7041
+ htmlFor,
7042
+ items = [],
7043
+ labelStyle = {},
7044
+ placeholder,
7045
+ size = "md",
7046
+ className = ""
7170
7047
  }) {
7171
- const id = useId();
7048
+ const [open, setOpen] = useState(false);
7049
+ const [selectedItems, setSelectedItems] = useState([]);
7050
+ const [searchTerm, setSearchTerm] = useState("");
7051
+ const [innerItems, setInnerItems] = useState([]);
7172
7052
  const errorId = useId();
7173
7053
  const hasError = errorMessage != null;
7174
- const isControlled = checked !== void 0;
7175
- const [internal, setInternal] = useState(defaultChecked);
7176
- const isOn = isControlled ? checked : internal;
7177
- const handle = (c) => {
7178
- if (!isControlled) setInternal(c);
7179
- onChange?.({ target: { checked: c, name } });
7054
+ useEffect(() => {
7055
+ setInnerItems(items);
7056
+ }, [items]);
7057
+ useEffect(() => {
7058
+ if (isMultiselect && Array.isArray(value)) {
7059
+ setSelectedItems(value);
7060
+ }
7061
+ }, [isMultiselect, value]);
7062
+ const selectItem = (key) => {
7063
+ if (isMultiselect) {
7064
+ const next = selectedItems.includes(key) ? selectedItems.filter((it) => it !== key) : [...selectedItems, key];
7065
+ setSelectedItems(next);
7066
+ onChange?.({ target: { value: next, id: htmlFor, name } });
7067
+ } else {
7068
+ setSelectedItems([key]);
7069
+ onChange?.({ target: { value: key, id: htmlFor, name } });
7070
+ setOpen(false);
7071
+ }
7180
7072
  };
7181
- const stateLabel = (active) => [
7182
- "text-sm select-none transition-colors",
7183
- active ? "text-foreground font-medium" : "text-foreground-muted",
7184
- disabled ? "opacity-50" : "cursor-pointer"
7185
- ].filter(Boolean).join(" ");
7186
- return /* @__PURE__ */ jsx(
7187
- Field,
7188
- {
7189
- className,
7190
- label,
7191
- htmlFor: id,
7192
- errorId,
7193
- errorMessage,
7194
- layout,
7195
- required,
7196
- helperText,
7197
- labelAlign: "center",
7198
- children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2.5", children: [
7199
- offLabel != null && /* @__PURE__ */ jsx("label", { htmlFor: id, className: stateLabel(!isOn), children: offLabel }),
7200
- /* @__PURE__ */ jsx(
7201
- SwitchPrimitive.Root,
7202
- {
7203
- id,
7204
- name,
7205
- checked: isOn,
7206
- onCheckedChange: handle,
7207
- disabled,
7208
- required,
7209
- "aria-invalid": hasError || void 0,
7210
- "aria-describedby": hasError ? errorId : void 0,
7211
- 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",
7212
- children: /* @__PURE__ */ jsx(
7213
- SwitchPrimitive.Thumb,
7073
+ const removeSelected = (key) => {
7074
+ if (isMultiselect) {
7075
+ const next = selectedItems.filter((it) => it !== key);
7076
+ setSelectedItems(next);
7077
+ onChange?.({ target: { value: next, id: htmlFor, name } });
7078
+ } else {
7079
+ setSelectedItems([]);
7080
+ onChange?.({ target: { value: "", id: htmlFor, name } });
7081
+ }
7082
+ };
7083
+ const labelFor = (key) => innerItems.find((it) => it.key === key)?.label ?? String(key);
7084
+ const onSearchChange = (e) => {
7085
+ const term = e.target.value;
7086
+ setSearchTerm(term);
7087
+ setInnerItems(
7088
+ term.trim() === "" ? items : items.filter(
7089
+ (it) => String(it.label).toLowerCase().includes(term.toLowerCase())
7090
+ )
7091
+ );
7092
+ };
7093
+ const isSelected = (key) => Array.isArray(value) ? value.includes(key) : value === key;
7094
+ return /* @__PURE__ */ jsxs("div", { className: className || void 0, children: [
7095
+ /* @__PURE__ */ jsxs(
7096
+ "div",
7097
+ {
7098
+ className: `flex ${layout === "vertical" ? "flex-col gap-1.5" : "flex-row items-start gap-3"}`,
7099
+ children: [
7100
+ /* @__PURE__ */ jsx(
7101
+ FieldLabel,
7102
+ {
7103
+ label,
7104
+ htmlFor,
7105
+ required,
7106
+ helperText,
7107
+ horizontal: layout === "horizontal",
7108
+ style: labelStyle
7109
+ }
7110
+ ),
7111
+ /* @__PURE__ */ jsxs(Popover.Root, { open: open && !disabled, onOpenChange: (o) => !disabled && setOpen(o), children: [
7112
+ /* @__PURE__ */ jsx(Popover.Trigger, { asChild: true, children: /* @__PURE__ */ jsxs(
7113
+ "div",
7214
7114
  {
7215
- 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]",
7216
- children: checkedIcon && uncheckedIcon ? /* @__PURE__ */ jsx("span", { className: "flex items-center justify-center w-3 h-3", children: isOn ? checkedIcon : uncheckedIcon }) : null
7115
+ id: htmlFor,
7116
+ role: "combobox",
7117
+ "aria-expanded": open,
7118
+ "aria-haspopup": "listbox",
7119
+ "aria-invalid": hasError || void 0,
7120
+ "aria-describedby": hasError ? errorId : void 0,
7121
+ style: { width: 240, ...style },
7122
+ 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 })}`,
7123
+ tabIndex: disabled ? -1 : 0,
7124
+ onKeyDown: (e) => {
7125
+ if (disabled) return;
7126
+ if (e.key === "Enter" || e.key === " " || e.key === "ArrowDown" || e.key === "ArrowUp") {
7127
+ e.preventDefault();
7128
+ setOpen(true);
7129
+ }
7130
+ },
7131
+ children: [
7132
+ !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(
7133
+ MultiTagRow,
7134
+ {
7135
+ values: value,
7136
+ disabled,
7137
+ labelFor,
7138
+ onRemove: removeSelected
7139
+ }
7140
+ ) : /* @__PURE__ */ jsx("div", { className: "flex-1 min-w-0 flex items-center overflow-hidden", children: /* @__PURE__ */ jsx(
7141
+ Tag,
7142
+ {
7143
+ disabled,
7144
+ removeLabel: `Remove ${labelFor(value)}`,
7145
+ onRemove: () => removeSelected(value),
7146
+ children: labelFor(value)
7147
+ }
7148
+ ) }),
7149
+ /* @__PURE__ */ jsx("div", { className: `flex-shrink-0 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" }) }) })
7150
+ ]
7217
7151
  }
7218
- )
7219
- }
7220
- ),
7221
- onLabel != null && /* @__PURE__ */ jsx("label", { htmlFor: id, className: stateLabel(isOn), children: onLabel })
7222
- ] })
7223
- }
7224
- );
7152
+ ) }),
7153
+ /* @__PURE__ */ jsx(Popover.Portal, { children: /* @__PURE__ */ jsxs(
7154
+ Popover.Content,
7155
+ {
7156
+ align: "start",
7157
+ sideOffset: 4,
7158
+ style: { width: style?.width || 240 },
7159
+ className: "bg-surface text-foreground border border-border rounded-lg shadow-md z-50 p-2 animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
7160
+ onInteractOutside: () => setOpen(false),
7161
+ children: [
7162
+ hasSearch && /* @__PURE__ */ jsx("div", { className: "mb-2", children: /* @__PURE__ */ jsx(
7163
+ SearchInput_default,
7164
+ {
7165
+ style: { width: "100%" },
7166
+ inputStyle: { width: "100%" },
7167
+ value: searchTerm,
7168
+ onChange: onSearchChange,
7169
+ placeholder: "Search..."
7170
+ }
7171
+ ) }),
7172
+ /* @__PURE__ */ jsx("div", { role: "listbox", "aria-multiselectable": isMultiselect, className: "max-h-40 overflow-y-auto", children: innerItems.map((item) => (
7173
+ // aria-rowindex was previously set here but
7174
+ // it's invalid ARIA on role="option" (it
7175
+ // belongs on rows of a grid/treegrid). Dropped.
7176
+ // tabIndex={0} + Enter/Space handler makes the
7177
+ // option keyboard-activatable; the full
7178
+ // combobox roving-tabindex pattern is deferred
7179
+ // until the planned Phase-5 rewrite.
7180
+ /* @__PURE__ */ jsxs(
7181
+ "div",
7182
+ {
7183
+ role: "option",
7184
+ "aria-selected": isSelected(item.key),
7185
+ tabIndex: 0,
7186
+ className: `flex items-center justify-between p-2 hover:bg-accent hover:text-accent-fg transition-colors duration-150 text-sm rounded-lg cursor-pointer focus:outline-none focus-visible:ring-2 focus-visible:ring-accent ${selectedItems.includes(item.key) ? "bg-surface-raised text-foreground" : "text-foreground"}`,
7187
+ onClick: () => selectItem(item.key),
7188
+ onKeyDown: (e) => {
7189
+ if (e.key === "Enter" || e.key === " ") {
7190
+ e.preventDefault();
7191
+ selectItem(item.key);
7192
+ }
7193
+ },
7194
+ children: [
7195
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-xs", children: [
7196
+ item.icon && /* @__PURE__ */ jsx("div", { children: item.icon }),
7197
+ item.label
7198
+ ] }),
7199
+ isSelected(item.key) && // currentColor — checkmark follows
7200
+ // the item's text colour, which
7201
+ // flips automatically on hover.
7202
+ /* @__PURE__ */ jsx("svg", { width: "16", height: "16", viewBox: "0 0 20 20", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx(
7203
+ "path",
7204
+ {
7205
+ d: "M4 10l4.5 4.5L16 6",
7206
+ stroke: "currentColor",
7207
+ strokeWidth: "2",
7208
+ strokeLinecap: "round",
7209
+ strokeLinejoin: "round"
7210
+ }
7211
+ ) })
7212
+ ]
7213
+ },
7214
+ item.key
7215
+ )
7216
+ )) })
7217
+ ]
7218
+ }
7219
+ ) })
7220
+ ] })
7221
+ ]
7222
+ }
7223
+ ),
7224
+ hasError && /* @__PURE__ */ jsx("div", { id: errorId, className: "text-status-error text-xs mt-1", children: errorMessage })
7225
+ ] });
7225
7226
  }
7226
7227
  function AutoComplete({
7227
7228
  disabled,
@@ -8533,7 +8534,7 @@ function OtpInput({
8533
8534
  emit(valid.join(""));
8534
8535
  focusBox(valid.length);
8535
8536
  };
8536
- return /* @__PURE__ */ jsx(Field, { className, label, htmlFor, errorId, errorMessage, required, layout, helperText, children: /* @__PURE__ */ jsx("div", { className: "flex flex-wrap items-center gap-2", role: "group", "aria-label": typeof label === "string" ? label : "One-time code", children: chars.map((char, idx) => /* @__PURE__ */ jsxs(React29.Fragment, { children: [
8537
+ return /* @__PURE__ */ jsx(Field, { className, label, htmlFor, errorId, errorMessage, required, layout, helperText, children: /* @__PURE__ */ jsx("div", { className: "flex flex-wrap items-center gap-2", role: "group", "aria-label": typeof label === "string" ? label : "One-time code", children: chars.map((char, idx) => /* @__PURE__ */ jsxs(React28.Fragment, { children: [
8537
8538
  /* @__PURE__ */ jsx(
8538
8539
  "input",
8539
8540
  {