@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/dist/index.d.cts
CHANGED
|
@@ -2375,11 +2375,55 @@ interface TableColumn<T extends Record<string, any> = Record<string, any>> {
|
|
|
2375
2375
|
width?: string | number;
|
|
2376
2376
|
/** Text alignment for both header and cells. Defaults to `'center'`. */
|
|
2377
2377
|
align?: 'left' | 'center' | 'right';
|
|
2378
|
+
/** Allow clicking this column's header to sort by it. */
|
|
2379
|
+
sortable?: boolean;
|
|
2380
|
+
/** Custom value used when sorting (defaults to `row[keyBind]`). */
|
|
2381
|
+
sortAccessor?: (row: T) => string | number | boolean | Date | null | undefined;
|
|
2382
|
+
/** Make this column's cells inline-editable. Pair with `onCellEdit`. */
|
|
2383
|
+
editable?: boolean;
|
|
2384
|
+
/** Custom inline editor (overrides the built-in text input). */
|
|
2385
|
+
editor?: (args: EditorArgs<T>) => react__default.ReactNode;
|
|
2386
|
+
}
|
|
2387
|
+
interface EditorArgs<T extends Record<string, any> = Record<string, any>> {
|
|
2388
|
+
value: T[keyof T];
|
|
2389
|
+
row: T;
|
|
2390
|
+
/** Commit a new value (fires `onCellEdit`) and leave edit mode. */
|
|
2391
|
+
commit: (next: string) => void;
|
|
2392
|
+
/** Discard changes and leave edit mode. */
|
|
2393
|
+
cancel: () => void;
|
|
2394
|
+
}
|
|
2395
|
+
interface CellEditInfo<T extends Record<string, any> = Record<string, any>> {
|
|
2396
|
+
row: T;
|
|
2397
|
+
key: keyof T & string;
|
|
2398
|
+
value: string;
|
|
2399
|
+
/** Index of the row within the current page. */
|
|
2400
|
+
rowIndex: number;
|
|
2401
|
+
}
|
|
2402
|
+
type SortDirection = 'asc' | 'desc';
|
|
2403
|
+
interface SortState {
|
|
2404
|
+
key: string;
|
|
2405
|
+
direction: SortDirection;
|
|
2406
|
+
}
|
|
2407
|
+
interface SearchOptions<T extends Record<string, any> = Record<string, any>> {
|
|
2408
|
+
/** Restrict search to these row keys. Default: every value in the row. */
|
|
2409
|
+
keys?: (keyof T & string)[];
|
|
2410
|
+
/** Match strategy. Default `'contains'`. */
|
|
2411
|
+
matchMode?: 'contains' | 'startsWith' | 'equals';
|
|
2412
|
+
/** Case-sensitive matching. Default `false`. */
|
|
2413
|
+
caseSensitive?: boolean;
|
|
2414
|
+
/** Debounce the filter (ms) — useful for large lists. Default `0`. */
|
|
2415
|
+
debounceMs?: number;
|
|
2416
|
+
/** Input placeholder. */
|
|
2417
|
+
placeholder?: string;
|
|
2418
|
+
/** Full custom matcher — overrides keys / matchMode / caseSensitive. */
|
|
2419
|
+
predicate?: (row: T, term: string) => boolean;
|
|
2378
2420
|
}
|
|
2379
2421
|
interface PaginationOptions {
|
|
2380
2422
|
enabled?: boolean;
|
|
2381
2423
|
perPage?: number;
|
|
2382
2424
|
withPicker?: boolean;
|
|
2425
|
+
/** Where to render the pager: `'top'` (default), `'bottom'`, or `'both'`. */
|
|
2426
|
+
position?: 'top' | 'bottom' | 'both';
|
|
2383
2427
|
serverSide?: boolean;
|
|
2384
2428
|
/** Server-side: current 1-based page number */
|
|
2385
2429
|
page?: number;
|
|
@@ -2397,7 +2441,10 @@ interface PaginationOptions {
|
|
|
2397
2441
|
}
|
|
2398
2442
|
interface ExpandRowOptions<T extends Record<string, any> = Record<string, any>> {
|
|
2399
2443
|
enabled?: boolean;
|
|
2444
|
+
/** Icon shown when collapsed. If no `collapseIcon` is given, this icon rotates 180° when open. */
|
|
2400
2445
|
expandIcon?: react__default.ReactNode;
|
|
2446
|
+
/** Distinct icon shown when expanded. When set, the icons swap instead of rotating. */
|
|
2447
|
+
collapseIcon?: react__default.ReactNode;
|
|
2401
2448
|
expandComponent?: (row: T) => react__default.ReactNode;
|
|
2402
2449
|
}
|
|
2403
2450
|
interface TableProps<T extends Record<string, any> = Record<string, any>> {
|
|
@@ -2414,6 +2461,14 @@ interface TableProps<T extends Record<string, any> = Record<string, any>> {
|
|
|
2414
2461
|
pagination?: PaginationOptions;
|
|
2415
2462
|
expandRow?: ExpandRowOptions<T>;
|
|
2416
2463
|
hasSearch?: boolean;
|
|
2464
|
+
/** Fine-tune search: keys, match mode, debounce, placeholder, custom predicate. */
|
|
2465
|
+
search?: SearchOptions<T>;
|
|
2466
|
+
/** Initial sort (uncontrolled). */
|
|
2467
|
+
defaultSort?: SortState | null;
|
|
2468
|
+
/** Fires when the sort column/direction changes, or clears (`null`). Use for server-side sorting. */
|
|
2469
|
+
onSortChange?: (sort: SortState | null) => void;
|
|
2470
|
+
/** Fires when an editable cell commits a new value. */
|
|
2471
|
+
onCellEdit?: (info: CellEditInfo<T>) => void;
|
|
2417
2472
|
footer?: react__default.ReactNode;
|
|
2418
2473
|
header?: react__default.ReactNode;
|
|
2419
2474
|
/**
|
|
@@ -2472,7 +2527,7 @@ interface TableProps<T extends Record<string, any> = Record<string, any>> {
|
|
|
2472
2527
|
* />
|
|
2473
2528
|
* ```
|
|
2474
2529
|
*/
|
|
2475
|
-
declare function Table<T extends Record<string, any> = Record<string, any>>({ columns, rows, getRowKey, pagination, expandRow, hasSearch, footer, header, loading, loadingRowCount, className, style, }: TableProps<T>): react_jsx_runtime.JSX.Element;
|
|
2530
|
+
declare function Table<T extends Record<string, any> = Record<string, any>>({ columns, rows, getRowKey, pagination, expandRow, hasSearch, search, defaultSort, onSortChange, onCellEdit, footer, header, loading, loadingRowCount, className, style, }: TableProps<T>): react_jsx_runtime.JSX.Element;
|
|
2476
2531
|
|
|
2477
2532
|
interface ThemeSwitchProps {
|
|
2478
2533
|
checked: boolean;
|
package/dist/index.d.ts
CHANGED
|
@@ -2375,11 +2375,55 @@ interface TableColumn<T extends Record<string, any> = Record<string, any>> {
|
|
|
2375
2375
|
width?: string | number;
|
|
2376
2376
|
/** Text alignment for both header and cells. Defaults to `'center'`. */
|
|
2377
2377
|
align?: 'left' | 'center' | 'right';
|
|
2378
|
+
/** Allow clicking this column's header to sort by it. */
|
|
2379
|
+
sortable?: boolean;
|
|
2380
|
+
/** Custom value used when sorting (defaults to `row[keyBind]`). */
|
|
2381
|
+
sortAccessor?: (row: T) => string | number | boolean | Date | null | undefined;
|
|
2382
|
+
/** Make this column's cells inline-editable. Pair with `onCellEdit`. */
|
|
2383
|
+
editable?: boolean;
|
|
2384
|
+
/** Custom inline editor (overrides the built-in text input). */
|
|
2385
|
+
editor?: (args: EditorArgs<T>) => react__default.ReactNode;
|
|
2386
|
+
}
|
|
2387
|
+
interface EditorArgs<T extends Record<string, any> = Record<string, any>> {
|
|
2388
|
+
value: T[keyof T];
|
|
2389
|
+
row: T;
|
|
2390
|
+
/** Commit a new value (fires `onCellEdit`) and leave edit mode. */
|
|
2391
|
+
commit: (next: string) => void;
|
|
2392
|
+
/** Discard changes and leave edit mode. */
|
|
2393
|
+
cancel: () => void;
|
|
2394
|
+
}
|
|
2395
|
+
interface CellEditInfo<T extends Record<string, any> = Record<string, any>> {
|
|
2396
|
+
row: T;
|
|
2397
|
+
key: keyof T & string;
|
|
2398
|
+
value: string;
|
|
2399
|
+
/** Index of the row within the current page. */
|
|
2400
|
+
rowIndex: number;
|
|
2401
|
+
}
|
|
2402
|
+
type SortDirection = 'asc' | 'desc';
|
|
2403
|
+
interface SortState {
|
|
2404
|
+
key: string;
|
|
2405
|
+
direction: SortDirection;
|
|
2406
|
+
}
|
|
2407
|
+
interface SearchOptions<T extends Record<string, any> = Record<string, any>> {
|
|
2408
|
+
/** Restrict search to these row keys. Default: every value in the row. */
|
|
2409
|
+
keys?: (keyof T & string)[];
|
|
2410
|
+
/** Match strategy. Default `'contains'`. */
|
|
2411
|
+
matchMode?: 'contains' | 'startsWith' | 'equals';
|
|
2412
|
+
/** Case-sensitive matching. Default `false`. */
|
|
2413
|
+
caseSensitive?: boolean;
|
|
2414
|
+
/** Debounce the filter (ms) — useful for large lists. Default `0`. */
|
|
2415
|
+
debounceMs?: number;
|
|
2416
|
+
/** Input placeholder. */
|
|
2417
|
+
placeholder?: string;
|
|
2418
|
+
/** Full custom matcher — overrides keys / matchMode / caseSensitive. */
|
|
2419
|
+
predicate?: (row: T, term: string) => boolean;
|
|
2378
2420
|
}
|
|
2379
2421
|
interface PaginationOptions {
|
|
2380
2422
|
enabled?: boolean;
|
|
2381
2423
|
perPage?: number;
|
|
2382
2424
|
withPicker?: boolean;
|
|
2425
|
+
/** Where to render the pager: `'top'` (default), `'bottom'`, or `'both'`. */
|
|
2426
|
+
position?: 'top' | 'bottom' | 'both';
|
|
2383
2427
|
serverSide?: boolean;
|
|
2384
2428
|
/** Server-side: current 1-based page number */
|
|
2385
2429
|
page?: number;
|
|
@@ -2397,7 +2441,10 @@ interface PaginationOptions {
|
|
|
2397
2441
|
}
|
|
2398
2442
|
interface ExpandRowOptions<T extends Record<string, any> = Record<string, any>> {
|
|
2399
2443
|
enabled?: boolean;
|
|
2444
|
+
/** Icon shown when collapsed. If no `collapseIcon` is given, this icon rotates 180° when open. */
|
|
2400
2445
|
expandIcon?: react__default.ReactNode;
|
|
2446
|
+
/** Distinct icon shown when expanded. When set, the icons swap instead of rotating. */
|
|
2447
|
+
collapseIcon?: react__default.ReactNode;
|
|
2401
2448
|
expandComponent?: (row: T) => react__default.ReactNode;
|
|
2402
2449
|
}
|
|
2403
2450
|
interface TableProps<T extends Record<string, any> = Record<string, any>> {
|
|
@@ -2414,6 +2461,14 @@ interface TableProps<T extends Record<string, any> = Record<string, any>> {
|
|
|
2414
2461
|
pagination?: PaginationOptions;
|
|
2415
2462
|
expandRow?: ExpandRowOptions<T>;
|
|
2416
2463
|
hasSearch?: boolean;
|
|
2464
|
+
/** Fine-tune search: keys, match mode, debounce, placeholder, custom predicate. */
|
|
2465
|
+
search?: SearchOptions<T>;
|
|
2466
|
+
/** Initial sort (uncontrolled). */
|
|
2467
|
+
defaultSort?: SortState | null;
|
|
2468
|
+
/** Fires when the sort column/direction changes, or clears (`null`). Use for server-side sorting. */
|
|
2469
|
+
onSortChange?: (sort: SortState | null) => void;
|
|
2470
|
+
/** Fires when an editable cell commits a new value. */
|
|
2471
|
+
onCellEdit?: (info: CellEditInfo<T>) => void;
|
|
2417
2472
|
footer?: react__default.ReactNode;
|
|
2418
2473
|
header?: react__default.ReactNode;
|
|
2419
2474
|
/**
|
|
@@ -2472,7 +2527,7 @@ interface TableProps<T extends Record<string, any> = Record<string, any>> {
|
|
|
2472
2527
|
* />
|
|
2473
2528
|
* ```
|
|
2474
2529
|
*/
|
|
2475
|
-
declare function Table<T extends Record<string, any> = Record<string, any>>({ columns, rows, getRowKey, pagination, expandRow, hasSearch, footer, header, loading, loadingRowCount, className, style, }: TableProps<T>): react_jsx_runtime.JSX.Element;
|
|
2530
|
+
declare function Table<T extends Record<string, any> = Record<string, any>>({ columns, rows, getRowKey, pagination, expandRow, hasSearch, search, defaultSort, onSortChange, onCellEdit, footer, header, loading, loadingRowCount, className, style, }: TableProps<T>): react_jsx_runtime.JSX.Element;
|
|
2476
2531
|
|
|
2477
2532
|
interface ThemeSwitchProps {
|
|
2478
2533
|
checked: boolean;
|
package/dist/index.js
CHANGED
|
@@ -5273,22 +5273,78 @@ function createDatasets(rows, perPage) {
|
|
|
5273
5273
|
}
|
|
5274
5274
|
var defaultGetRowKey = (_row, index) => index;
|
|
5275
5275
|
var cellAlign = (align) => align === "left" ? "text-left" : align === "right" ? "text-right" : "text-center";
|
|
5276
|
+
function compareValues(a, b) {
|
|
5277
|
+
if (a == null && b == null) return 0;
|
|
5278
|
+
if (a == null) return -1;
|
|
5279
|
+
if (b == null) return 1;
|
|
5280
|
+
if (typeof a === "number" && typeof b === "number") return a - b;
|
|
5281
|
+
if (a instanceof Date && b instanceof Date) return a.getTime() - b.getTime();
|
|
5282
|
+
if (typeof a === "boolean" && typeof b === "boolean") return a === b ? 0 : a ? 1 : -1;
|
|
5283
|
+
return String(a).localeCompare(String(b), void 0, { numeric: true, sensitivity: "base" });
|
|
5284
|
+
}
|
|
5285
|
+
function SortGlyph({ direction }) {
|
|
5286
|
+
return /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", width: "14", height: "14", fill: "none", "aria-hidden": "true", className: "shrink-0", children: [
|
|
5287
|
+
/* @__PURE__ */ jsx(
|
|
5288
|
+
"path",
|
|
5289
|
+
{
|
|
5290
|
+
d: "M8 11l4-4 4 4",
|
|
5291
|
+
stroke: "currentColor",
|
|
5292
|
+
strokeWidth: 2,
|
|
5293
|
+
strokeLinecap: "round",
|
|
5294
|
+
strokeLinejoin: "round",
|
|
5295
|
+
className: direction === "asc" ? "text-accent" : "text-foreground-muted",
|
|
5296
|
+
opacity: direction === "asc" ? 1 : 0.45
|
|
5297
|
+
}
|
|
5298
|
+
),
|
|
5299
|
+
/* @__PURE__ */ jsx(
|
|
5300
|
+
"path",
|
|
5301
|
+
{
|
|
5302
|
+
d: "M8 13l4 4 4-4",
|
|
5303
|
+
stroke: "currentColor",
|
|
5304
|
+
strokeWidth: 2,
|
|
5305
|
+
strokeLinecap: "round",
|
|
5306
|
+
strokeLinejoin: "round",
|
|
5307
|
+
className: direction === "desc" ? "text-accent" : "text-foreground-muted",
|
|
5308
|
+
opacity: direction === "desc" ? 1 : 0.45
|
|
5309
|
+
}
|
|
5310
|
+
)
|
|
5311
|
+
] });
|
|
5312
|
+
}
|
|
5276
5313
|
function TableHeader({
|
|
5277
5314
|
columns,
|
|
5278
|
-
hasExpand
|
|
5315
|
+
hasExpand,
|
|
5316
|
+
sort,
|
|
5317
|
+
onSort
|
|
5279
5318
|
}) {
|
|
5280
5319
|
return /* @__PURE__ */ jsx("thead", { className: "bg-surface-raised border-b border-border", children: /* @__PURE__ */ jsxs("tr", { children: [
|
|
5281
5320
|
hasExpand && /* @__PURE__ */ jsx("th", { "aria-hidden": "true", className: "w-9" }),
|
|
5282
|
-
columns.map((col) =>
|
|
5283
|
-
|
|
5284
|
-
|
|
5285
|
-
|
|
5286
|
-
|
|
5287
|
-
|
|
5288
|
-
|
|
5289
|
-
|
|
5290
|
-
|
|
5291
|
-
|
|
5321
|
+
columns.map((col) => {
|
|
5322
|
+
const active = sort?.key === col.keyBind;
|
|
5323
|
+
const dir = active ? sort.direction : void 0;
|
|
5324
|
+
const justify = col.align === "left" ? "justify-start" : col.align === "right" ? "justify-end" : "justify-center";
|
|
5325
|
+
return /* @__PURE__ */ jsx(
|
|
5326
|
+
"th",
|
|
5327
|
+
{
|
|
5328
|
+
scope: "col",
|
|
5329
|
+
"aria-sort": col.sortable ? active ? dir === "asc" ? "ascending" : "descending" : "none" : void 0,
|
|
5330
|
+
className: `${cellAlign(col.align)} text-sm font-semibold text-foreground py-3 px-3`,
|
|
5331
|
+
style: col.width != null ? { width: col.width } : void 0,
|
|
5332
|
+
children: col.sortable ? /* @__PURE__ */ jsxs(
|
|
5333
|
+
"button",
|
|
5334
|
+
{
|
|
5335
|
+
type: "button",
|
|
5336
|
+
onClick: () => onSort(col),
|
|
5337
|
+
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"}`,
|
|
5338
|
+
children: [
|
|
5339
|
+
/* @__PURE__ */ jsx("span", { children: col.label }),
|
|
5340
|
+
/* @__PURE__ */ jsx(SortGlyph, { direction: dir })
|
|
5341
|
+
]
|
|
5342
|
+
}
|
|
5343
|
+
) : col.label
|
|
5344
|
+
},
|
|
5345
|
+
col.key
|
|
5346
|
+
);
|
|
5347
|
+
})
|
|
5292
5348
|
] }) });
|
|
5293
5349
|
}
|
|
5294
5350
|
var DefaultExpandIcon = /* @__PURE__ */ jsx(
|
|
@@ -5296,24 +5352,66 @@ var DefaultExpandIcon = /* @__PURE__ */ jsx(
|
|
|
5296
5352
|
{
|
|
5297
5353
|
xmlns: "http://www.w3.org/2000/svg",
|
|
5298
5354
|
viewBox: "0 0 24 24",
|
|
5299
|
-
fill: "
|
|
5300
|
-
|
|
5355
|
+
fill: "none",
|
|
5356
|
+
stroke: "currentColor",
|
|
5357
|
+
strokeWidth: 2,
|
|
5358
|
+
className: "w-4 h-4 text-foreground-muted",
|
|
5301
5359
|
"aria-hidden": "true",
|
|
5302
|
-
children: /* @__PURE__ */ jsx(
|
|
5303
|
-
|
|
5360
|
+
children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M19.5 8.25l-7.5 7.5-7.5-7.5" })
|
|
5361
|
+
}
|
|
5362
|
+
);
|
|
5363
|
+
function EditableCell({
|
|
5364
|
+
col,
|
|
5365
|
+
row,
|
|
5366
|
+
rowIndex,
|
|
5367
|
+
onCellEdit
|
|
5368
|
+
}) {
|
|
5369
|
+
const [editing, setEditing] = useState(false);
|
|
5370
|
+
const value = row[col.keyBind];
|
|
5371
|
+
const commit = (next) => {
|
|
5372
|
+
setEditing(false);
|
|
5373
|
+
onCellEdit?.({ row, key: col.keyBind, value: next, rowIndex });
|
|
5374
|
+
};
|
|
5375
|
+
const cancel = () => setEditing(false);
|
|
5376
|
+
if (editing) {
|
|
5377
|
+
if (col.editor) return /* @__PURE__ */ jsx(Fragment, { children: col.editor({ value, row, commit, cancel }) });
|
|
5378
|
+
return /* @__PURE__ */ jsx(
|
|
5379
|
+
"input",
|
|
5304
5380
|
{
|
|
5305
|
-
|
|
5306
|
-
|
|
5307
|
-
|
|
5381
|
+
autoFocus: true,
|
|
5382
|
+
defaultValue: value == null ? "" : String(value),
|
|
5383
|
+
onBlur: (e) => commit(e.target.value),
|
|
5384
|
+
onKeyDown: (e) => {
|
|
5385
|
+
if (e.key === "Enter") {
|
|
5386
|
+
e.preventDefault();
|
|
5387
|
+
commit(e.target.value);
|
|
5388
|
+
} else if (e.key === "Escape") {
|
|
5389
|
+
e.preventDefault();
|
|
5390
|
+
cancel();
|
|
5391
|
+
}
|
|
5392
|
+
},
|
|
5393
|
+
"aria-label": `Edit ${typeof col.label === "string" ? col.label : col.keyBind}`,
|
|
5394
|
+
className: "w-full rounded border border-accent bg-surface px-2 py-1 text-sm text-foreground outline-none"
|
|
5308
5395
|
}
|
|
5309
|
-
)
|
|
5396
|
+
);
|
|
5310
5397
|
}
|
|
5311
|
-
|
|
5398
|
+
return /* @__PURE__ */ jsx(
|
|
5399
|
+
"button",
|
|
5400
|
+
{
|
|
5401
|
+
type: "button",
|
|
5402
|
+
onClick: () => setEditing(true),
|
|
5403
|
+
title: "Click to edit",
|
|
5404
|
+
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`,
|
|
5405
|
+
children: col.component ? col.component(value, row) : value
|
|
5406
|
+
}
|
|
5407
|
+
);
|
|
5408
|
+
}
|
|
5312
5409
|
function TableBody({
|
|
5313
5410
|
columns,
|
|
5314
5411
|
rows,
|
|
5315
5412
|
expandRow,
|
|
5316
|
-
getRowKey
|
|
5413
|
+
getRowKey,
|
|
5414
|
+
onCellEdit
|
|
5317
5415
|
}) {
|
|
5318
5416
|
const [expanded, setExpanded] = useState(() => /* @__PURE__ */ new Set());
|
|
5319
5417
|
const reduced = useReducedMotion();
|
|
@@ -5343,15 +5441,15 @@ function TableBody({
|
|
|
5343
5441
|
onClick: () => toggleRow(rowKey),
|
|
5344
5442
|
"aria-expanded": isExpanded,
|
|
5345
5443
|
"aria-label": isExpanded ? "Collapse row" : "Expand row",
|
|
5346
|
-
className: `w-9 h-9 inline-flex items-center justify-center rounded-md hover:bg-
|
|
5347
|
-
children: expandRow.expandIcon ?? DefaultExpandIcon
|
|
5444
|
+
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" : ""}`,
|
|
5445
|
+
children: isExpanded ? expandRow.collapseIcon ?? expandRow.expandIcon ?? DefaultExpandIcon : expandRow.expandIcon ?? DefaultExpandIcon
|
|
5348
5446
|
}
|
|
5349
5447
|
) }),
|
|
5350
5448
|
columns.map((col) => /* @__PURE__ */ jsx(
|
|
5351
5449
|
"td",
|
|
5352
5450
|
{
|
|
5353
5451
|
className: `${cellAlign(col.align)} text-sm text-foreground py-2 px-3 align-middle`,
|
|
5354
|
-
children: col.component ? col.component(row[col.keyBind], row) : row[col.keyBind]
|
|
5452
|
+
children: col.editable ? /* @__PURE__ */ jsx(EditableCell, { col, row, rowIndex: i, onCellEdit }) : col.component ? col.component(row[col.keyBind], row) : row[col.keyBind]
|
|
5355
5453
|
},
|
|
5356
5454
|
col.key
|
|
5357
5455
|
))
|
|
@@ -5445,6 +5543,10 @@ function Table({
|
|
|
5445
5543
|
pagination = DEFAULT_PAGINATION,
|
|
5446
5544
|
expandRow = DEFAULT_EXPAND,
|
|
5447
5545
|
hasSearch = true,
|
|
5546
|
+
search,
|
|
5547
|
+
defaultSort = null,
|
|
5548
|
+
onSortChange,
|
|
5549
|
+
onCellEdit,
|
|
5448
5550
|
footer = null,
|
|
5449
5551
|
header = null,
|
|
5450
5552
|
loading = false,
|
|
@@ -5458,20 +5560,54 @@ function Table({
|
|
|
5458
5560
|
typeof pagination.perPage === "number" ? pagination.perPage : 15
|
|
5459
5561
|
);
|
|
5460
5562
|
const [activePage, setActivePage] = useState(0);
|
|
5563
|
+
const [sortState, setSortState] = useState(defaultSort);
|
|
5461
5564
|
const isServerSide = !!(pagination.enabled && pagination.serverSide);
|
|
5565
|
+
const handleSort = (col) => {
|
|
5566
|
+
const key = col.keyBind;
|
|
5567
|
+
let next;
|
|
5568
|
+
if (!sortState || sortState.key !== key) next = { key, direction: "asc" };
|
|
5569
|
+
else if (sortState.direction === "asc") next = { key, direction: "desc" };
|
|
5570
|
+
else next = null;
|
|
5571
|
+
setSortState(next);
|
|
5572
|
+
onSortChange?.(next);
|
|
5573
|
+
};
|
|
5574
|
+
const debounceMs = search?.debounceMs ?? 0;
|
|
5575
|
+
const [debouncedTerm, setDebouncedTerm] = useState("");
|
|
5576
|
+
useEffect(() => {
|
|
5577
|
+
if (debounceMs <= 0) {
|
|
5578
|
+
setDebouncedTerm(searchTerm);
|
|
5579
|
+
return;
|
|
5580
|
+
}
|
|
5581
|
+
const t = setTimeout(() => setDebouncedTerm(searchTerm), debounceMs);
|
|
5582
|
+
return () => clearTimeout(t);
|
|
5583
|
+
}, [searchTerm, debounceMs]);
|
|
5584
|
+
const term = debounceMs > 0 ? debouncedTerm : searchTerm;
|
|
5462
5585
|
const filteredRows = useMemo(() => {
|
|
5463
|
-
if (isServerSide || !
|
|
5464
|
-
|
|
5465
|
-
|
|
5466
|
-
|
|
5467
|
-
|
|
5468
|
-
|
|
5469
|
-
)
|
|
5470
|
-
|
|
5586
|
+
if (isServerSide || !term) return rows;
|
|
5587
|
+
if (search?.predicate) return rows.filter((row) => search.predicate(row, term));
|
|
5588
|
+
const cs = !!search?.caseSensitive;
|
|
5589
|
+
const needle = cs ? term : term.toLowerCase();
|
|
5590
|
+
const mode = search?.matchMode ?? "contains";
|
|
5591
|
+
const keys = search?.keys;
|
|
5592
|
+
const test = (raw) => {
|
|
5593
|
+
if (raw == null) return false;
|
|
5594
|
+
const s = cs ? String(raw) : String(raw).toLowerCase();
|
|
5595
|
+
return mode === "startsWith" ? s.startsWith(needle) : mode === "equals" ? s === needle : s.includes(needle);
|
|
5596
|
+
};
|
|
5597
|
+
return rows.filter((row) => keys ? keys.some((k) => test(row[k])) : Object.values(row).some(test));
|
|
5598
|
+
}, [rows, term, isServerSide, search?.predicate, search?.caseSensitive, search?.matchMode, search?.keys]);
|
|
5599
|
+
const sortedRows = useMemo(() => {
|
|
5600
|
+
if (isServerSide || !sortState) return filteredRows;
|
|
5601
|
+
const col = columns.find((c) => c.keyBind === sortState.key);
|
|
5602
|
+
const accessor = col?.sortAccessor ?? ((r) => r[sortState.key]);
|
|
5603
|
+
const out = [...filteredRows].sort((a, b) => compareValues(accessor(a), accessor(b)));
|
|
5604
|
+
if (sortState.direction === "desc") out.reverse();
|
|
5605
|
+
return out;
|
|
5606
|
+
}, [filteredRows, sortState, isServerSide, columns]);
|
|
5471
5607
|
const datasets = useMemo(() => {
|
|
5472
5608
|
if (isServerSide) return [rows];
|
|
5473
|
-
return createDatasets(
|
|
5474
|
-
}, [
|
|
5609
|
+
return createDatasets(sortedRows, pagination.enabled ? perPage : null);
|
|
5610
|
+
}, [sortedRows, perPage, pagination.enabled, isServerSide, rows]);
|
|
5475
5611
|
const MAX_PAGE = useMemo(() => {
|
|
5476
5612
|
if (isServerSide && typeof pagination.maxPage === "number") return Math.max(0, pagination.maxPage);
|
|
5477
5613
|
if (isServerSide && typeof pagination.totalCount === "number")
|
|
@@ -5510,32 +5646,36 @@ function Table({
|
|
|
5510
5646
|
}
|
|
5511
5647
|
setActivePage(newPage);
|
|
5512
5648
|
};
|
|
5649
|
+
const pagPos = pagination.position ?? "top";
|
|
5650
|
+
const showTopPager = !!pagination.enabled && (pagPos === "top" || pagPos === "both");
|
|
5651
|
+
const showBottomPager = !!pagination.enabled && (pagPos === "bottom" || pagPos === "both");
|
|
5652
|
+
const pager = /* @__PURE__ */ jsx(
|
|
5653
|
+
Pagination,
|
|
5654
|
+
{
|
|
5655
|
+
activePage,
|
|
5656
|
+
onPageChange: handlePageChange,
|
|
5657
|
+
maxPage: MAX_PAGE,
|
|
5658
|
+
onPerPageChange: onPaginationChange,
|
|
5659
|
+
options: pagination,
|
|
5660
|
+
serverSide: isServerSide
|
|
5661
|
+
}
|
|
5662
|
+
);
|
|
5513
5663
|
return /* @__PURE__ */ jsxs("div", { className: `w-full h-max rounded-lg ${className}`.trim(), style, children: [
|
|
5514
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-2", children: [
|
|
5515
|
-
hasSearch
|
|
5664
|
+
(hasSearch || showTopPager) && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3 mb-2", children: [
|
|
5665
|
+
hasSearch ? /* @__PURE__ */ jsx(
|
|
5516
5666
|
SearchInput_default,
|
|
5517
5667
|
{
|
|
5518
5668
|
ref: searchRef,
|
|
5519
5669
|
value: searchTerm,
|
|
5520
5670
|
onChange: onSearchChange,
|
|
5521
|
-
placeholder: "Search term..."
|
|
5671
|
+
placeholder: search?.placeholder ?? "Search term..."
|
|
5522
5672
|
}
|
|
5523
|
-
),
|
|
5524
|
-
|
|
5525
|
-
Pagination,
|
|
5526
|
-
{
|
|
5527
|
-
activePage,
|
|
5528
|
-
onPageChange: handlePageChange,
|
|
5529
|
-
maxPage: MAX_PAGE,
|
|
5530
|
-
onPerPageChange: onPaginationChange,
|
|
5531
|
-
options: pagination,
|
|
5532
|
-
serverSide: isServerSide
|
|
5533
|
-
}
|
|
5534
|
-
)
|
|
5673
|
+
) : /* @__PURE__ */ jsx("span", {}),
|
|
5674
|
+
showTopPager && pager
|
|
5535
5675
|
] }),
|
|
5536
5676
|
/* @__PURE__ */ jsx("div", { children: header }),
|
|
5537
5677
|
/* @__PURE__ */ jsx("div", { className: "overflow-x-auto rounded-lg", children: /* @__PURE__ */ jsxs("table", { className: "w-full border-collapse", "aria-busy": loading || void 0, children: [
|
|
5538
|
-
/* @__PURE__ */ jsx(TableHeader, { columns, hasExpand: !!expandRow.enabled }),
|
|
5678
|
+
/* @__PURE__ */ jsx(TableHeader, { columns, hasExpand: !!expandRow.enabled, sort: sortState, onSort: handleSort }),
|
|
5539
5679
|
loading ? /* @__PURE__ */ jsx(
|
|
5540
5680
|
TableSkeletonBody,
|
|
5541
5681
|
{
|
|
@@ -5549,10 +5689,12 @@ function Table({
|
|
|
5549
5689
|
columns,
|
|
5550
5690
|
rows: currentPageRows,
|
|
5551
5691
|
expandRow,
|
|
5552
|
-
getRowKey
|
|
5692
|
+
getRowKey,
|
|
5693
|
+
onCellEdit
|
|
5553
5694
|
}
|
|
5554
5695
|
)
|
|
5555
5696
|
] }) }),
|
|
5697
|
+
showBottomPager && /* @__PURE__ */ jsx("div", { className: "mt-2 flex justify-end", children: pager }),
|
|
5556
5698
|
/* @__PURE__ */ jsx("div", { children: footer })
|
|
5557
5699
|
] });
|
|
5558
5700
|
}
|