@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/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) => /* @__PURE__ */ jsx(
5283
- "th",
5284
- {
5285
- scope: "col",
5286
- className: `${cellAlign(col.align)} text-sm font-semibold text-foreground py-3 px-3`,
5287
- style: col.width != null ? { width: col.width } : void 0,
5288
- children: col.label
5289
- },
5290
- col.key
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: "currentColor",
5300
- className: "w-5 h-5 text-foreground-muted",
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
- "path",
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
- fillRule: "evenodd",
5306
- d: "M12 2.25c-5.385 0-9.75 4.365-9.75 9.75s4.365 9.75 9.75 9.75 9.75-4.365 9.75-9.75S17.385 2.25 12 2.25zM12.75 9a.75.75 0 00-1.5 0v2.25H9a.75.75 0 000 1.5h2.25V15a.75.75 0 001.5 0v-2.25H15a.75.75 0 000-1.5h-2.25V9z",
5307
- clipRule: "evenodd"
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-surface/80 transition-transform duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-accent ${isExpanded ? "rotate-180" : ""}`,
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 || !searchTerm) return rows;
5464
- const term = searchTerm.toLowerCase();
5465
- return rows.filter(
5466
- (row) => Object.values(row).some(
5467
- (v) => v != null && String(v).toLowerCase().includes(term)
5468
- )
5469
- );
5470
- }, [rows, searchTerm, isServerSide]);
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(filteredRows, pagination.enabled ? perPage : null);
5474
- }, [filteredRows, perPage, pagination.enabled, isServerSide, rows]);
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 && /* @__PURE__ */ jsx(
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
- pagination.enabled && /* @__PURE__ */ jsx(
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
  }