@geomak/ui 7.0.0 → 7.1.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/README.md +3 -3
- package/dist/index.cjs +193 -51
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +56 -1
- package/dist/index.d.ts +56 -1
- package/dist/index.js +193 -51
- package/dist/index.js.map +1 -1
- package/dist/styles.css +3 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,9 +10,9 @@
|
|
|
10
10
|
[](https://www.npmjs.com/package/@geomak/ui)
|
|
11
11
|
[](./LICENSE)
|
|
12
12
|
[](https://react.dev)
|
|
13
|
-
[](https://
|
|
13
|
+
[](https://oxygenui.com)
|
|
14
14
|
|
|
15
|
-
### ▶ [Browse the live, interactive demo →](https://
|
|
15
|
+
### ▶ [Browse the live, interactive demo →](https://oxygenui.com)
|
|
16
16
|
|
|
17
17
|
<!-- Tip: drop a Storybook screen-recording here for the launch — docs/assets/preview.gif -->
|
|
18
18
|
|
|
@@ -64,7 +64,7 @@ import { ChevronDown, Search, createIcon } from '@geomak/ui/icons'
|
|
|
64
64
|
|
|
65
65
|
## Components
|
|
66
66
|
|
|
67
|
-
60+ components across these groups — all with **live controls and a written guide** in [Storybook](https://
|
|
67
|
+
60+ components across these groups — all with **live controls and a written guide** in [Storybook](https://oxygenui.com).
|
|
68
68
|
|
|
69
69
|
| Group | Components |
|
|
70
70
|
|---|---|
|
package/dist/index.cjs
CHANGED
|
@@ -5310,22 +5310,78 @@ function createDatasets(rows, perPage) {
|
|
|
5310
5310
|
}
|
|
5311
5311
|
var defaultGetRowKey = (_row, index) => index;
|
|
5312
5312
|
var cellAlign = (align) => align === "left" ? "text-left" : align === "right" ? "text-right" : "text-center";
|
|
5313
|
+
function compareValues(a, b) {
|
|
5314
|
+
if (a == null && b == null) return 0;
|
|
5315
|
+
if (a == null) return -1;
|
|
5316
|
+
if (b == null) return 1;
|
|
5317
|
+
if (typeof a === "number" && typeof b === "number") return a - b;
|
|
5318
|
+
if (a instanceof Date && b instanceof Date) return a.getTime() - b.getTime();
|
|
5319
|
+
if (typeof a === "boolean" && typeof b === "boolean") return a === b ? 0 : a ? 1 : -1;
|
|
5320
|
+
return String(a).localeCompare(String(b), void 0, { numeric: true, sensitivity: "base" });
|
|
5321
|
+
}
|
|
5322
|
+
function SortGlyph({ direction }) {
|
|
5323
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("svg", { viewBox: "0 0 24 24", width: "14", height: "14", fill: "none", "aria-hidden": "true", className: "shrink-0", children: [
|
|
5324
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5325
|
+
"path",
|
|
5326
|
+
{
|
|
5327
|
+
d: "M8 11l4-4 4 4",
|
|
5328
|
+
stroke: "currentColor",
|
|
5329
|
+
strokeWidth: 2,
|
|
5330
|
+
strokeLinecap: "round",
|
|
5331
|
+
strokeLinejoin: "round",
|
|
5332
|
+
className: direction === "asc" ? "text-accent" : "text-foreground-muted",
|
|
5333
|
+
opacity: direction === "asc" ? 1 : 0.45
|
|
5334
|
+
}
|
|
5335
|
+
),
|
|
5336
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5337
|
+
"path",
|
|
5338
|
+
{
|
|
5339
|
+
d: "M8 13l4 4 4-4",
|
|
5340
|
+
stroke: "currentColor",
|
|
5341
|
+
strokeWidth: 2,
|
|
5342
|
+
strokeLinecap: "round",
|
|
5343
|
+
strokeLinejoin: "round",
|
|
5344
|
+
className: direction === "desc" ? "text-accent" : "text-foreground-muted",
|
|
5345
|
+
opacity: direction === "desc" ? 1 : 0.45
|
|
5346
|
+
}
|
|
5347
|
+
)
|
|
5348
|
+
] });
|
|
5349
|
+
}
|
|
5313
5350
|
function TableHeader({
|
|
5314
5351
|
columns,
|
|
5315
|
-
hasExpand
|
|
5352
|
+
hasExpand,
|
|
5353
|
+
sort,
|
|
5354
|
+
onSort
|
|
5316
5355
|
}) {
|
|
5317
5356
|
return /* @__PURE__ */ jsxRuntime.jsx("thead", { className: "bg-surface-raised border-b border-border", children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
|
|
5318
5357
|
hasExpand && /* @__PURE__ */ jsxRuntime.jsx("th", { "aria-hidden": "true", className: "w-9" }),
|
|
5319
|
-
columns.map((col) =>
|
|
5320
|
-
|
|
5321
|
-
|
|
5322
|
-
|
|
5323
|
-
|
|
5324
|
-
|
|
5325
|
-
|
|
5326
|
-
|
|
5327
|
-
|
|
5328
|
-
|
|
5358
|
+
columns.map((col) => {
|
|
5359
|
+
const active = sort?.key === col.keyBind;
|
|
5360
|
+
const dir = active ? sort.direction : void 0;
|
|
5361
|
+
const justify = col.align === "left" ? "justify-start" : col.align === "right" ? "justify-end" : "justify-center";
|
|
5362
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
5363
|
+
"th",
|
|
5364
|
+
{
|
|
5365
|
+
scope: "col",
|
|
5366
|
+
"aria-sort": col.sortable ? active ? dir === "asc" ? "ascending" : "descending" : "none" : void 0,
|
|
5367
|
+
className: `${cellAlign(col.align)} text-sm font-semibold text-foreground py-3 px-3`,
|
|
5368
|
+
style: col.width != null ? { width: col.width } : void 0,
|
|
5369
|
+
children: col.sortable ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
5370
|
+
"button",
|
|
5371
|
+
{
|
|
5372
|
+
type: "button",
|
|
5373
|
+
onClick: () => onSort(col),
|
|
5374
|
+
className: `inline-flex items-center gap-1.5 ${justify} w-full select-none rounded transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-accent ${active ? "text-accent" : "hover:text-accent"}`,
|
|
5375
|
+
children: [
|
|
5376
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: col.label }),
|
|
5377
|
+
/* @__PURE__ */ jsxRuntime.jsx(SortGlyph, { direction: dir })
|
|
5378
|
+
]
|
|
5379
|
+
}
|
|
5380
|
+
) : col.label
|
|
5381
|
+
},
|
|
5382
|
+
col.key
|
|
5383
|
+
);
|
|
5384
|
+
})
|
|
5329
5385
|
] }) });
|
|
5330
5386
|
}
|
|
5331
5387
|
var DefaultExpandIcon = /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -5333,24 +5389,66 @@ var DefaultExpandIcon = /* @__PURE__ */ jsxRuntime.jsx(
|
|
|
5333
5389
|
{
|
|
5334
5390
|
xmlns: "http://www.w3.org/2000/svg",
|
|
5335
5391
|
viewBox: "0 0 24 24",
|
|
5336
|
-
fill: "
|
|
5337
|
-
|
|
5392
|
+
fill: "none",
|
|
5393
|
+
stroke: "currentColor",
|
|
5394
|
+
strokeWidth: 2,
|
|
5395
|
+
className: "w-4 h-4 text-foreground-muted",
|
|
5338
5396
|
"aria-hidden": "true",
|
|
5339
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
5340
|
-
|
|
5397
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M19.5 8.25l-7.5 7.5-7.5-7.5" })
|
|
5398
|
+
}
|
|
5399
|
+
);
|
|
5400
|
+
function EditableCell({
|
|
5401
|
+
col,
|
|
5402
|
+
row,
|
|
5403
|
+
rowIndex,
|
|
5404
|
+
onCellEdit
|
|
5405
|
+
}) {
|
|
5406
|
+
const [editing, setEditing] = React28.useState(false);
|
|
5407
|
+
const value = row[col.keyBind];
|
|
5408
|
+
const commit = (next) => {
|
|
5409
|
+
setEditing(false);
|
|
5410
|
+
onCellEdit?.({ row, key: col.keyBind, value: next, rowIndex });
|
|
5411
|
+
};
|
|
5412
|
+
const cancel = () => setEditing(false);
|
|
5413
|
+
if (editing) {
|
|
5414
|
+
if (col.editor) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: col.editor({ value, row, commit, cancel }) });
|
|
5415
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
5416
|
+
"input",
|
|
5341
5417
|
{
|
|
5342
|
-
|
|
5343
|
-
|
|
5344
|
-
|
|
5418
|
+
autoFocus: true,
|
|
5419
|
+
defaultValue: value == null ? "" : String(value),
|
|
5420
|
+
onBlur: (e) => commit(e.target.value),
|
|
5421
|
+
onKeyDown: (e) => {
|
|
5422
|
+
if (e.key === "Enter") {
|
|
5423
|
+
e.preventDefault();
|
|
5424
|
+
commit(e.target.value);
|
|
5425
|
+
} else if (e.key === "Escape") {
|
|
5426
|
+
e.preventDefault();
|
|
5427
|
+
cancel();
|
|
5428
|
+
}
|
|
5429
|
+
},
|
|
5430
|
+
"aria-label": `Edit ${typeof col.label === "string" ? col.label : col.keyBind}`,
|
|
5431
|
+
className: "w-full rounded border border-accent bg-surface px-2 py-1 text-sm text-foreground outline-none"
|
|
5345
5432
|
}
|
|
5346
|
-
)
|
|
5433
|
+
);
|
|
5347
5434
|
}
|
|
5348
|
-
|
|
5435
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
5436
|
+
"button",
|
|
5437
|
+
{
|
|
5438
|
+
type: "button",
|
|
5439
|
+
onClick: () => setEditing(true),
|
|
5440
|
+
title: "Click to edit",
|
|
5441
|
+
className: `${cellAlign(col.align)} w-full cursor-text rounded px-1 py-0.5 hover:bg-background focus:outline-none focus-visible:ring-2 focus-visible:ring-accent`,
|
|
5442
|
+
children: col.component ? col.component(value, row) : value
|
|
5443
|
+
}
|
|
5444
|
+
);
|
|
5445
|
+
}
|
|
5349
5446
|
function TableBody({
|
|
5350
5447
|
columns,
|
|
5351
5448
|
rows,
|
|
5352
5449
|
expandRow,
|
|
5353
|
-
getRowKey
|
|
5450
|
+
getRowKey,
|
|
5451
|
+
onCellEdit
|
|
5354
5452
|
}) {
|
|
5355
5453
|
const [expanded, setExpanded] = React28.useState(() => /* @__PURE__ */ new Set());
|
|
5356
5454
|
const reduced = framerMotion.useReducedMotion();
|
|
@@ -5380,15 +5478,15 @@ function TableBody({
|
|
|
5380
5478
|
onClick: () => toggleRow(rowKey),
|
|
5381
5479
|
"aria-expanded": isExpanded,
|
|
5382
5480
|
"aria-label": isExpanded ? "Collapse row" : "Expand row",
|
|
5383
|
-
className: `w-9 h-9 inline-flex items-center justify-center rounded-md hover:bg-
|
|
5384
|
-
children: expandRow.expandIcon ?? DefaultExpandIcon
|
|
5481
|
+
className: `w-9 h-9 inline-flex items-center justify-center rounded-md hover:bg-background transition-transform duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-accent ${isExpanded && !expandRow.collapseIcon ? "rotate-180" : ""}`,
|
|
5482
|
+
children: isExpanded ? expandRow.collapseIcon ?? expandRow.expandIcon ?? DefaultExpandIcon : expandRow.expandIcon ?? DefaultExpandIcon
|
|
5385
5483
|
}
|
|
5386
5484
|
) }),
|
|
5387
5485
|
columns.map((col) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
5388
5486
|
"td",
|
|
5389
5487
|
{
|
|
5390
5488
|
className: `${cellAlign(col.align)} text-sm text-foreground py-2 px-3 align-middle`,
|
|
5391
|
-
children: col.component ? col.component(row[col.keyBind], row) : row[col.keyBind]
|
|
5489
|
+
children: col.editable ? /* @__PURE__ */ jsxRuntime.jsx(EditableCell, { col, row, rowIndex: i, onCellEdit }) : col.component ? col.component(row[col.keyBind], row) : row[col.keyBind]
|
|
5392
5490
|
},
|
|
5393
5491
|
col.key
|
|
5394
5492
|
))
|
|
@@ -5482,6 +5580,10 @@ function Table({
|
|
|
5482
5580
|
pagination = DEFAULT_PAGINATION,
|
|
5483
5581
|
expandRow = DEFAULT_EXPAND,
|
|
5484
5582
|
hasSearch = true,
|
|
5583
|
+
search,
|
|
5584
|
+
defaultSort = null,
|
|
5585
|
+
onSortChange,
|
|
5586
|
+
onCellEdit,
|
|
5485
5587
|
footer = null,
|
|
5486
5588
|
header = null,
|
|
5487
5589
|
loading = false,
|
|
@@ -5495,20 +5597,54 @@ function Table({
|
|
|
5495
5597
|
typeof pagination.perPage === "number" ? pagination.perPage : 15
|
|
5496
5598
|
);
|
|
5497
5599
|
const [activePage, setActivePage] = React28.useState(0);
|
|
5600
|
+
const [sortState, setSortState] = React28.useState(defaultSort);
|
|
5498
5601
|
const isServerSide = !!(pagination.enabled && pagination.serverSide);
|
|
5602
|
+
const handleSort = (col) => {
|
|
5603
|
+
const key = col.keyBind;
|
|
5604
|
+
let next;
|
|
5605
|
+
if (!sortState || sortState.key !== key) next = { key, direction: "asc" };
|
|
5606
|
+
else if (sortState.direction === "asc") next = { key, direction: "desc" };
|
|
5607
|
+
else next = null;
|
|
5608
|
+
setSortState(next);
|
|
5609
|
+
onSortChange?.(next);
|
|
5610
|
+
};
|
|
5611
|
+
const debounceMs = search?.debounceMs ?? 0;
|
|
5612
|
+
const [debouncedTerm, setDebouncedTerm] = React28.useState("");
|
|
5613
|
+
React28.useEffect(() => {
|
|
5614
|
+
if (debounceMs <= 0) {
|
|
5615
|
+
setDebouncedTerm(searchTerm);
|
|
5616
|
+
return;
|
|
5617
|
+
}
|
|
5618
|
+
const t = setTimeout(() => setDebouncedTerm(searchTerm), debounceMs);
|
|
5619
|
+
return () => clearTimeout(t);
|
|
5620
|
+
}, [searchTerm, debounceMs]);
|
|
5621
|
+
const term = debounceMs > 0 ? debouncedTerm : searchTerm;
|
|
5499
5622
|
const filteredRows = React28.useMemo(() => {
|
|
5500
|
-
if (isServerSide || !
|
|
5501
|
-
|
|
5502
|
-
|
|
5503
|
-
|
|
5504
|
-
|
|
5505
|
-
|
|
5506
|
-
)
|
|
5507
|
-
|
|
5623
|
+
if (isServerSide || !term) return rows;
|
|
5624
|
+
if (search?.predicate) return rows.filter((row) => search.predicate(row, term));
|
|
5625
|
+
const cs = !!search?.caseSensitive;
|
|
5626
|
+
const needle = cs ? term : term.toLowerCase();
|
|
5627
|
+
const mode = search?.matchMode ?? "contains";
|
|
5628
|
+
const keys = search?.keys;
|
|
5629
|
+
const test = (raw) => {
|
|
5630
|
+
if (raw == null) return false;
|
|
5631
|
+
const s = cs ? String(raw) : String(raw).toLowerCase();
|
|
5632
|
+
return mode === "startsWith" ? s.startsWith(needle) : mode === "equals" ? s === needle : s.includes(needle);
|
|
5633
|
+
};
|
|
5634
|
+
return rows.filter((row) => keys ? keys.some((k) => test(row[k])) : Object.values(row).some(test));
|
|
5635
|
+
}, [rows, term, isServerSide, search?.predicate, search?.caseSensitive, search?.matchMode, search?.keys]);
|
|
5636
|
+
const sortedRows = React28.useMemo(() => {
|
|
5637
|
+
if (isServerSide || !sortState) return filteredRows;
|
|
5638
|
+
const col = columns.find((c) => c.keyBind === sortState.key);
|
|
5639
|
+
const accessor = col?.sortAccessor ?? ((r) => r[sortState.key]);
|
|
5640
|
+
const out = [...filteredRows].sort((a, b) => compareValues(accessor(a), accessor(b)));
|
|
5641
|
+
if (sortState.direction === "desc") out.reverse();
|
|
5642
|
+
return out;
|
|
5643
|
+
}, [filteredRows, sortState, isServerSide, columns]);
|
|
5508
5644
|
const datasets = React28.useMemo(() => {
|
|
5509
5645
|
if (isServerSide) return [rows];
|
|
5510
|
-
return createDatasets(
|
|
5511
|
-
}, [
|
|
5646
|
+
return createDatasets(sortedRows, pagination.enabled ? perPage : null);
|
|
5647
|
+
}, [sortedRows, perPage, pagination.enabled, isServerSide, rows]);
|
|
5512
5648
|
const MAX_PAGE = React28.useMemo(() => {
|
|
5513
5649
|
if (isServerSide && typeof pagination.maxPage === "number") return Math.max(0, pagination.maxPage);
|
|
5514
5650
|
if (isServerSide && typeof pagination.totalCount === "number")
|
|
@@ -5547,32 +5683,36 @@ function Table({
|
|
|
5547
5683
|
}
|
|
5548
5684
|
setActivePage(newPage);
|
|
5549
5685
|
};
|
|
5686
|
+
const pagPos = pagination.position ?? "top";
|
|
5687
|
+
const showTopPager = !!pagination.enabled && (pagPos === "top" || pagPos === "both");
|
|
5688
|
+
const showBottomPager = !!pagination.enabled && (pagPos === "bottom" || pagPos === "both");
|
|
5689
|
+
const pager = /* @__PURE__ */ jsxRuntime.jsx(
|
|
5690
|
+
Pagination,
|
|
5691
|
+
{
|
|
5692
|
+
activePage,
|
|
5693
|
+
onPageChange: handlePageChange,
|
|
5694
|
+
maxPage: MAX_PAGE,
|
|
5695
|
+
onPerPageChange: onPaginationChange,
|
|
5696
|
+
options: pagination,
|
|
5697
|
+
serverSide: isServerSide
|
|
5698
|
+
}
|
|
5699
|
+
);
|
|
5550
5700
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `w-full h-max rounded-lg ${className}`.trim(), style, children: [
|
|
5551
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between mb-2", children: [
|
|
5552
|
-
hasSearch
|
|
5701
|
+
(hasSearch || showTopPager) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-3 mb-2", children: [
|
|
5702
|
+
hasSearch ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
5553
5703
|
SearchInput_default,
|
|
5554
5704
|
{
|
|
5555
5705
|
ref: searchRef,
|
|
5556
5706
|
value: searchTerm,
|
|
5557
5707
|
onChange: onSearchChange,
|
|
5558
|
-
placeholder: "Search term..."
|
|
5708
|
+
placeholder: search?.placeholder ?? "Search term..."
|
|
5559
5709
|
}
|
|
5560
|
-
),
|
|
5561
|
-
|
|
5562
|
-
Pagination,
|
|
5563
|
-
{
|
|
5564
|
-
activePage,
|
|
5565
|
-
onPageChange: handlePageChange,
|
|
5566
|
-
maxPage: MAX_PAGE,
|
|
5567
|
-
onPerPageChange: onPaginationChange,
|
|
5568
|
-
options: pagination,
|
|
5569
|
-
serverSide: isServerSide
|
|
5570
|
-
}
|
|
5571
|
-
)
|
|
5710
|
+
) : /* @__PURE__ */ jsxRuntime.jsx("span", {}),
|
|
5711
|
+
showTopPager && pager
|
|
5572
5712
|
] }),
|
|
5573
5713
|
/* @__PURE__ */ jsxRuntime.jsx("div", { children: header }),
|
|
5574
5714
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "overflow-x-auto rounded-lg", children: /* @__PURE__ */ jsxRuntime.jsxs("table", { className: "w-full border-collapse", "aria-busy": loading || void 0, children: [
|
|
5575
|
-
/* @__PURE__ */ jsxRuntime.jsx(TableHeader, { columns, hasExpand: !!expandRow.enabled }),
|
|
5715
|
+
/* @__PURE__ */ jsxRuntime.jsx(TableHeader, { columns, hasExpand: !!expandRow.enabled, sort: sortState, onSort: handleSort }),
|
|
5576
5716
|
loading ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
5577
5717
|
TableSkeletonBody,
|
|
5578
5718
|
{
|
|
@@ -5586,10 +5726,12 @@ function Table({
|
|
|
5586
5726
|
columns,
|
|
5587
5727
|
rows: currentPageRows,
|
|
5588
5728
|
expandRow,
|
|
5589
|
-
getRowKey
|
|
5729
|
+
getRowKey,
|
|
5730
|
+
onCellEdit
|
|
5590
5731
|
}
|
|
5591
5732
|
)
|
|
5592
5733
|
] }) }),
|
|
5734
|
+
showBottomPager && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-2 flex justify-end", children: pager }),
|
|
5593
5735
|
/* @__PURE__ */ jsxRuntime.jsx("div", { children: footer })
|
|
5594
5736
|
] });
|
|
5595
5737
|
}
|