@geomak/ui 7.9.0 → 7.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@ import { X, Error as Error$1, Warning, CheckCircle, Info } from './chunk-KAFJJO5
2
2
  export { icons_default as Icon, createIcon } from './chunk-KAFJJO5O.js';
3
3
  import { colors_default } from './chunk-DXOWXLKK.js';
4
4
  export { colors_default as COLORS, PALETTE as palette, semanticTokens, vars } from './chunk-DXOWXLKK.js';
5
- import React34, { useMemo, createContext, useState, useEffect, useId, useCallback, useRef, useContext, useSyncExternalStore, useLayoutEffect } from 'react';
5
+ import React36, { useMemo, createContext, useState, useEffect, useId, useCallback, useRef, useContext, useSyncExternalStore, useLayoutEffect } from 'react';
6
6
  import { createPortal } from 'react-dom';
7
7
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
8
8
  import * as AvatarPrimitive from '@radix-ui/react-avatar';
@@ -453,7 +453,7 @@ function Typography({
453
453
  }
454
454
  );
455
455
  }
456
- var IconButton = React34.forwardRef(function IconButton2({
456
+ var IconButton = React36.forwardRef(function IconButton2({
457
457
  icon,
458
458
  onClick,
459
459
  type = "primary",
@@ -562,7 +562,7 @@ var SIZE_CLASSES = {
562
562
  md: "h-9 px-4 text-sm gap-1.5 rounded-lg",
563
563
  lg: "h-11 px-5 text-sm gap-2 rounded-xl"
564
564
  };
565
- var Button = React34.forwardRef(function Button2({
565
+ var Button = React36.forwardRef(function Button2({
566
566
  content,
567
567
  variant = "primary",
568
568
  size = "md",
@@ -670,7 +670,7 @@ function MenuButton({
670
670
  "data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
671
671
  "data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95"
672
672
  ].join(" "),
673
- children: items.map((item) => /* @__PURE__ */ jsxs(React34.Fragment, { children: [
673
+ children: items.map((item) => /* @__PURE__ */ jsxs(React36.Fragment, { children: [
674
674
  item.separatorBefore && /* @__PURE__ */ jsx(DropdownMenu2.Separator, { className: "my-1 h-px bg-border" }),
675
675
  /* @__PURE__ */ jsxs(
676
676
  DropdownMenu2.Item,
@@ -693,6 +693,10 @@ function MenuButton({
693
693
  ) })
694
694
  ] });
695
695
  }
696
+ var DialogContainerContext = createContext(null);
697
+ function useDialogContainer() {
698
+ return useContext(DialogContainerContext);
699
+ }
696
700
  var SIZE_MAP = {
697
701
  sm: 400,
698
702
  md: 600,
@@ -717,6 +721,7 @@ function Modal({
717
721
  className = ""
718
722
  }) {
719
723
  const reduced = useReducedMotion();
724
+ const [panelEl, setPanelEl] = useState(null);
720
725
  const maxWidth = width ?? SIZE_MAP[size];
721
726
  return /* @__PURE__ */ jsx(Dialog.Root, { open, onOpenChange: (next) => {
722
727
  if (!next) onClose?.();
@@ -734,6 +739,7 @@ function Modal({
734
739
  /* @__PURE__ */ jsx(AnimatePresence, { children: open && /* @__PURE__ */ jsx(Dialog.Content, { asChild: true, children: /* @__PURE__ */ jsxs(
735
740
  motion.div,
736
741
  {
742
+ ref: setPanelEl,
737
743
  className: `fixed left-1/2 top-1/2 z-modal flex flex-col w-[calc(100%-2rem)] max-h-[90dvh] bg-surface rounded-2xl shadow-xl overflow-hidden focus:outline-none ${className}`.trim(),
738
744
  style: {
739
745
  maxWidth,
@@ -761,7 +767,7 @@ function Modal({
761
767
  }
762
768
  ) })
763
769
  ] }),
764
- /* @__PURE__ */ jsx("div", { className: `flex-1 overflow-y-auto p-5 ${hasFooter ? "" : "pb-5"}`, children }),
770
+ /* @__PURE__ */ jsx("div", { className: `flex-1 overflow-y-auto p-5 ${hasFooter ? "" : "pb-5"}`, children: /* @__PURE__ */ jsx(DialogContainerContext.Provider, { value: panelEl, children }) }),
765
771
  hasFooter && /* @__PURE__ */ jsxs("div", { className: "flex flex-shrink-0 items-center justify-end gap-3 border-t border-border px-5 py-3", children: [
766
772
  /* @__PURE__ */ jsx(
767
773
  Button_default,
@@ -812,6 +818,7 @@ function Drawer({
812
818
  className = ""
813
819
  }) {
814
820
  const reduced = useReducedMotion();
821
+ const [panelEl, setPanelEl] = useState(null);
815
822
  const isRight = placement === "right";
816
823
  const hiddenX = isRight ? "100%" : "-100%";
817
824
  const resolvedWidth = width ?? SIZE_MAP2[size];
@@ -832,6 +839,7 @@ function Drawer({
832
839
  /* @__PURE__ */ jsx(AnimatePresence, { children: open && /* @__PURE__ */ jsx(Dialog.Content, { asChild: true, children: /* @__PURE__ */ jsxs(
833
840
  motion.div,
834
841
  {
842
+ ref: setPanelEl,
835
843
  className: `fixed top-0 bottom-0 ${isRight ? "right-0" : "left-0"} z-modal flex flex-col bg-surface shadow-xl focus:outline-none ${className}`.trim(),
836
844
  style: { width: `min(calc(100vw - 1rem), ${widthCss})` },
837
845
  initial: { x: reduced ? 0 : hiddenX, opacity: reduced ? 0 : 1 },
@@ -858,7 +866,7 @@ function Drawer({
858
866
  }
859
867
  ) })
860
868
  ] }),
861
- /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-y-auto p-5", children }),
869
+ /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-y-auto p-5", children: /* @__PURE__ */ jsx(DialogContainerContext.Provider, { value: panelEl, children }) }),
862
870
  hasFooter && /* @__PURE__ */ jsxs("div", { className: `flex flex-shrink-0 items-center gap-3 border-t border-border px-5 py-3 ${isRight ? "justify-start" : "justify-end"}`, children: [
863
871
  /* @__PURE__ */ jsx(Button_default, { style: { width: 90 }, variant: cancelVariant, content: cancelText, onClick: onCancel }),
864
872
  /* @__PURE__ */ jsx(Button_default, { style: { width: 90 }, variant: okVariant, content: okText, onClick: onOk })
@@ -1817,7 +1825,7 @@ function Kbd({
1817
1825
  style
1818
1826
  }) {
1819
1827
  if (keys && keys.length > 0) {
1820
- return /* @__PURE__ */ jsx("span", { className: cx("inline-flex items-center gap-1", className), style, children: keys.map((k, i) => /* @__PURE__ */ jsxs(React34.Fragment, { children: [
1828
+ return /* @__PURE__ */ jsx("span", { className: cx("inline-flex items-center gap-1", className), style, children: keys.map((k, i) => /* @__PURE__ */ jsxs(React36.Fragment, { children: [
1821
1829
  i > 0 && /* @__PURE__ */ jsx("span", { className: "text-foreground-muted text-xs select-none", children: separator }),
1822
1830
  /* @__PURE__ */ jsx("kbd", { className: [cap, SIZE3[size]].join(" "), children: k })
1823
1831
  ] }, `${k}-${i}`)) });
@@ -1909,7 +1917,7 @@ function FlatCarousel({
1909
1917
  style
1910
1918
  }) {
1911
1919
  const scrollerRef = useRef(null);
1912
- const slides = React34.Children.toArray(children);
1920
+ const slides = React36.Children.toArray(children);
1913
1921
  const [active, setActive] = useState(0);
1914
1922
  const [atStart, setAtStart] = useState(true);
1915
1923
  const [atEnd, setAtEnd] = useState(false);
@@ -1964,7 +1972,7 @@ function RotatingCarousel({
1964
1972
  className = "",
1965
1973
  style
1966
1974
  }) {
1967
- const slides = React34.Children.toArray(children);
1975
+ const slides = React36.Children.toArray(children);
1968
1976
  const count = slides.length;
1969
1977
  const [active, setActive] = useState(0);
1970
1978
  const reduced = useReducedMotion();
@@ -5245,7 +5253,7 @@ function Wizard({
5245
5253
  ] });
5246
5254
  }
5247
5255
  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" }) });
5248
- var SearchInput = React34.forwardRef(function SearchInput2({ value, onChange, disabled, label, htmlFor, placeholder, name, inputStyle, style, layout = "vertical", size = "md", icon, helperText, className }, ref) {
5256
+ var SearchInput = React36.forwardRef(function SearchInput2({ value, onChange, disabled, label, htmlFor, placeholder, name, inputStyle, style, layout = "vertical", size = "md", icon, helperText, className }, ref) {
5249
5257
  return /* @__PURE__ */ jsx(Field, { className, label, htmlFor, layout, helperText, children: /* @__PURE__ */ jsxs(
5250
5258
  "div",
5251
5259
  {
@@ -5562,7 +5570,7 @@ function TableBody({
5562
5570
  return /* @__PURE__ */ jsx("tbody", { children: rows.map((row, i) => {
5563
5571
  const rowKey = getRowKey(row, i);
5564
5572
  const isExpanded = expanded.has(rowKey);
5565
- return /* @__PURE__ */ jsxs(React34.Fragment, { children: [
5573
+ return /* @__PURE__ */ jsxs(React36.Fragment, { children: [
5566
5574
  /* @__PURE__ */ jsxs(
5567
5575
  "tr",
5568
5576
  {
@@ -6007,6 +6015,8 @@ function PdfViewer({
6007
6015
  onLoad,
6008
6016
  onError,
6009
6017
  onPageChange,
6018
+ height = 600,
6019
+ width,
6010
6020
  className = "",
6011
6021
  style
6012
6022
  }) {
@@ -6241,7 +6251,7 @@ function PdfViewer({
6241
6251
  ] });
6242
6252
  }
6243
6253
  const ready = status === "ready";
6244
- return /* @__PURE__ */ jsxs("div", { className: cx("flex flex-col overflow-hidden rounded-lg border border-border bg-surface-raised", className), style: { height: 600, ...style }, children: [
6254
+ return /* @__PURE__ */ jsxs("div", { className: cx("flex flex-col overflow-hidden rounded-lg border border-border bg-surface-raised", className), style: { height, width, ...style }, children: [
6245
6255
  toolbar !== false && /* @__PURE__ */ jsxs("div", { className: "flex flex-shrink-0 flex-col border-b border-border bg-surface", children: [
6246
6256
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 px-2 py-1.5", children: [
6247
6257
  tb.pager && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
@@ -6422,6 +6432,15 @@ var FitPageIcon = () => /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", fill
6422
6432
  ] });
6423
6433
  var DEFAULT_COL_WIDTH = 140;
6424
6434
  var GUTTER = 52;
6435
+ var END_PAD = 12;
6436
+ function compareValues2(a, b) {
6437
+ if (a == null && b == null) return 0;
6438
+ if (a == null) return 1;
6439
+ if (b == null) return -1;
6440
+ if (typeof a === "number" && typeof b === "number") return a - b;
6441
+ if (typeof a === "boolean" && typeof b === "boolean") return a === b ? 0 : a ? 1 : -1;
6442
+ return String(a).localeCompare(String(b), void 0, { numeric: true, sensitivity: "base" });
6443
+ }
6425
6444
  function resolveWidth(w) {
6426
6445
  if (typeof w === "number" && Number.isFinite(w)) return w;
6427
6446
  if (typeof w === "string") {
@@ -6440,10 +6459,15 @@ function DataGrid({
6440
6459
  rowHeight = 34,
6441
6460
  headerHeight = 38,
6442
6461
  height = 480,
6462
+ width,
6443
6463
  editable = false,
6464
+ sortable = false,
6465
+ sort: sortProp,
6466
+ onSortChange,
6444
6467
  virtualize = true,
6445
6468
  overscan = 4,
6446
6469
  rowNumbers = true,
6470
+ trailingRows = 0,
6447
6471
  onCellEdit,
6448
6472
  className = "",
6449
6473
  style,
@@ -6454,7 +6478,10 @@ function DataGrid({
6454
6478
  const [viewport, setViewport] = useState({ w: 0, h: 0 });
6455
6479
  const [editing, setEditing] = useState(null);
6456
6480
  const [draft, setDraft] = useState("");
6481
+ const [internalSort, setInternalSort] = useState(null);
6482
+ const sort = sortProp !== void 0 ? sortProp : internalSort;
6457
6483
  const gutter = rowNumbers ? GUTTER : 0;
6484
+ const colSortable = (c) => c.sortable ?? sortable;
6458
6485
  const { widths, offsets, totalWidth } = useMemo(() => {
6459
6486
  const widths2 = columns.map((c) => resolveWidth(c.width));
6460
6487
  const offsets2 = [];
@@ -6465,7 +6492,21 @@ function DataGrid({
6465
6492
  }
6466
6493
  return { widths: widths2, offsets: offsets2, totalWidth: acc };
6467
6494
  }, [columns]);
6468
- const totalHeight = rows.length * rowHeight;
6495
+ const order = useMemo(() => {
6496
+ const idx = rows.map((_, i) => i);
6497
+ if (!sort) return idx;
6498
+ const dir = sort.dir === "asc" ? 1 : -1;
6499
+ return idx.sort((ia, ib) => dir * compareValues2(rows[ia]?.[sort.key] ?? null, rows[ib]?.[sort.key] ?? null));
6500
+ }, [rows, sort]);
6501
+ const toggleSort = (key) => {
6502
+ const nextDir = sort?.key !== key ? "asc" : sort.dir === "asc" ? "desc" : null;
6503
+ const next = nextDir ? { key, dir: nextDir } : null;
6504
+ if (sortProp === void 0) setInternalSort(next);
6505
+ onSortChange?.(next);
6506
+ };
6507
+ const displayRowCount = rows.length + Math.max(0, trailingRows);
6508
+ const totalHeight = displayRowCount * rowHeight;
6509
+ const rowIndexForDisp = (disp) => disp < rows.length ? order[disp] : disp;
6469
6510
  useEffect(() => {
6470
6511
  const el = scrollRef.current;
6471
6512
  if (!el || typeof ResizeObserver === "undefined") return;
@@ -6477,7 +6518,7 @@ function DataGrid({
6477
6518
  }, []);
6478
6519
  const bodyH = (viewport.h || (typeof height === "number" ? height : 480)) - headerHeight;
6479
6520
  const rowStart = virtualize ? Math.max(0, Math.floor(scroll.top / rowHeight) - overscan) : 0;
6480
- const rowEnd = virtualize ? Math.min(rows.length, Math.ceil((scroll.top + bodyH) / rowHeight) + overscan) : rows.length;
6521
+ const rowEnd = virtualize ? Math.min(displayRowCount, Math.ceil((scroll.top + bodyH) / rowHeight) + overscan) : displayRowCount;
6481
6522
  let colStart = 0;
6482
6523
  let colEnd = columns.length;
6483
6524
  if (virtualize && viewport.w) {
@@ -6495,14 +6536,15 @@ function DataGrid({
6495
6536
  const commit = useCallback(() => {
6496
6537
  if (!editing) return;
6497
6538
  const col = columns[editing.col];
6498
- onCellEdit?.({ row: editing.row, column: col.key, value: draft });
6539
+ const ri = editing.disp < rows.length ? order[editing.disp] : editing.disp;
6540
+ onCellEdit?.({ row: ri, column: col.key, value: draft });
6499
6541
  setEditing(null);
6500
- }, [editing, columns, draft, onCellEdit]);
6501
- const startEdit = (row, col) => {
6542
+ }, [editing, columns, draft, onCellEdit, order, rows.length]);
6543
+ const startEdit = (disp, col) => {
6502
6544
  const c = columns[col];
6503
6545
  if (!(c.editable ?? editable)) return;
6504
- setDraft(displayValue(rows[row]?.[c.key] ?? ""));
6505
- setEditing({ row, col });
6546
+ setDraft(displayValue(rows[rowIndexForDisp(disp)]?.[c.key] ?? ""));
6547
+ setEditing({ disp, col });
6506
6548
  };
6507
6549
  return /* @__PURE__ */ jsxs(
6508
6550
  "div",
@@ -6510,12 +6552,12 @@ function DataGrid({
6510
6552
  ref: scrollRef,
6511
6553
  onScroll: (e) => setScroll({ top: e.currentTarget.scrollTop, left: e.currentTarget.scrollLeft }),
6512
6554
  className: cx("relative overflow-auto rounded-lg border border-border bg-surface-raised text-sm", className),
6513
- style: { height, ...style },
6555
+ style: { height, width, ...style },
6514
6556
  role: "grid",
6515
6557
  "aria-rowcount": rows.length,
6516
6558
  "aria-colcount": columns.length,
6517
6559
  children: [
6518
- /* @__PURE__ */ jsxs("div", { style: { position: "relative", width: gutter + totalWidth, height: headerHeight + totalHeight }, children: [
6560
+ /* @__PURE__ */ jsxs("div", { style: { position: "relative", width: gutter + totalWidth + END_PAD, height: headerHeight + totalHeight + END_PAD }, children: [
6519
6561
  rowNumbers && /* @__PURE__ */ jsx(
6520
6562
  "div",
6521
6563
  {
@@ -6525,11 +6567,18 @@ function DataGrid({
6525
6567
  ),
6526
6568
  visibleCols.map((ci) => {
6527
6569
  const c = columns[ci];
6528
- return /* @__PURE__ */ jsx(
6570
+ const sortDir = sort?.key === c.key ? sort.dir : null;
6571
+ const canSort = colSortable(c);
6572
+ return /* @__PURE__ */ jsxs(
6529
6573
  "div",
6530
6574
  {
6531
6575
  role: "columnheader",
6532
- className: "flex items-center border-b border-r border-border bg-surface px-3 font-medium text-foreground-secondary",
6576
+ "aria-sort": sortDir ? sortDir === "asc" ? "ascending" : "descending" : void 0,
6577
+ onClick: canSort ? () => toggleSort(c.key) : void 0,
6578
+ className: cx(
6579
+ "flex items-center gap-1 border-b border-r border-border bg-surface px-3 font-medium text-foreground-secondary",
6580
+ canSort && "cursor-pointer select-none hover:text-foreground"
6581
+ ),
6533
6582
  style: {
6534
6583
  position: "absolute",
6535
6584
  top: scroll.top,
@@ -6539,38 +6588,42 @@ function DataGrid({
6539
6588
  zIndex: 2,
6540
6589
  justifyContent: c.align === "right" ? "flex-end" : c.align === "center" ? "center" : "flex-start"
6541
6590
  },
6542
- children: /* @__PURE__ */ jsx("span", { className: "truncate", children: c.label ?? c.key })
6591
+ children: [
6592
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: c.label ?? c.key }),
6593
+ canSort && /* @__PURE__ */ jsx(SortCaret, { dir: sortDir })
6594
+ ]
6543
6595
  },
6544
6596
  `h-${c.key}`
6545
6597
  );
6546
6598
  }),
6547
- rowNumbers && visibleRows.map((ri) => /* @__PURE__ */ jsx(
6599
+ rowNumbers && visibleRows.map((disp) => /* @__PURE__ */ jsx(
6548
6600
  "div",
6549
6601
  {
6550
6602
  className: "flex items-center justify-center border-b border-r border-border bg-surface text-xs tabular-nums text-foreground-muted",
6551
- style: { position: "absolute", left: scroll.left, top: headerHeight + ri * rowHeight, width: gutter, height: rowHeight, zIndex: 1 },
6552
- children: ri + 1
6603
+ style: { position: "absolute", left: scroll.left, top: headerHeight + disp * rowHeight, width: gutter, height: rowHeight, zIndex: 1 },
6604
+ children: disp + 1
6553
6605
  },
6554
- `g-${ri}`
6606
+ `g-${disp}`
6555
6607
  )),
6556
- visibleRows.map(
6557
- (ri) => visibleCols.map((ci) => {
6608
+ visibleRows.map((disp) => {
6609
+ const ri = rowIndexForDisp(disp);
6610
+ return visibleCols.map((ci) => {
6558
6611
  const c = columns[ci];
6559
- const isEditing = editing?.row === ri && editing?.col === ci;
6612
+ const isEditing = editing?.disp === disp && editing?.col === ci;
6560
6613
  const canEdit = c.editable ?? editable;
6561
6614
  return /* @__PURE__ */ jsx(
6562
6615
  "div",
6563
6616
  {
6564
6617
  role: "gridcell",
6565
- onDoubleClick: () => startEdit(ri, ci),
6618
+ onDoubleClick: () => startEdit(disp, ci),
6566
6619
  className: cx(
6567
6620
  "flex items-center border-b border-r border-border px-3",
6568
- ri % 2 ? "bg-surface-raised" : "bg-surface",
6621
+ disp % 2 ? "bg-surface-raised" : "bg-surface",
6569
6622
  canEdit && "cursor-text"
6570
6623
  ),
6571
6624
  style: {
6572
6625
  position: "absolute",
6573
- top: headerHeight + ri * rowHeight,
6626
+ top: headerHeight + disp * rowHeight,
6574
6627
  left: gutter + offsets[ci],
6575
6628
  width: widths[ci],
6576
6629
  height: rowHeight,
@@ -6591,16 +6644,22 @@ function DataGrid({
6591
6644
  }
6592
6645
  ) : /* @__PURE__ */ jsx("span", { className: "truncate text-foreground", children: displayValue(rows[ri]?.[c.key] ?? "") })
6593
6646
  },
6594
- `${ri}-${c.key}`
6647
+ `${disp}-${c.key}`
6595
6648
  );
6596
- })
6597
- )
6649
+ });
6650
+ })
6598
6651
  ] }),
6599
- rows.length === 0 && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center justify-center text-foreground-muted", style: { top: headerHeight }, children: emptyState })
6652
+ displayRowCount === 0 && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center justify-center text-foreground-muted", style: { top: headerHeight }, children: emptyState })
6600
6653
  ]
6601
6654
  }
6602
6655
  );
6603
6656
  }
6657
+ function SortCaret({ dir }) {
6658
+ return /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 16 16", className: "h-3 w-3 flex-shrink-0", fill: "none", stroke: "currentColor", strokeWidth: 1.75, "aria-hidden": "true", children: [
6659
+ /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M5 6.5 8 3.5l3 3", className: dir === "asc" ? "text-accent" : "text-foreground-muted opacity-50" }),
6660
+ /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M5 9.5 8 12.5l3-3", className: dir === "desc" ? "text-accent" : "text-foreground-muted opacity-50" })
6661
+ ] });
6662
+ }
6604
6663
  function toColumns(cols) {
6605
6664
  return cols.map((c) => typeof c === "string" ? { key: c, label: c } : { ...c, label: c.label ?? c.key });
6606
6665
  }
@@ -6608,6 +6667,18 @@ function cellValue(v) {
6608
6667
  if (v != null && typeof v === "object" && "value" in v) return v.value;
6609
6668
  return v;
6610
6669
  }
6670
+ function columnLetter(i) {
6671
+ let s = "";
6672
+ let n = i;
6673
+ do {
6674
+ s = String.fromCharCode(65 + n % 26) + s;
6675
+ n = Math.floor(n / 26) - 1;
6676
+ } while (n >= 0);
6677
+ return s;
6678
+ }
6679
+ function blankSheet(name, cols = 8) {
6680
+ return { name, columns: Array.from({ length: cols }, (_, i) => ({ key: columnLetter(i), label: columnLetter(i) })), rows: [] };
6681
+ }
6611
6682
  function toPlainRows(sheet, columns) {
6612
6683
  return sheet.rows.map((row) => {
6613
6684
  const out = {};
@@ -6639,9 +6710,15 @@ function Spreadsheet({
6639
6710
  export: exportFormats = ["xlsx", "csv", "pdf"],
6640
6711
  fileName,
6641
6712
  virtualize = true,
6713
+ sortable = true,
6714
+ emptyRows = 50,
6715
+ allowAddSheet,
6716
+ height = 480,
6717
+ width,
6642
6718
  className = "",
6643
6719
  style
6644
6720
  }) {
6721
+ const canAddSheet = allowAddSheet ?? editable;
6645
6722
  const [sheets, setSheets] = useState(Array.isArray(source) ? source : null);
6646
6723
  const [active, setActive] = useState(0);
6647
6724
  const [status, setStatus] = useState(Array.isArray(source) ? "ready" : "loading");
@@ -6701,6 +6778,7 @@ function Spreadsheet({
6701
6778
  if (!prev) return prev;
6702
6779
  const next = prev.map((s, i) => i === active ? { ...s, rows: s.rows.map((r) => ({ ...r })) } : s);
6703
6780
  const target = next[active];
6781
+ while (target.rows.length <= row) target.rows.push({});
6704
6782
  const existing = target.rows[row]?.[column];
6705
6783
  const prevValue = cellValue(existing);
6706
6784
  coerced = coerceToCellType(prevValue, value);
@@ -6714,6 +6792,18 @@ function Spreadsheet({
6714
6792
  });
6715
6793
  onCellEdit?.({ sheet: sheets?.[active]?.name ?? "", row, column, value: coerced });
6716
6794
  }, [active, onCellEdit, onChange, sheets]);
6795
+ const addSheet = useCallback(() => {
6796
+ setSheets((prev) => {
6797
+ const list = prev ?? [];
6798
+ const used = new Set(list.map((s) => s.name));
6799
+ let n = list.length + 1;
6800
+ while (used.has(`Sheet ${n}`)) n++;
6801
+ const next = [...list, blankSheet(`Sheet ${n}`)];
6802
+ setActive(next.length - 1);
6803
+ onChange?.(next);
6804
+ return next;
6805
+ });
6806
+ }, [onChange]);
6717
6807
  const baseName = useMemo(
6718
6808
  () => (fileName || (typeof source === "object" && "name" in source ? sourceName(source) : null) || "spreadsheet").replace(/\.[^.]+$/, ""),
6719
6809
  [fileName, source]
@@ -6786,16 +6876,16 @@ function Spreadsheet({
6786
6876
  else void exportPdf();
6787
6877
  };
6788
6878
  if (status === "error") {
6789
- return /* @__PURE__ */ jsxs("div", { className: cx("flex flex-col items-center justify-center gap-3 rounded-lg border border-border bg-surface p-8 text-center", className), style, children: [
6879
+ return /* @__PURE__ */ jsxs("div", { className: cx("flex flex-col items-center justify-center gap-3 rounded-lg border border-border bg-surface p-8 text-center", className), style: { height, width, ...style }, children: [
6790
6880
  /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-status-error", children: "Couldn\u2019t load the spreadsheet" }),
6791
6881
  error?.message && /* @__PURE__ */ jsx("p", { className: "max-w-md text-xs text-foreground-muted", children: error.message }),
6792
6882
  /* @__PURE__ */ jsx(Button_default, { content: "Retry", size: "sm", variant: "outline", onClick: () => setReloadKey((k) => k + 1) })
6793
6883
  ] });
6794
6884
  }
6795
6885
  if (status === "loading" || !sheets) {
6796
- return /* @__PURE__ */ jsxs("div", { className: cx("rounded-lg border border-border bg-surface-raised p-4", className), style, children: [
6886
+ return /* @__PURE__ */ jsxs("div", { className: cx("overflow-hidden rounded-lg border border-border bg-surface-raised p-4", className), style: { height, width, ...style }, children: [
6797
6887
  /* @__PURE__ */ jsx(SkeletonBox, { height: 32, className: "mb-3 rounded" }),
6798
- /* @__PURE__ */ jsx(SkeletonBox, { height: 360, className: "rounded" })
6888
+ /* @__PURE__ */ jsx(SkeletonBox, { height: "calc(100% - 44px)", className: "rounded" })
6799
6889
  ] });
6800
6890
  }
6801
6891
  const formats = exportFormats || [];
@@ -6804,25 +6894,10 @@ function Spreadsheet({
6804
6894
  csv: "CSV \u2014 this sheet (.csv)",
6805
6895
  pdf: "PDF table (.pdf)"
6806
6896
  };
6807
- return /* @__PURE__ */ jsxs("div", { className: cx("flex flex-col overflow-hidden rounded-lg border border-border bg-surface-raised", className), style, children: [
6808
- /* @__PURE__ */ jsxs("div", { className: "flex flex-shrink-0 items-center gap-2 border-b border-border bg-surface px-2 py-1.5", children: [
6809
- sheets.length > 1 ? /* @__PURE__ */ jsx("div", { role: "tablist", "aria-label": "Sheets", className: "flex min-w-0 flex-1 items-center gap-0.5 overflow-x-auto", children: sheets.map((s, i) => /* @__PURE__ */ jsx(
6810
- "button",
6811
- {
6812
- role: "tab",
6813
- type: "button",
6814
- "aria-selected": i === active,
6815
- onClick: () => setActive(i),
6816
- className: cx(
6817
- "flex-shrink-0 rounded-md px-3 py-1 text-sm font-medium transition-colors",
6818
- "focus:outline-none focus-visible:ring-2 focus-visible:ring-accent",
6819
- i === active ? "bg-accent text-accent-fg shadow-sm" : "text-foreground-secondary hover:bg-surface-raised hover:text-foreground"
6820
- ),
6821
- children: s.name || `Sheet ${i + 1}`
6822
- },
6823
- `${s.name}-${i}`
6824
- )) }) : /* @__PURE__ */ jsx("span", { className: "flex-1 truncate px-1 text-sm font-medium text-foreground", children: sheet?.name || "Sheet 1" }),
6825
- /* @__PURE__ */ jsxs("span", { className: "hidden flex-shrink-0 px-1 text-xs tabular-nums text-foreground-muted sm:inline", children: [
6897
+ return /* @__PURE__ */ jsxs("div", { className: cx("flex flex-col overflow-hidden rounded-lg border border-border bg-surface-raised", className), style: { height, width, ...style }, children: [
6898
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-shrink-0 items-center gap-2 border-b border-border bg-surface px-3 py-1.5", children: [
6899
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-sm font-semibold text-foreground", children: sheet?.name || "Sheet 1" }),
6900
+ /* @__PURE__ */ jsxs("span", { className: "hidden flex-shrink-0 text-xs tabular-nums text-foreground-muted sm:inline", children: [
6826
6901
  plainRows.length.toLocaleString(),
6827
6902
  " ",
6828
6903
  plainRows.length === 1 ? "row" : "rows",
@@ -6849,17 +6924,53 @@ function Spreadsheet({
6849
6924
  )
6850
6925
  ] })
6851
6926
  ] }),
6852
- /* @__PURE__ */ jsx(
6927
+ /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1", children: /* @__PURE__ */ jsx(
6853
6928
  DataGrid,
6854
6929
  {
6855
6930
  columns,
6856
6931
  rows: plainRows,
6857
6932
  editable,
6933
+ sortable,
6858
6934
  virtualize,
6935
+ trailingRows: emptyRows,
6859
6936
  onCellEdit: handleCellEdit,
6937
+ height: "100%",
6860
6938
  className: "!rounded-none !border-0"
6861
- }
6862
- )
6939
+ },
6940
+ active
6941
+ ) }),
6942
+ (sheets.length > 1 || canAddSheet) && /* @__PURE__ */ jsxs("div", { className: "flex flex-shrink-0 items-center gap-1 border-t border-border bg-surface px-2 py-1", children: [
6943
+ /* @__PURE__ */ jsx("div", { role: "tablist", "aria-label": "Sheets", className: "flex min-w-0 flex-1 items-center gap-1 overflow-x-auto", children: sheets.map((s, i) => {
6944
+ const activeTab = i === active;
6945
+ return /* @__PURE__ */ jsx(
6946
+ "button",
6947
+ {
6948
+ role: "tab",
6949
+ type: "button",
6950
+ "aria-selected": activeTab,
6951
+ onClick: () => setActive(i),
6952
+ className: cx(
6953
+ "flex-shrink-0 rounded-t-md border-t-2 px-3 py-1 text-xs transition-colors",
6954
+ "focus:outline-none focus-visible:ring-2 focus-visible:ring-accent",
6955
+ activeTab ? "border-t-accent bg-surface-raised font-semibold text-foreground shadow-sm" : "border-t-transparent font-medium text-foreground-secondary hover:bg-surface-raised hover:text-foreground"
6956
+ ),
6957
+ children: s.name || `Sheet ${i + 1}`
6958
+ },
6959
+ `${s.name}-${i}`
6960
+ );
6961
+ }) }),
6962
+ canAddSheet && /* @__PURE__ */ jsx(
6963
+ "button",
6964
+ {
6965
+ type: "button",
6966
+ onClick: addSheet,
6967
+ title: "Add sheet",
6968
+ "aria-label": "Add sheet",
6969
+ className: "flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-md text-foreground-muted transition-colors hover:bg-surface-raised hover:text-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-accent",
6970
+ children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, className: "h-4 w-4", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", d: "M12 5v14M5 12h14" }) })
6971
+ }
6972
+ )
6973
+ ] })
6863
6974
  ] });
6864
6975
  }
6865
6976
  var DownloadIcon2 = () => /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, className: "h-4 w-4", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M12 3v12m0 0 4-4m-4 4-4-4M4 21h16" }) });
@@ -6963,7 +7074,7 @@ function FlyoutItems({ items }) {
6963
7074
  }
6964
7075
  function CollapsedFlyout({ item, trigger }) {
6965
7076
  const [open, setOpen] = useState(false);
6966
- const closeTimer = React34.useRef(void 0);
7077
+ const closeTimer = React36.useRef(void 0);
6967
7078
  const openNow = () => {
6968
7079
  window.clearTimeout(closeTimer.current);
6969
7080
  setOpen(true);
@@ -6972,7 +7083,7 @@ function CollapsedFlyout({ item, trigger }) {
6972
7083
  window.clearTimeout(closeTimer.current);
6973
7084
  closeTimer.current = window.setTimeout(() => setOpen(false), 150);
6974
7085
  };
6975
- React34.useEffect(() => () => window.clearTimeout(closeTimer.current), []);
7086
+ React36.useEffect(() => () => window.clearTimeout(closeTimer.current), []);
6976
7087
  return /* @__PURE__ */ jsxs(DropdownMenu2.Root, { open, onOpenChange: setOpen, modal: false, children: [
6977
7088
  /* @__PURE__ */ jsx(
6978
7089
  DropdownMenu2.Trigger,
@@ -7264,8 +7375,8 @@ function MegaMenuLink({ href, icon, description, active, onClick, children, clas
7264
7375
  function MegaMenuFeatured({ children, className = "" }) {
7265
7376
  return /* @__PURE__ */ jsx("div", { className: cx("min-w-0 rounded-lg bg-surface-raised border border-border p-4 flex flex-col", className), children });
7266
7377
  }
7267
- var elementsOfType = (children, type) => React34.Children.toArray(children).filter(
7268
- (c) => React34.isValidElement(c) && c.type === type
7378
+ var elementsOfType = (children, type) => React36.Children.toArray(children).filter(
7379
+ (c) => React36.isValidElement(c) && c.type === type
7269
7380
  );
7270
7381
  var MOBILE_CHEVRON = /* @__PURE__ */ jsx(
7271
7382
  "svg",
@@ -7302,9 +7413,9 @@ function MobileLinkRow({ link, onNavigate }) {
7302
7413
  );
7303
7414
  }
7304
7415
  function MobilePanel({ panel, onNavigate }) {
7305
- const nodes = React34.Children.toArray(panel.props.children);
7416
+ const nodes = React36.Children.toArray(panel.props.children);
7306
7417
  return /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-4 px-2 pb-3 pt-1", children: nodes.map((node, i) => {
7307
- if (!React34.isValidElement(node)) return null;
7418
+ if (!React36.isValidElement(node)) return null;
7308
7419
  const el = node;
7309
7420
  if (el.type === MegaMenuSection) {
7310
7421
  const { title, children } = el.props;
@@ -7713,7 +7824,7 @@ function ThemeProvider({
7713
7824
  className = "",
7714
7825
  style
7715
7826
  }) {
7716
- const id = React34.useId().replace(/:/g, "");
7827
+ const id = React36.useId().replace(/:/g, "");
7717
7828
  const scopeClass = `geo-th-${id}`;
7718
7829
  const divRef = useRef(null);
7719
7830
  useEffect(() => {
@@ -8526,8 +8637,11 @@ function Dropdown({
8526
8637
  labelStyle = {},
8527
8638
  placeholder,
8528
8639
  size = "md",
8529
- className = ""
8640
+ className = "",
8641
+ container
8530
8642
  }) {
8643
+ const dialogContainer = useDialogContainer();
8644
+ const portalContainer = container ?? dialogContainer ?? void 0;
8531
8645
  const [open, setOpen] = useState(false);
8532
8646
  const [selectedItems, setSelectedItems] = useState([]);
8533
8647
  const [searchTerm, setSearchTerm] = useState("");
@@ -8633,7 +8747,7 @@ function Dropdown({
8633
8747
  ]
8634
8748
  }
8635
8749
  ) }),
8636
- /* @__PURE__ */ jsx(Popover.Portal, { children: /* @__PURE__ */ jsxs(
8750
+ /* @__PURE__ */ jsx(Popover.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxs(
8637
8751
  Popover.Content,
8638
8752
  {
8639
8753
  align: "start",
@@ -10021,7 +10135,7 @@ function OtpInput({
10021
10135
  emit(valid.join(""));
10022
10136
  focusBox(valid.length);
10023
10137
  };
10024
- 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(React34.Fragment, { children: [
10138
+ 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(React36.Fragment, { children: [
10025
10139
  /* @__PURE__ */ jsx(
10026
10140
  "input",
10027
10141
  {
@@ -11080,7 +11194,7 @@ function Blog({
11080
11194
  post.tag != null && !post.image && /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(Badge, { tone: "accent", variant: "soft", size: "sm", children: post.tag }) }),
11081
11195
  /* @__PURE__ */ jsx("h3", { className: "text-base font-semibold leading-snug text-foreground transition-colors group-hover:text-accent", children: post.title }),
11082
11196
  post.excerpt != null && /* @__PURE__ */ jsx("p", { className: "line-clamp-3 text-sm leading-relaxed text-foreground-secondary", children: post.excerpt }),
11083
- meta.length > 0 && /* @__PURE__ */ jsx("div", { className: "mt-auto flex flex-wrap items-center gap-x-2 gap-y-1 pt-3 text-xs text-foreground-muted", children: meta.map((m, j) => /* @__PURE__ */ jsxs(React34.Fragment, { children: [
11197
+ meta.length > 0 && /* @__PURE__ */ jsx("div", { className: "mt-auto flex flex-wrap items-center gap-x-2 gap-y-1 pt-3 text-xs text-foreground-muted", children: meta.map((m, j) => /* @__PURE__ */ jsxs(React36.Fragment, { children: [
11084
11198
  j > 0 && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\xB7" }),
11085
11199
  /* @__PURE__ */ jsx("span", { children: m })
11086
11200
  ] }, j)) })