pika-ux 1.0.1 → 1.0.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pika-ux",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  "./pika/*": "./src/pika/*",
@@ -21,9 +21,11 @@
21
21
 
22
22
  /** When provided, this is the title of the button */
23
23
  title?: string;
24
+
25
+ size?: 'small' | 'medium';
24
26
  }
25
27
 
26
- const { children, value: propValue, embedded = false, truncateAfter = 0, showTextAsLink = false, linkCallbackFn, title }: Props = $props();
28
+ const { children, value: propValue, embedded = false, truncateAfter = 0, showTextAsLink = false, linkCallbackFn, title, size = 'medium' }: Props = $props();
27
29
 
28
30
  let hiddenRef = $state<HTMLElement>() as HTMLElement;
29
31
  let value: string | undefined = $state(undefined);
@@ -118,16 +120,16 @@
118
120
 
119
121
  <span class="inline-flex w-fit items-center {embedded ? '' : 'border border-gray-200 rounded-sm'}">
120
122
  {#if showTextAsLink}
121
- <Button class="p-0" variant="link" onclick={() => linkCallbackFn?.()}>{buttonTitle}</Button>
123
+ <Button class="p-0 {size === 'small' ? 'text-xs' : ''}" variant="link" onclick={() => linkCallbackFn?.()}>{buttonTitle}</Button>
122
124
  {:else}
123
- <span class={embedded ? '' : 'border-r border-gray-200 px-2'}>{buttonTitle}</span>
125
+ <span class="{embedded ? '' : 'border-r border-gray-200 px-2'} {size === 'small' ? 'text-xs' : ''}">{buttonTitle}</span>
124
126
  {/if}
125
- <span class="w-6 h-6 flex items-center justify-center">
127
+ <span class="{size === 'small' ? 'w-4 h-4 ml-1' : 'w-6 h-6'} flex items-center justify-center">
126
128
  {#if showCheckmark}
127
- <Check class="w-3.5 h-3.5 text-green-500" />
129
+ <Check class="{size === 'small' ? 'w-2.5 h-2.5' : 'w-3.5 h-3.5'} text-green-500" />
128
130
  {:else}
129
131
  <Button variant="ghost" class="h-full w-full min-h-0 p-0 ml-1 rounded-none hover:border-blue-100 hover:border hover:rounded-sm" onclick={copy} disabled={!value}>
130
- <Copy class="w-3.5 h-3.5 text-gray-400" />
132
+ <Copy class="{size === 'small' ? 'w-2.5 h-2.5' : 'w-3.5 h-3.5'} text-gray-400" />
131
133
  </Button>
132
134
  {/if}
133
135
  </span>
@@ -20,22 +20,48 @@ PikaTablePagination - Pagination controls for PikaTable
20
20
  table: Table<TData>;
21
21
  serverSide: ServerSideConfig;
22
22
  showRowsPerPage?: boolean;
23
+ globalFilterActive?: boolean;
23
24
  }
24
25
 
25
- let { table, serverSide, showRowsPerPage = true }: Props = $props();
26
+ let { table, serverSide, showRowsPerPage = true, globalFilterActive = false }: Props = $props();
26
27
 
27
28
  // For cursor-based pagination, we can't jump to arbitrary pages
28
29
  const isCursorBased = $derived(serverSide?.paginationMode === 'cursor');
30
+
31
+ // Calculate counts for display
32
+ const totalLoadedRows = $derived(table.getCoreRowModel().rows.length);
33
+ const visibleRows = $derived(table.getRowModel().rows.length);
34
+ const selectedRows = $derived(table.getFilteredSelectedRowModel().rows.length);
29
35
  </script>
30
36
 
31
37
  <div class="flex items-center justify-between px-2">
32
- <div class="text-muted-foreground flex-1 text-sm">
33
- {#if serverSide?.tableState?.totalRecords !== undefined}
34
- {table.getFilteredSelectedRowModel().rows.length} of
35
- {serverSide.tableState.totalRecords} total row(s) selected.
38
+ <div class="flex-1 text-sm">
39
+ {#if globalFilterActive && visibleRows < totalLoadedRows}
40
+ <!-- Client-side filtering active -->
41
+ <div class="flex items-center gap-2">
42
+ <span class="text-sm font-medium text-blue-700">
43
+ Showing {visibleRows} of {totalLoadedRows} loaded results
44
+ </span>
45
+ {#if selectedRows > 0}
46
+ <span class="text-muted-foreground text-xs">
47
+ ({selectedRows} selected)
48
+ </span>
49
+ {/if}
50
+ </div>
51
+ {:else if selectedRows > 0}
52
+ <!-- No filtering, just show selection -->
53
+ <span class="text-muted-foreground">
54
+ {#if serverSide?.tableState?.totalRecords !== undefined}
55
+ {selectedRows} of {serverSide.tableState.totalRecords} total row(s) selected.
56
+ {:else}
57
+ {selectedRows} of {visibleRows} row(s) selected.
58
+ {/if}
59
+ </span>
36
60
  {:else}
37
- {table.getFilteredSelectedRowModel().rows.length} of
38
- {table.getFilteredRowModel().rows.length} row(s) selected.
61
+ <!-- No filtering, no selection -->
62
+ <span class="text-muted-foreground">
63
+ {totalLoadedRows} row(s) loaded
64
+ </span>
39
65
  {/if}
40
66
  </div>
41
67
  <div class="flex items-center space-x-8">
@@ -172,7 +172,9 @@ PikaTable - A reusable table component with server-side pagination, sorting, and
172
172
  return !!serverSideConfig;
173
173
  },
174
174
  get manualFiltering() {
175
- return !!serverSideConfig;
175
+ // CRITICAL: If using client-side global filter, we must disable manualFiltering
176
+ // Otherwise TanStack skips ALL filtering logic before checking manualGlobalFilter
177
+ return serverSideConfig ? !serverSideConfig.clientSideGlobalFilter : false;
176
178
  },
177
179
  get manualPagination() {
178
180
  return !!serverSideConfig;
@@ -183,13 +185,19 @@ PikaTable - A reusable table component with server-side pagination, sorting, and
183
185
 
184
186
  const tableState = serverSideConfig?.tableState;
185
187
 
188
+ // For server-side pagination, use the pageSize from tableState if available
189
+ const effectivePageSize = tableState?.pageSize ?? pageSize;
190
+
186
191
  // Calculate pageCount if totalRecords is available, regardless of pagination mode
187
- if (tableState?.totalRecords !== undefined && pageSize > 0) {
188
- return Math.ceil(tableState.totalRecords / pageSize);
192
+ if (tableState?.totalRecords !== undefined && effectivePageSize > 0) {
193
+ return Math.ceil(tableState.totalRecords / effectivePageSize);
189
194
  }
190
195
  // For cursor-based pagination without totalRecords, we don't know the total
191
196
  if (serverState.paginationMode === 'cursor') {
192
- return -1; // Unknown page count for cursor pagination without totalRecords
197
+ // For cursor pagination, determine page count based on hasNextPage
198
+ // If we're on page 0 and hasNextPage is false, we have 1 page
199
+ // If hasNextPage is true, we have at least 2 pages
200
+ return tableState?.hasNextPage ? pageIndex + 2 : pageIndex + 1;
193
201
  }
194
202
  return undefined; // Unknown page count
195
203
  },
@@ -251,17 +259,21 @@ PikaTable - A reusable table component with server-side pagination, sorting, and
251
259
  if (globalFilterProps?.showGlobalFilter) {
252
260
  globalFilterProps.globalFilterValue = value;
253
261
 
254
- // Trigger server request for server-side tables
255
- if (serverSideConfig) {
262
+ // Only trigger server request if global filter is server-side (not client-side)
263
+ if (serverSideConfig && !serverSideConfig.clientSideGlobalFilter) {
256
264
  triggerServerRequest();
257
265
  }
266
+ // Otherwise, global filter will work client-side automatically via getFilteredRowModel
258
267
  }
259
268
  },
260
269
 
261
270
  // === ROW MODELS ===
262
271
  getCoreRowModel: getCoreRowModel(),
263
272
  ...(serverSideConfig
264
- ? {}
273
+ ? {
274
+ // Include filtered row model ONLY if using client-side global filter
275
+ ...(serverSideConfig.clientSideGlobalFilter ? { getFilteredRowModel: getFilteredRowModel() } : {})
276
+ }
265
277
  : {
266
278
  getFilteredRowModel: getFilteredRowModel(),
267
279
  getPaginationRowModel: getPaginationRowModel(),
@@ -278,7 +290,12 @@ PikaTable - A reusable table component with server-side pagination, sorting, and
278
290
  </div>
279
291
  {#if paginationPlacement === 'top' || paginationPlacement === 'both'}
280
292
  <div class="mt-2 pb-1">
281
- <TablePagination {table} serverSide={serverSideConfig} {showRowsPerPage} />
293
+ <TablePagination
294
+ {table}
295
+ serverSide={serverSideConfig}
296
+ {showRowsPerPage}
297
+ globalFilterActive={!!(serverSideConfig?.clientSideGlobalFilter && globalFilterProps?.globalFilterValue)}
298
+ />
282
299
  </div>
283
300
  {/if}
284
301
  <div class="rounded-md border h-full flex flex-col overflow-y-auto">
@@ -315,7 +332,12 @@ PikaTable - A reusable table component with server-side pagination, sorting, and
315
332
  </div>
316
333
  {#if paginationPlacement === 'bottom' || paginationPlacement === 'both'}
317
334
  <div class="mt-2">
318
- <TablePagination {table} serverSide={serverSideConfig} {showRowsPerPage} />
335
+ <TablePagination
336
+ {table}
337
+ serverSide={serverSideConfig}
338
+ {showRowsPerPage}
339
+ globalFilterActive={!!(serverSideConfig?.clientSideGlobalFilter && globalFilterProps?.globalFilterValue)}
340
+ />
319
341
  </div>
320
342
  {/if}
321
343
  </div>
@@ -111,6 +111,14 @@ export interface ServerSideConfig {
111
111
  // === ERROR HANDLING ===
112
112
  onError?: (error: string) => void;
113
113
 
114
+ // === CLIENT-SIDE FILTERING ===
115
+ /**
116
+ * When true, enables client-side global filter even in server-side mode.
117
+ * This allows users to quickly filter already-loaded results without triggering server requests.
118
+ * Column filters still work server-side, but the global filter searches loaded data only.
119
+ */
120
+ clientSideGlobalFilter?: boolean;
121
+
114
122
  // === DYNAMIC TABLE STATE ===
115
123
  tableState: ServerSideTableState;
116
124
  }