@invopop/popui 0.1.4-beta.1 → 0.1.4-beta.2

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.
@@ -10,19 +10,21 @@ export function createSelectionColumn() {
10
10
  checked: table.getIsAllPageRowsSelected(),
11
11
  onchange: (value) => table.toggleAllPageRowsSelected(value),
12
12
  indeterminate: table.getIsSomePageRowsSelected() && !table.getIsAllPageRowsSelected(),
13
- 'aria-label': 'Select all'
13
+ 'aria-label': 'Select all',
14
+ onclick: (e) => e.stopPropagation()
14
15
  }),
15
16
  cell: ({ row }) => renderComponent(InputCheckbox, {
16
17
  checked: row.getIsSelected(),
17
18
  onchange: (value) => row.toggleSelected(value),
18
- 'aria-label': 'Select row'
19
+ 'aria-label': 'Select row',
20
+ onclick: (e) => e.stopPropagation()
19
21
  }),
20
22
  enableSorting: false,
21
23
  enableHiding: false,
22
24
  enableResizing: false,
23
- size: 40,
24
- minSize: 40,
25
- maxSize: 40
25
+ size: 52,
26
+ minSize: 52,
27
+ maxSize: 52
26
28
  };
27
29
  }
28
30
  /**
@@ -33,8 +35,8 @@ export function createActionsColumn(rowActionsSnippet) {
33
35
  id: 'actions',
34
36
  cell: ({ row }) => renderSnippet(rowActionsSnippet, { row }),
35
37
  enableResizing: false,
36
- size: 44,
37
- minSize: 44,
38
- maxSize: 44
38
+ size: 56,
39
+ minSize: 56,
40
+ maxSize: 56
39
41
  };
40
42
  }
@@ -6,7 +6,7 @@
6
6
  let { table, filters }: { table: Table<TData>; filters?: Snippet } = $props()
7
7
  </script>
8
8
 
9
- <div class="flex items-center justify-between">
9
+ <div class="flex items-center justify-between px-6 py-4">
10
10
  {#if filters}
11
11
  <div class="flex-1">
12
12
  {@render filters()}
@@ -47,6 +47,7 @@ export interface DataTableProps<TData> {
47
47
  disableSelection?: boolean;
48
48
  disablePagination?: boolean;
49
49
  rowActions?: TableAction[];
50
+ getRowActions?: (row: TData) => TableAction[];
50
51
  onRowAction?: (action: AnyProp, row: TData) => void;
51
52
  initialPageSize?: number;
52
53
  pageSizeOptions?: number[];
@@ -33,6 +33,7 @@
33
33
  disableSelection = false,
34
34
  disablePagination = false,
35
35
  rowActions = [],
36
+ getRowActions,
36
37
  onRowAction,
37
38
  initialPageSize = 10,
38
39
  emptyState = {
@@ -70,7 +71,7 @@
70
71
 
71
72
  // Build TanStack columns from config
72
73
  const columns = $derived.by(() =>
73
- buildColumns<TData>(columnConfig, enableSelection, RowActions, rowActions.length > 0)
74
+ buildColumns<TData>(columnConfig, enableSelection, RowActions, getRowActions !== undefined || rowActions.length > 0)
74
75
  )
75
76
 
76
77
  // Calculate initial column sizes based on available width
@@ -129,8 +130,8 @@
129
130
  })}
130
131
  <div
131
132
  class={cn(
132
- 'h-10 flex items-center px-3 relative group-hover/row:bg-background-default-secondary group-data-[state=selected]/row:bg-background-selected',
133
- align === 'right' ? 'justify-end' : ''
133
+ 'h-10 flex items-center relative group-hover/row:bg-background-default-secondary group-data-[state=selected]/row:bg-background-selected',
134
+ align === 'right' ? 'justify-end pl-3 pr-6' : 'pl-6 pr-3'
134
135
  )}
135
136
  >
136
137
  <div class="relative z-10">
@@ -141,7 +142,7 @@
141
142
 
142
143
  {#snippet RowActions({ row }: { row: Row<TData> })}
143
144
  <BaseTableActions
144
- actions={rowActions}
145
+ actions={getRowActions ? getRowActions(row.original) : rowActions}
145
146
  onclick={(action) => {
146
147
  if (onRowAction) {
147
148
  onRowAction(action, row.original)
@@ -188,128 +189,126 @@
188
189
  {/if}
189
190
  {/snippet}
190
191
 
191
- <div class="flex flex-col gap-4">
192
+ <div class="flex flex-col h-screen">
192
193
  <DataTableToolbar {table} {filters} />
193
- <div class="flex flex-col gap-[5px]">
194
- <div bind:this={containerRef} class="relative bg-background">
195
- <div class="overflow-x-auto relative z-10">
196
- <Table.Root>
197
- <Table.Header>
198
- {#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
199
- <Table.Row class="hover:!bg-transparent border-b border-border">
200
- {#each headerGroup.headers as header, index (header.id)}
201
- {@const isLastScrollable = index === headerGroup.headers.length - 2}
202
- <Table.Head
203
- colspan={header.colSpan}
204
- style={getHeaderStyle(header, isLastScrollable)}
205
- class={getHeaderClasses(header, isLastScrollable)}
206
- >
207
- {#if !header.isPlaceholder}
208
- {#if typeof header.column.columnDef.header === 'string'}
209
- {@render ColumnHeader({
210
- column: header.column as Column<TData>,
211
- title: header.column.columnDef.header as string
212
- })}
213
- {:else}
214
- <FlexRender
215
- content={header.column.columnDef.header}
216
- context={header.getContext()}
217
- />
218
- {/if}
194
+ <div class="flex-1 overflow-hidden flex flex-col">
195
+ <div bind:this={containerRef} class="relative bg-background flex-1 overflow-auto">
196
+ <Table.Root>
197
+ <Table.Header>
198
+ {#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
199
+ <Table.Row class="hover:!bg-transparent border-b border-border">
200
+ {#each headerGroup.headers as header, index (header.id)}
201
+ {@const isLastScrollable = index === headerGroup.headers.length - 2}
202
+ <Table.Head
203
+ colspan={header.colSpan}
204
+ style={getHeaderStyle(header, isLastScrollable)}
205
+ class={getHeaderClasses(header, isLastScrollable)}
206
+ >
207
+ {#if !header.isPlaceholder}
208
+ {#if typeof header.column.columnDef.header === 'string'}
209
+ {@render ColumnHeader({
210
+ column: header.column as Column<TData>,
211
+ title: header.column.columnDef.header as string
212
+ })}
213
+ {:else}
214
+ <FlexRender
215
+ content={header.column.columnDef.header}
216
+ context={header.getContext()}
217
+ />
219
218
  {/if}
220
- {#if header.column.getCanResize()}
221
- <!-- Always visible vertical border -->
219
+ {/if}
220
+ {#if header.column.getCanResize()}
221
+ <!-- Always visible vertical border -->
222
+ <div
223
+ class={cn(
224
+ 'absolute right-0 top-1/2 -translate-y-1/2 h-3 w-px bg-background-default-tertiary',
225
+ header.column.getIsResizing() && 'opacity-0'
226
+ )}
227
+ ></div>
228
+ <!-- Resize handler (larger interactive area, enhanced on hover) -->
229
+ <div
230
+ role="button"
231
+ tabindex="0"
232
+ aria-label="Resize column"
233
+ class="absolute right-0 top-0 h-full w-3 cursor-col-resize select-none touch-none group -mr-1.5"
234
+ onmousedown={header.getResizeHandler()}
235
+ ontouchstart={header.getResizeHandler()}
236
+ >
222
237
  <div
223
238
  class={cn(
224
- 'absolute right-0 top-1/2 -translate-y-1/2 h-3 w-px bg-background-default-tertiary',
225
- header.column.getIsResizing() && 'opacity-0'
239
+ 'absolute right-1.5 top-0 h-full w-0.5 bg-border-default-secondary transition-opacity opacity-0',
240
+ !header.column.getIsResizing() && 'group-hover:opacity-100'
226
241
  )}
227
242
  ></div>
228
- <!-- Resize handler (larger interactive area, enhanced on hover) -->
229
- <div
230
- role="button"
231
- tabindex="0"
232
- aria-label="Resize column"
233
- class="absolute right-0 top-0 h-full w-3 cursor-col-resize select-none touch-none group -mr-1.5"
234
- onmousedown={header.getResizeHandler()}
235
- ontouchstart={header.getResizeHandler()}
236
- >
237
- <div
238
- class={cn(
239
- 'absolute right-1.5 top-0 h-full w-0.5 bg-border-default-secondary transition-opacity opacity-0',
240
- !header.column.getIsResizing() && 'group-hover:opacity-100'
241
- )}
242
- ></div>
243
- </div>
244
- {/if}
245
- </Table.Head>
246
- {/each}
247
- </Table.Row>
248
- {/each}
249
- </Table.Header>
250
- <Table.Body>
251
- {#each table.getRowModel().rows as row (row.id)}
252
- <Table.Row
253
- data-state={row.getIsSelected() ? 'selected' : undefined}
254
- class="border-b border-border"
255
- onclick={() => onRowClick?.(row.original as TData)}
256
- >
257
- {#each row.getVisibleCells() as cell, index (cell.id)}
258
- {@const isLastScrollable = index === row.getVisibleCells().length - 2}
259
- {@const visibleCells = row.getVisibleCells()}
260
- {@const firstDataColumnIndex = visibleCells.findIndex(
261
- (c) => c.column.id !== 'select' && c.column.id !== 'actions'
262
- )}
263
- {@const isFirstDataColumn = index === firstDataColumnIndex}
264
- <Table.Cell
265
- style={getCellStyle(cell, isLastScrollable)}
266
- class={getCellClasses(cell, isLastScrollable, isFirstDataColumn)}
267
- >
268
- {#if cell.column.id === 'actions'}
269
- {@render StickyCellWrapper({
270
- align: 'right',
271
- children: CellContent
272
- })}
273
- {#snippet CellContent()}
274
- <FlexRender
275
- content={cell.column.columnDef.cell}
276
- context={cell.getContext()}
277
- />
278
- {/snippet}
279
- {:else if cell.column.id === 'select'}
280
- {@render StickyCellWrapper({
281
- align: 'left',
282
- children: CellContent
283
- })}
284
- {#snippet CellContent()}
285
- <FlexRender
286
- content={cell.column.columnDef.cell}
287
- context={cell.getContext()}
288
- />
289
- {/snippet}
290
- {:else}
243
+ </div>
244
+ {/if}
245
+ </Table.Head>
246
+ {/each}
247
+ </Table.Row>
248
+ {/each}
249
+ </Table.Header>
250
+ <Table.Body>
251
+ {#each table.getRowModel().rows as row (row.id)}
252
+ <Table.Row
253
+ data-state={row.getIsSelected() ? 'selected' : undefined}
254
+ class="border-b border-border"
255
+ onclick={() => onRowClick?.(row.original as TData)}
256
+ >
257
+ {#each row.getVisibleCells() as cell, index (cell.id)}
258
+ {@const isLastScrollable = index === row.getVisibleCells().length - 2}
259
+ {@const visibleCells = row.getVisibleCells()}
260
+ {@const firstDataColumnIndex = visibleCells.findIndex(
261
+ (c) => c.column.id !== 'select' && c.column.id !== 'actions'
262
+ )}
263
+ {@const isFirstDataColumn = index === firstDataColumnIndex}
264
+ <Table.Cell
265
+ style={getCellStyle(cell, isLastScrollable)}
266
+ class={getCellClasses(cell, isLastScrollable, isFirstDataColumn)}
267
+ >
268
+ {#if cell.column.id === 'actions'}
269
+ {@render StickyCellWrapper({
270
+ align: 'right',
271
+ children: CellContent
272
+ })}
273
+ {#snippet CellContent()}
291
274
  <FlexRender
292
275
  content={cell.column.columnDef.cell}
293
276
  context={cell.getContext()}
294
277
  />
295
- {/if}
296
- </Table.Cell>
297
- {/each}
298
- </Table.Row>
299
- {:else}
300
- <Table.Row>
301
- <Table.Cell colspan={columns.length} class="h-48">
302
- <EmptyState
303
- iconSource={emptyState.iconSource}
304
- title={emptyState.title}
305
- description={emptyState.description}
306
- />
278
+ {/snippet}
279
+ {:else if cell.column.id === 'select'}
280
+ {@render StickyCellWrapper({
281
+ align: 'left',
282
+ children: CellContent
283
+ })}
284
+ {#snippet CellContent()}
285
+ <FlexRender
286
+ content={cell.column.columnDef.cell}
287
+ context={cell.getContext()}
288
+ />
289
+ {/snippet}
290
+ {:else}
291
+ <FlexRender
292
+ content={cell.column.columnDef.cell}
293
+ context={cell.getContext()}
294
+ />
295
+ {/if}
307
296
  </Table.Cell>
308
- </Table.Row>
309
- {/each}
310
- </Table.Body>
311
- </Table.Root>
312
- </div>
297
+ {/each}
298
+ </Table.Row>
299
+ {:else}
300
+ <Table.Row>
301
+ <Table.Cell colspan={columns.length} class="h-48">
302
+ <EmptyState
303
+ iconSource={emptyState.iconSource}
304
+ title={emptyState.title}
305
+ description={emptyState.description}
306
+ />
307
+ </Table.Cell>
308
+ </Table.Row>
309
+ {/each}
310
+ </Table.Body>
311
+ </Table.Root>
313
312
  </div>
314
313
  {#if enablePagination}
315
314
  <DataTablePagination
@@ -17,8 +17,8 @@ export function getHeaderStyle(header, isLastScrollable) {
17
17
  */
18
18
  export function getHeaderClasses(header, isLastScrollable) {
19
19
  return clsx('relative whitespace-nowrap overflow-hidden', {
20
- 'sticky right-0 text-right bg-background': header.id === 'actions',
21
- 'sticky left-0 bg-background z-10': header.id === 'select',
20
+ 'sticky right-0 text-right bg-background pl-3 pr-6': header.id === 'actions',
21
+ 'sticky left-0 bg-background z-10 pl-6 pr-3': header.id === 'select',
22
22
  'w-full': isLastScrollable,
23
23
  'hover:!bg-transparent': !header.column.getCanSort()
24
24
  });
@@ -15,7 +15,7 @@
15
15
  <thead
16
16
  bind:this={ref}
17
17
  data-slot="table-header"
18
- class={cn('[&_tr]:border-b [&_tr]:border-border bg-background', className)}
18
+ class={cn('sticky top-0 z-20 [&_tr]:border-b [&_tr]:border-border bg-background', className)}
19
19
  onclick={bubble('click')}
20
20
  onkeydown={bubble('keydown')}
21
21
  >
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@invopop/popui",
3
3
  "license": "MIT",
4
- "version": "0.1.4-beta.1",
4
+ "version": "0.1.4-beta.2",
5
5
  "repository": {
6
6
  "url": "https://github.com/invopop/popui"
7
7
  },