@geomak/ui 6.27.0 → 6.27.2

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';
@@ -652,6 +652,17 @@ var VARIANT_CLASSES = {
652
652
  "disabled:border-foreground-muted disabled:text-foreground-muted disabled:cursor-not-allowed",
653
653
  "focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2"
654
654
  ].join(" "),
655
+ // Neutral, flat, hairline-bordered button — the quiet sibling of `secondary`
656
+ // (which carries the accent border). Mirrors the input border behaviour:
657
+ // hairline at rest, strong-hairline on hover. No shadow (flat at rest).
658
+ outline: [
659
+ "bg-surface text-foreground",
660
+ "border border-border hover:border-border-strong",
661
+ "hover:bg-surface-raised",
662
+ "active:bg-surface",
663
+ "disabled:text-foreground-muted disabled:cursor-not-allowed",
664
+ "focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2"
665
+ ].join(" "),
655
666
  ghost: [
656
667
  "bg-transparent text-foreground-secondary",
657
668
  "hover:bg-surface-raised hover:text-foreground",
@@ -693,7 +704,7 @@ var SIZE_CLASSES = {
693
704
  md: "h-9 px-4 text-sm gap-1.5 rounded-lg",
694
705
  lg: "h-11 px-5 text-sm gap-2 rounded-xl"
695
706
  };
696
- var Button = React29.forwardRef(function Button2({
707
+ var Button = React28.forwardRef(function Button2({
697
708
  content,
698
709
  variant = "primary",
699
710
  size = "md",
@@ -801,7 +812,7 @@ function MenuButton({
801
812
  "data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
802
813
  "data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95"
803
814
  ].join(" "),
804
- children: items.map((item) => /* @__PURE__ */ jsxs(React29.Fragment, { children: [
815
+ children: items.map((item) => /* @__PURE__ */ jsxs(React28.Fragment, { children: [
805
816
  item.separatorBefore && /* @__PURE__ */ jsx(DropdownMenu.Separator, { className: "my-1 h-px bg-border" }),
806
817
  /* @__PURE__ */ jsxs(
807
818
  DropdownMenu.Item,
@@ -1900,7 +1911,7 @@ function Kbd({
1900
1911
  style
1901
1912
  }) {
1902
1913
  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: [
1914
+ 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
1915
  i > 0 && /* @__PURE__ */ jsx("span", { className: "text-foreground-muted text-xs select-none", children: separator }),
1905
1916
  /* @__PURE__ */ jsx("kbd", { className: [cap, SIZE3[size]].join(" "), children: k })
1906
1917
  ] }, `${k}-${i}`)) });
@@ -1992,7 +2003,7 @@ function FlatCarousel({
1992
2003
  style
1993
2004
  }) {
1994
2005
  const scrollerRef = useRef(null);
1995
- const slides = React29.Children.toArray(children);
2006
+ const slides = React28.Children.toArray(children);
1996
2007
  const [active, setActive] = useState(0);
1997
2008
  const [atStart, setAtStart] = useState(true);
1998
2009
  const [atEnd, setAtEnd] = useState(false);
@@ -2047,7 +2058,7 @@ function RotatingCarousel({
2047
2058
  className = "",
2048
2059
  style
2049
2060
  }) {
2050
- const slides = React29.Children.toArray(children);
2061
+ const slides = React28.Children.toArray(children);
2051
2062
  const count = slides.length;
2052
2063
  const [active, setActive] = useState(0);
2053
2064
  const reduced = useReducedMotion();
@@ -5202,7 +5213,7 @@ function Wizard({
5202
5213
  ] });
5203
5214
  }
5204
5215
  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) {
5216
+ var SearchInput = React28.forwardRef(function SearchInput2({ value, onChange, disabled, label, htmlFor, placeholder, name, inputStyle, style, layout = "vertical", size = "md", icon, helperText, className }, ref) {
5206
5217
  return /* @__PURE__ */ jsx(Field, { className, label, htmlFor, layout, helperText, children: /* @__PURE__ */ jsxs(
5207
5218
  "div",
5208
5219
  {
@@ -5231,438 +5242,136 @@ var SearchInput = React29.forwardRef(function SearchInput2({ value, onChange, di
5231
5242
  ) });
5232
5243
  });
5233
5244
  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" }) })
5245
+ var SHIMMER = "oxy-skeleton rounded-sm bg-surface-raised";
5246
+ function SkeletonBox({ width, height = 16, radius, className = "", style }) {
5247
+ return /* @__PURE__ */ jsx(
5248
+ "span",
5249
+ {
5250
+ role: "presentation",
5251
+ "aria-hidden": "true",
5252
+ className: `block ${SHIMMER} ${className}`,
5253
+ style: {
5254
+ width: width ?? "100%",
5255
+ height,
5256
+ borderRadius: radius ?? "var(--radius-md)",
5257
+ ...style
5249
5258
  }
5250
- )
5251
- ] });
5259
+ }
5260
+ );
5252
5261
  }
5253
- function MultiTagRow({
5254
- values,
5255
- disabled,
5256
- labelFor,
5257
- onRemove
5262
+ function SkeletonText({
5263
+ lines = 3,
5264
+ lastLineWidth = 60,
5265
+ lineHeight = 14,
5266
+ gap = 8,
5267
+ className = "",
5268
+ style
5258
5269
  }) {
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
- ]
5270
+ return /* @__PURE__ */ jsx(
5271
+ "div",
5272
+ {
5273
+ role: "presentation",
5274
+ "aria-hidden": "true",
5275
+ className: `flex flex-col ${className}`,
5276
+ style: { gap, ...style },
5277
+ children: Array.from({ length: lines }).map((_, i) => {
5278
+ const isLast = i === lines - 1;
5279
+ const width = isLast && lines > 1 ? `${lastLineWidth}%` : "100%";
5280
+ return /* @__PURE__ */ jsx(
5281
+ "span",
5282
+ {
5283
+ className: `block ${SHIMMER}`,
5284
+ style: { height: lineHeight, width, borderRadius: "var(--radius-sm)" }
5285
+ },
5286
+ i
5287
+ );
5288
+ })
5289
+ }
5290
+ );
5291
+ }
5292
+ function SkeletonCircle({ size = 40, className = "", style }) {
5293
+ return /* @__PURE__ */ jsx(
5294
+ "span",
5295
+ {
5296
+ role: "presentation",
5297
+ "aria-hidden": "true",
5298
+ className: `block flex-shrink-0 ${SHIMMER} ${className}`,
5299
+ style: {
5300
+ width: size,
5301
+ height: size,
5302
+ borderRadius: "50%",
5303
+ ...style
5322
5304
  }
5323
- ),
5324
- values.slice(0, visibleCount).map((val) => /* @__PURE__ */ jsx(
5325
- Tag,
5305
+ }
5306
+ );
5307
+ }
5308
+ function SkeletonCard({ hasAvatar = true, lines = 3, className = "", style }) {
5309
+ return /* @__PURE__ */ jsxs(
5310
+ "div",
5311
+ {
5312
+ role: "presentation",
5313
+ "aria-hidden": "true",
5314
+ className: `rounded-lg border border-border bg-surface p-4 ${className}`,
5315
+ style,
5316
+ children: [
5317
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 mb-4", children: [
5318
+ hasAvatar && /* @__PURE__ */ jsx(SkeletonCircle, { size: 36 }),
5319
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 flex flex-col gap-2", children: [
5320
+ /* @__PURE__ */ jsx(SkeletonBox, { height: 12, width: "55%" }),
5321
+ /* @__PURE__ */ jsx(SkeletonBox, { height: 10, width: "35%" })
5322
+ ] })
5323
+ ] }),
5324
+ /* @__PURE__ */ jsx(SkeletonText, { lines, lastLineWidth: 55 }),
5325
+ /* @__PURE__ */ jsxs("div", { className: "mt-4 flex gap-2", children: [
5326
+ /* @__PURE__ */ jsx(SkeletonBox, { height: 28, width: 72 }),
5327
+ /* @__PURE__ */ jsx(SkeletonBox, { height: 28, width: 56 })
5328
+ ] })
5329
+ ]
5330
+ }
5331
+ );
5332
+ }
5333
+ var DEFAULT_PICKER = [
5334
+ { key: 1, value: 5, label: 5 },
5335
+ { key: 2, value: 10, label: 10 },
5336
+ { key: 3, value: 15, label: 15 },
5337
+ { key: 4, value: 20, label: 20 }
5338
+ ];
5339
+ var DEFAULT_PAGINATION = {
5340
+ enabled: true,
5341
+ perPage: 15,
5342
+ withPicker: true,
5343
+ pickerOptions: DEFAULT_PICKER
5344
+ };
5345
+ var DEFAULT_EXPAND = {
5346
+ enabled: false
5347
+ };
5348
+ function createDatasets(rows, perPage) {
5349
+ if (!perPage) return [rows.slice()];
5350
+ const all = [];
5351
+ for (let i = 0; i < rows.length; i += perPage) {
5352
+ all.push(rows.slice(i, i + perPage));
5353
+ }
5354
+ return all;
5355
+ }
5356
+ var defaultGetRowKey = (_row, index) => index;
5357
+ var cellAlign = (align) => align === "left" ? "text-left" : align === "right" ? "text-right" : "text-center";
5358
+ function TableHeader({
5359
+ columns,
5360
+ hasExpand
5361
+ }) {
5362
+ return /* @__PURE__ */ jsx("thead", { className: "bg-surface-raised border-b border-border", children: /* @__PURE__ */ jsxs("tr", { children: [
5363
+ hasExpand && /* @__PURE__ */ jsx("th", { "aria-hidden": "true", className: "w-9" }),
5364
+ columns.map((col) => /* @__PURE__ */ jsx(
5365
+ "th",
5326
5366
  {
5327
- disabled,
5328
- removeLabel: `Remove ${labelFor(val)}`,
5329
- onRemove: () => onRemove(val),
5330
- children: labelFor(val)
5367
+ scope: "col",
5368
+ className: `${cellAlign(col.align)} text-sm font-semibold text-foreground py-3 px-3`,
5369
+ style: col.width != null ? { width: col.width } : void 0,
5370
+ children: col.label
5331
5371
  },
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
- var SHIMMER = "oxy-skeleton rounded-sm bg-surface-raised";
5537
- function SkeletonBox({ width, height = 16, radius, className = "", style }) {
5538
- return /* @__PURE__ */ jsx(
5539
- "span",
5540
- {
5541
- role: "presentation",
5542
- "aria-hidden": "true",
5543
- className: `block ${SHIMMER} ${className}`,
5544
- style: {
5545
- width: width ?? "100%",
5546
- height,
5547
- borderRadius: radius ?? "var(--radius-md)",
5548
- ...style
5549
- }
5550
- }
5551
- );
5552
- }
5553
- function SkeletonText({
5554
- lines = 3,
5555
- lastLineWidth = 60,
5556
- lineHeight = 14,
5557
- gap = 8,
5558
- className = "",
5559
- style
5560
- }) {
5561
- return /* @__PURE__ */ jsx(
5562
- "div",
5563
- {
5564
- role: "presentation",
5565
- "aria-hidden": "true",
5566
- className: `flex flex-col ${className}`,
5567
- style: { gap, ...style },
5568
- children: Array.from({ length: lines }).map((_, i) => {
5569
- const isLast = i === lines - 1;
5570
- const width = isLast && lines > 1 ? `${lastLineWidth}%` : "100%";
5571
- return /* @__PURE__ */ jsx(
5572
- "span",
5573
- {
5574
- className: `block ${SHIMMER}`,
5575
- style: { height: lineHeight, width, borderRadius: "var(--radius-sm)" }
5576
- },
5577
- i
5578
- );
5579
- })
5580
- }
5581
- );
5582
- }
5583
- function SkeletonCircle({ size = 40, className = "", style }) {
5584
- return /* @__PURE__ */ jsx(
5585
- "span",
5586
- {
5587
- role: "presentation",
5588
- "aria-hidden": "true",
5589
- className: `block flex-shrink-0 ${SHIMMER} ${className}`,
5590
- style: {
5591
- width: size,
5592
- height: size,
5593
- borderRadius: "50%",
5594
- ...style
5595
- }
5596
- }
5597
- );
5598
- }
5599
- function SkeletonCard({ hasAvatar = true, lines = 3, className = "", style }) {
5600
- return /* @__PURE__ */ jsxs(
5601
- "div",
5602
- {
5603
- role: "presentation",
5604
- "aria-hidden": "true",
5605
- className: `rounded-lg border border-border bg-surface p-4 ${className}`,
5606
- style,
5607
- children: [
5608
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 mb-4", children: [
5609
- hasAvatar && /* @__PURE__ */ jsx(SkeletonCircle, { size: 36 }),
5610
- /* @__PURE__ */ jsxs("div", { className: "flex-1 flex flex-col gap-2", children: [
5611
- /* @__PURE__ */ jsx(SkeletonBox, { height: 12, width: "55%" }),
5612
- /* @__PURE__ */ jsx(SkeletonBox, { height: 10, width: "35%" })
5613
- ] })
5614
- ] }),
5615
- /* @__PURE__ */ jsx(SkeletonText, { lines, lastLineWidth: 55 }),
5616
- /* @__PURE__ */ jsxs("div", { className: "mt-4 flex gap-2", children: [
5617
- /* @__PURE__ */ jsx(SkeletonBox, { height: 28, width: 72 }),
5618
- /* @__PURE__ */ jsx(SkeletonBox, { height: 28, width: 56 })
5619
- ] })
5620
- ]
5621
- }
5622
- );
5623
- }
5624
- var DEFAULT_PICKER = [
5625
- { key: 1, value: 5, label: 5 },
5626
- { key: 2, value: 10, label: 10 },
5627
- { key: 3, value: 15, label: 15 },
5628
- { key: 4, value: 20, label: 20 }
5629
- ];
5630
- var DEFAULT_PAGINATION = {
5631
- enabled: true,
5632
- perPage: 15,
5633
- withPicker: true,
5634
- pickerOptions: DEFAULT_PICKER
5635
- };
5636
- var DEFAULT_EXPAND = {
5637
- enabled: false
5638
- };
5639
- function createDatasets(rows, perPage) {
5640
- if (!perPage) return [rows.slice()];
5641
- const all = [];
5642
- for (let i = 0; i < rows.length; i += perPage) {
5643
- all.push(rows.slice(i, i + perPage));
5644
- }
5645
- return all;
5646
- }
5647
- var defaultGetRowKey = (_row, index) => index;
5648
- var cellAlign = (align) => align === "left" ? "text-left" : align === "right" ? "text-right" : "text-center";
5649
- function TableHeader({
5650
- columns,
5651
- hasExpand
5652
- }) {
5653
- return /* @__PURE__ */ jsx("thead", { className: "bg-surface-raised border-b border-border", children: /* @__PURE__ */ jsxs("tr", { children: [
5654
- hasExpand && /* @__PURE__ */ jsx("th", { "aria-hidden": "true", className: "w-9" }),
5655
- columns.map((col) => /* @__PURE__ */ jsx(
5656
- "th",
5657
- {
5658
- scope: "col",
5659
- className: `${cellAlign(col.align)} text-sm font-semibold text-foreground py-3 px-3`,
5660
- style: col.width != null ? { width: col.width } : void 0,
5661
- children: col.label
5662
- },
5663
- col.key
5664
- ))
5665
- ] }) });
5372
+ col.key
5373
+ ))
5374
+ ] }) });
5666
5375
  }
5667
5376
  var DefaultExpandIcon = /* @__PURE__ */ jsx(
5668
5377
  "svg",
@@ -5703,7 +5412,7 @@ function TableBody({
5703
5412
  return /* @__PURE__ */ jsx("tbody", { children: rows.map((row, i) => {
5704
5413
  const rowKey = getRowKey(row, i);
5705
5414
  const isExpanded = expanded.has(rowKey);
5706
- return /* @__PURE__ */ jsxs(React29.Fragment, { children: [
5415
+ return /* @__PURE__ */ jsxs(React28.Fragment, { children: [
5707
5416
  /* @__PURE__ */ jsxs(
5708
5417
  "tr",
5709
5418
  {
@@ -5767,28 +5476,29 @@ function Pagination({
5767
5476
  if (next) setPerPageKey(next.key);
5768
5477
  }
5769
5478
  }, [serverSide, options.perPage, picker]);
5770
- const navBtn = (icon, disabled, onClick, title) => /* @__PURE__ */ jsx(IconButton, { type: "bordered", size: "sm", disabled, onClick, icon, title });
5479
+ const currentOpt = picker.find((o) => o.key === displayPerPageKey);
5480
+ const currentPerPageLabel = currentOpt?.label ?? currentOpt?.value ?? options.perPage ?? "";
5481
+ const navBtn = (icon, disabled, onClick, title) => /* @__PURE__ */ jsx(Button_default, { variant: "outline", size: "sm", disabled, onClick, icon, className: "w-7 !px-0", "aria-label": title, title });
5771
5482
  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
5483
  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" }) });
5773
5484
  return /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-end gap-x-4 gap-y-3 pt-3", children: [
5774
5485
  options.withPicker && /* @__PURE__ */ jsxs("div", { className: "mr-auto flex items-center gap-2", children: [
5775
5486
  /* @__PURE__ */ jsx("span", { className: "whitespace-nowrap text-xs text-foreground-muted", children: "Rows per page" }),
5776
5487
  /* @__PURE__ */ jsx(
5777
- Dropdown,
5488
+ MenuButton,
5778
5489
  {
5490
+ variant: "outline",
5779
5491
  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
- }
5492
+ side: "top",
5493
+ label: String(currentPerPageLabel),
5494
+ items: picker.map((o) => ({
5495
+ key: o.key,
5496
+ label: String(o.label ?? o.value ?? o.key),
5497
+ onSelect: () => {
5498
+ if (!serverSide) setPerPageKey(o.key);
5499
+ onPerPageChange(o.label ?? o.value ?? o.key);
5500
+ }
5501
+ }))
5792
5502
  }
5793
5503
  )
5794
5504
  ] }),
@@ -6244,8 +5954,8 @@ function MegaMenuLink({ href, icon, description, active, onClick, children, clas
6244
5954
  function MegaMenuFeatured({ children, className = "" }) {
6245
5955
  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
5956
  }
6247
- var elementsOfType = (children, type) => React29.Children.toArray(children).filter(
6248
- (c) => React29.isValidElement(c) && c.type === type
5957
+ var elementsOfType = (children, type) => React28.Children.toArray(children).filter(
5958
+ (c) => React28.isValidElement(c) && c.type === type
6249
5959
  );
6250
5960
  var MOBILE_CHEVRON = /* @__PURE__ */ jsx(
6251
5961
  "svg",
@@ -6282,9 +5992,9 @@ function MobileLinkRow({ link, onNavigate }) {
6282
5992
  );
6283
5993
  }
6284
5994
  function MobilePanel({ panel, onNavigate }) {
6285
- const nodes = React29.Children.toArray(panel.props.children);
5995
+ const nodes = React28.Children.toArray(panel.props.children);
6286
5996
  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;
5997
+ if (!React28.isValidElement(node)) return null;
6288
5998
  const el = node;
6289
5999
  if (el.type === MegaMenuSection) {
6290
6000
  const { title, children } = el.props;
@@ -6693,7 +6403,7 @@ function ThemeProvider({
6693
6403
  className = "",
6694
6404
  style
6695
6405
  }) {
6696
- const id = React29.useId().replace(/:/g, "");
6406
+ const id = React28.useId().replace(/:/g, "");
6697
6407
  const scopeClass = `geo-th-${id}`;
6698
6408
  const divRef = useRef(null);
6699
6409
  useEffect(() => {
@@ -7151,77 +6861,379 @@ function RadioGroup({
7151
6861
  }
7152
6862
  );
7153
6863
  }
7154
- function Switch({
7155
- checked,
7156
- defaultChecked = false,
7157
- onChange,
7158
- checkedIcon,
7159
- uncheckedIcon,
6864
+ function Switch({
6865
+ checked,
6866
+ defaultChecked = false,
6867
+ onChange,
6868
+ checkedIcon,
6869
+ uncheckedIcon,
6870
+ label,
6871
+ layout = "horizontal",
6872
+ helperText,
6873
+ className,
6874
+ offLabel,
6875
+ onLabel,
6876
+ name,
6877
+ required,
6878
+ disabled,
6879
+ errorMessage
6880
+ }) {
6881
+ const id = useId();
6882
+ const errorId = useId();
6883
+ const hasError = errorMessage != null;
6884
+ const isControlled = checked !== void 0;
6885
+ const [internal, setInternal] = useState(defaultChecked);
6886
+ const isOn = isControlled ? checked : internal;
6887
+ const handle = (c) => {
6888
+ if (!isControlled) setInternal(c);
6889
+ onChange?.({ target: { checked: c, name } });
6890
+ };
6891
+ const stateLabel = (active) => [
6892
+ "text-sm select-none transition-colors",
6893
+ active ? "text-foreground font-medium" : "text-foreground-muted",
6894
+ disabled ? "opacity-50" : "cursor-pointer"
6895
+ ].filter(Boolean).join(" ");
6896
+ return /* @__PURE__ */ jsx(
6897
+ Field,
6898
+ {
6899
+ className,
6900
+ label,
6901
+ htmlFor: id,
6902
+ errorId,
6903
+ errorMessage,
6904
+ layout,
6905
+ required,
6906
+ helperText,
6907
+ labelAlign: "center",
6908
+ children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2.5", children: [
6909
+ offLabel != null && /* @__PURE__ */ jsx("label", { htmlFor: id, className: stateLabel(!isOn), children: offLabel }),
6910
+ /* @__PURE__ */ jsx(
6911
+ SwitchPrimitive.Root,
6912
+ {
6913
+ id,
6914
+ name,
6915
+ checked: isOn,
6916
+ onCheckedChange: handle,
6917
+ disabled,
6918
+ required,
6919
+ "aria-invalid": hasError || void 0,
6920
+ "aria-describedby": hasError ? errorId : void 0,
6921
+ 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",
6922
+ children: /* @__PURE__ */ jsx(
6923
+ SwitchPrimitive.Thumb,
6924
+ {
6925
+ 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]",
6926
+ children: checkedIcon && uncheckedIcon ? /* @__PURE__ */ jsx("span", { className: "flex items-center justify-center w-3 h-3", children: isOn ? checkedIcon : uncheckedIcon }) : null
6927
+ }
6928
+ )
6929
+ }
6930
+ ),
6931
+ onLabel != null && /* @__PURE__ */ jsx("label", { htmlFor: id, className: stateLabel(isOn), children: onLabel })
6932
+ ] })
6933
+ }
6934
+ );
6935
+ }
6936
+ function Tag({ children, onRemove, removeLabel, disabled }) {
6937
+ 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: [
6938
+ /* @__PURE__ */ jsx("span", { className: "truncate", children }),
6939
+ onRemove && /* @__PURE__ */ jsx(
6940
+ "button",
6941
+ {
6942
+ type: "button",
6943
+ disabled,
6944
+ onClick: (e) => {
6945
+ e.stopPropagation();
6946
+ onRemove();
6947
+ },
6948
+ "aria-label": removeLabel ?? "Remove",
6949
+ 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",
6950
+ 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" }) })
6951
+ }
6952
+ )
6953
+ ] });
6954
+ }
6955
+ function MultiTagRow({
6956
+ values,
6957
+ disabled,
6958
+ labelFor,
6959
+ onRemove
6960
+ }) {
6961
+ const wrapRef = useRef(null);
6962
+ const measureRef = useRef(null);
6963
+ const [visibleCount, setVisibleCount] = useState(values.length);
6964
+ const key = values.map(String).join("|");
6965
+ useLayoutEffect(() => {
6966
+ const wrap = wrapRef.current;
6967
+ const measure = measureRef.current;
6968
+ if (!wrap || !measure) return;
6969
+ const GAP = 6;
6970
+ const recompute = () => {
6971
+ const avail = wrap.clientWidth;
6972
+ const tagEls = Array.from(measure.querySelectorAll("[data-mt]"));
6973
+ const moreEl = measure.querySelector("[data-mm]");
6974
+ const widths = tagEls.map((e) => e.offsetWidth);
6975
+ const moreW = moreEl ? moreEl.offsetWidth : 0;
6976
+ if (widths.length === 0) {
6977
+ setVisibleCount(0);
6978
+ return;
6979
+ }
6980
+ let used = 0;
6981
+ let count = 0;
6982
+ for (let i = 0; i < widths.length; i++) {
6983
+ const w = widths[i] + (i > 0 ? GAP : 0);
6984
+ if (used + w <= avail) {
6985
+ used += w;
6986
+ count++;
6987
+ } else break;
6988
+ }
6989
+ if (count < widths.length) {
6990
+ while (count > 0) {
6991
+ let t = 0;
6992
+ for (let i = 0; i < count; i++) t += widths[i] + (i > 0 ? GAP : 0);
6993
+ t += GAP + moreW;
6994
+ if (t <= avail) break;
6995
+ count--;
6996
+ }
6997
+ }
6998
+ setVisibleCount(count);
6999
+ };
7000
+ recompute();
7001
+ const ro = new ResizeObserver(recompute);
7002
+ ro.observe(wrap);
7003
+ return () => ro.disconnect();
7004
+ }, [key]);
7005
+ const hidden = values.length - visibleCount;
7006
+ 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: [
7007
+ "+",
7008
+ n,
7009
+ " more"
7010
+ ] });
7011
+ return /* @__PURE__ */ jsxs("div", { ref: wrapRef, className: "relative flex-1 min-w-0 flex flex-nowrap items-center gap-1.5 overflow-hidden", children: [
7012
+ /* @__PURE__ */ jsxs(
7013
+ "div",
7014
+ {
7015
+ ref: measureRef,
7016
+ "aria-hidden": "true",
7017
+ className: "absolute invisible pointer-events-none flex flex-nowrap items-center gap-1.5",
7018
+ style: { left: -9999, top: -9999 },
7019
+ children: [
7020
+ values.map((val) => /* @__PURE__ */ jsx("span", { "data-mt": true, children: /* @__PURE__ */ jsx(Tag, { removeLabel: "x", onRemove: () => {
7021
+ }, children: labelFor(val) }) }, `m-${val}`)),
7022
+ /* @__PURE__ */ jsx("span", { "data-mm": true, children: moreChip(values.length) })
7023
+ ]
7024
+ }
7025
+ ),
7026
+ values.slice(0, visibleCount).map((val) => /* @__PURE__ */ jsx(
7027
+ Tag,
7028
+ {
7029
+ disabled,
7030
+ removeLabel: `Remove ${labelFor(val)}`,
7031
+ onRemove: () => onRemove(val),
7032
+ children: labelFor(val)
7033
+ },
7034
+ String(val)
7035
+ )),
7036
+ hidden > 0 && moreChip(hidden)
7037
+ ] });
7038
+ }
7039
+ function Dropdown({
7040
+ isMultiselect = false,
7041
+ hasSearch = true,
7160
7042
  label,
7043
+ name,
7044
+ value,
7045
+ onChange,
7046
+ disabled,
7161
7047
  layout = "horizontal",
7162
7048
  helperText,
7163
- className,
7164
- offLabel,
7165
- onLabel,
7166
- name,
7167
7049
  required,
7168
- disabled,
7169
- errorMessage
7050
+ errorMessage,
7051
+ style = {},
7052
+ htmlFor,
7053
+ items = [],
7054
+ labelStyle = {},
7055
+ placeholder,
7056
+ size = "md",
7057
+ className = ""
7170
7058
  }) {
7171
- const id = useId();
7059
+ const [open, setOpen] = useState(false);
7060
+ const [selectedItems, setSelectedItems] = useState([]);
7061
+ const [searchTerm, setSearchTerm] = useState("");
7062
+ const [innerItems, setInnerItems] = useState([]);
7172
7063
  const errorId = useId();
7173
7064
  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 } });
7065
+ useEffect(() => {
7066
+ setInnerItems(items);
7067
+ }, [items]);
7068
+ useEffect(() => {
7069
+ if (isMultiselect && Array.isArray(value)) {
7070
+ setSelectedItems(value);
7071
+ }
7072
+ }, [isMultiselect, value]);
7073
+ const selectItem = (key) => {
7074
+ if (isMultiselect) {
7075
+ const next = selectedItems.includes(key) ? selectedItems.filter((it) => it !== key) : [...selectedItems, key];
7076
+ setSelectedItems(next);
7077
+ onChange?.({ target: { value: next, id: htmlFor, name } });
7078
+ } else {
7079
+ setSelectedItems([key]);
7080
+ onChange?.({ target: { value: key, id: htmlFor, name } });
7081
+ setOpen(false);
7082
+ }
7180
7083
  };
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,
7084
+ const removeSelected = (key) => {
7085
+ if (isMultiselect) {
7086
+ const next = selectedItems.filter((it) => it !== key);
7087
+ setSelectedItems(next);
7088
+ onChange?.({ target: { value: next, id: htmlFor, name } });
7089
+ } else {
7090
+ setSelectedItems([]);
7091
+ onChange?.({ target: { value: "", id: htmlFor, name } });
7092
+ }
7093
+ };
7094
+ const labelFor = (key) => innerItems.find((it) => it.key === key)?.label ?? String(key);
7095
+ const onSearchChange = (e) => {
7096
+ const term = e.target.value;
7097
+ setSearchTerm(term);
7098
+ setInnerItems(
7099
+ term.trim() === "" ? items : items.filter(
7100
+ (it) => String(it.label).toLowerCase().includes(term.toLowerCase())
7101
+ )
7102
+ );
7103
+ };
7104
+ const isSelected = (key) => Array.isArray(value) ? value.includes(key) : value === key;
7105
+ return /* @__PURE__ */ jsxs("div", { className: className || void 0, children: [
7106
+ /* @__PURE__ */ jsxs(
7107
+ "div",
7108
+ {
7109
+ className: `flex ${layout === "vertical" ? "flex-col gap-1.5" : "flex-row items-start gap-3"}`,
7110
+ children: [
7111
+ /* @__PURE__ */ jsx(
7112
+ FieldLabel,
7113
+ {
7114
+ label,
7115
+ htmlFor,
7116
+ required,
7117
+ helperText,
7118
+ horizontal: layout === "horizontal",
7119
+ style: labelStyle
7120
+ }
7121
+ ),
7122
+ /* @__PURE__ */ jsxs(Popover.Root, { open: open && !disabled, onOpenChange: (o) => !disabled && setOpen(o), children: [
7123
+ /* @__PURE__ */ jsx(Popover.Trigger, { asChild: true, children: /* @__PURE__ */ jsxs(
7124
+ "div",
7214
7125
  {
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
7126
+ id: htmlFor,
7127
+ role: "combobox",
7128
+ "aria-expanded": open,
7129
+ "aria-haspopup": "listbox",
7130
+ "aria-invalid": hasError || void 0,
7131
+ "aria-describedby": hasError ? errorId : void 0,
7132
+ style: { width: 240, ...style },
7133
+ 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 })}`,
7134
+ tabIndex: disabled ? -1 : 0,
7135
+ onKeyDown: (e) => {
7136
+ if (disabled) return;
7137
+ if (e.key === "Enter" || e.key === " " || e.key === "ArrowDown" || e.key === "ArrowUp") {
7138
+ e.preventDefault();
7139
+ setOpen(true);
7140
+ }
7141
+ },
7142
+ children: [
7143
+ !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(
7144
+ MultiTagRow,
7145
+ {
7146
+ values: value,
7147
+ disabled,
7148
+ labelFor,
7149
+ onRemove: removeSelected
7150
+ }
7151
+ ) : /* @__PURE__ */ jsx("div", { className: "flex-1 min-w-0 flex items-center overflow-hidden", children: /* @__PURE__ */ jsx(
7152
+ Tag,
7153
+ {
7154
+ disabled,
7155
+ removeLabel: `Remove ${labelFor(value)}`,
7156
+ onRemove: () => removeSelected(value),
7157
+ children: labelFor(value)
7158
+ }
7159
+ ) }),
7160
+ /* @__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" }) }) })
7161
+ ]
7217
7162
  }
7218
- )
7219
- }
7220
- ),
7221
- onLabel != null && /* @__PURE__ */ jsx("label", { htmlFor: id, className: stateLabel(isOn), children: onLabel })
7222
- ] })
7223
- }
7224
- );
7163
+ ) }),
7164
+ /* @__PURE__ */ jsx(Popover.Portal, { children: /* @__PURE__ */ jsxs(
7165
+ Popover.Content,
7166
+ {
7167
+ align: "start",
7168
+ sideOffset: 4,
7169
+ style: { width: style?.width || 240 },
7170
+ 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",
7171
+ onInteractOutside: () => setOpen(false),
7172
+ children: [
7173
+ hasSearch && /* @__PURE__ */ jsx("div", { className: "mb-2", children: /* @__PURE__ */ jsx(
7174
+ SearchInput_default,
7175
+ {
7176
+ style: { width: "100%" },
7177
+ inputStyle: { width: "100%" },
7178
+ value: searchTerm,
7179
+ onChange: onSearchChange,
7180
+ placeholder: "Search..."
7181
+ }
7182
+ ) }),
7183
+ /* @__PURE__ */ jsx("div", { role: "listbox", "aria-multiselectable": isMultiselect, className: "max-h-40 overflow-y-auto", children: innerItems.map((item) => (
7184
+ // aria-rowindex was previously set here but
7185
+ // it's invalid ARIA on role="option" (it
7186
+ // belongs on rows of a grid/treegrid). Dropped.
7187
+ // tabIndex={0} + Enter/Space handler makes the
7188
+ // option keyboard-activatable; the full
7189
+ // combobox roving-tabindex pattern is deferred
7190
+ // until the planned Phase-5 rewrite.
7191
+ /* @__PURE__ */ jsxs(
7192
+ "div",
7193
+ {
7194
+ role: "option",
7195
+ "aria-selected": isSelected(item.key),
7196
+ tabIndex: 0,
7197
+ 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"}`,
7198
+ onClick: () => selectItem(item.key),
7199
+ onKeyDown: (e) => {
7200
+ if (e.key === "Enter" || e.key === " ") {
7201
+ e.preventDefault();
7202
+ selectItem(item.key);
7203
+ }
7204
+ },
7205
+ children: [
7206
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-xs", children: [
7207
+ item.icon && /* @__PURE__ */ jsx("div", { children: item.icon }),
7208
+ item.label
7209
+ ] }),
7210
+ isSelected(item.key) && // currentColor — checkmark follows
7211
+ // the item's text colour, which
7212
+ // flips automatically on hover.
7213
+ /* @__PURE__ */ jsx("svg", { width: "16", height: "16", viewBox: "0 0 20 20", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx(
7214
+ "path",
7215
+ {
7216
+ d: "M4 10l4.5 4.5L16 6",
7217
+ stroke: "currentColor",
7218
+ strokeWidth: "2",
7219
+ strokeLinecap: "round",
7220
+ strokeLinejoin: "round"
7221
+ }
7222
+ ) })
7223
+ ]
7224
+ },
7225
+ item.key
7226
+ )
7227
+ )) })
7228
+ ]
7229
+ }
7230
+ ) })
7231
+ ] })
7232
+ ]
7233
+ }
7234
+ ),
7235
+ hasError && /* @__PURE__ */ jsx("div", { id: errorId, className: "text-status-error text-xs mt-1", children: errorMessage })
7236
+ ] });
7225
7237
  }
7226
7238
  function AutoComplete({
7227
7239
  disabled,
@@ -8533,7 +8545,7 @@ function OtpInput({
8533
8545
  emit(valid.join(""));
8534
8546
  focusBox(valid.length);
8535
8547
  };
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: [
8548
+ 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
8549
  /* @__PURE__ */ jsx(
8538
8550
  "input",
8539
8551
  {