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

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.
@@ -3,13 +3,16 @@
3
3
  import RangeCalendar from './range-calendar/range-calendar.svelte'
4
4
  import { parseDate, type DateValue } from '@internationalized/date'
5
5
  import type { DateRange } from 'bits-ui'
6
- import { Icon } from '@steeze-ui/svelte-icon'
7
- import { Calendar } from '@invopop/ui-icons'
6
+ import { Icon, type IconSource } from '@steeze-ui/svelte-icon'
8
7
  import Transition from 'svelte-transition'
9
8
  import type { DatePickerProps } from './types'
10
9
  import { clickOutside } from './clickOutside'
11
10
  import BaseButton from './BaseButton.svelte'
12
- import { datesFromToday, toCalendarDate } from './helpers'
11
+ import { datesFromToday, toCalendarDate, resolveIcon } from './helpers'
12
+ import { buttonVariants } from './button/button.svelte'
13
+ import { offset, flip, shift } from 'svelte-floating-ui/dom'
14
+ import { createFloatingActions } from 'svelte-floating-ui'
15
+ import { portal } from 'svelte-portal'
13
16
 
14
17
  const {
15
18
  startOfThisWeek,
@@ -109,23 +112,47 @@
109
112
 
110
113
  let {
111
114
  label = 'Date',
112
- position = 'left',
115
+ placement = 'bottom-start',
113
116
  from = '',
114
117
  to = '',
115
- onSelect
118
+ onSelect,
119
+ stackLeft = false,
120
+ stackRight = false,
121
+ icon = undefined,
122
+ iconTheme = 'default'
116
123
  }: DatePickerProps = $props()
117
124
 
125
+ const [floatingRef, floatingContent] = createFloatingActions({
126
+ strategy: 'absolute',
127
+ placement,
128
+ middleware: [offset(8), flip(), shift()]
129
+ })
130
+
131
+ let resolvedIcon: IconSource | undefined = $state()
132
+
133
+ $effect(() => {
134
+ resolveIcon(icon).then((res) => (resolvedIcon = res))
135
+ })
136
+
118
137
  let selectedPeriod = $state('custom')
119
138
  let value = $state<DateRange>({
120
139
  start: undefined,
121
140
  end: undefined
122
141
  })
123
142
  let isOpen = $state(false)
143
+ let isStacked = $derived(stackLeft || stackRight)
144
+ let hasSelectedDates = $derived(value.start !== undefined)
124
145
  let styles = $derived(
125
- clsx({
126
- 'border-border-selected-bold shadow-active': isOpen,
127
- 'border-border-secondary hover:border-border-default-secondary-hover': !isOpen
128
- })
146
+ isStacked
147
+ ? buttonVariants({
148
+ variant: 'ghost',
149
+ stackedLeft: stackLeft,
150
+ stackedRight: stackRight
151
+ })
152
+ : clsx('border backdrop-blur-sm backdrop-filter', {
153
+ 'border-border-selected-bold shadow-active': isOpen,
154
+ 'border-border-default-secondary hover:border-border-default-secondary-hover': !isOpen
155
+ })
129
156
  )
130
157
  let selectedLabel = $state(label)
131
158
 
@@ -137,9 +164,19 @@
137
164
 
138
165
  $effect(() => {
139
166
  if (from) {
167
+ const startDate = parseDate(from)
168
+ const endDate = to ? parseDate(to) : undefined
169
+
140
170
  value = {
141
- start: parseDate(from),
142
- end: to ? parseDate(to) : undefined
171
+ start: startDate,
172
+ end: endDate
173
+ }
174
+
175
+ // Update label directly without calling getLabel() to avoid circular dependency
176
+ if (startDate === endDate) {
177
+ selectedLabel = getDisplayFromValue(startDate)
178
+ } else {
179
+ selectedLabel = `${getDisplayFromValue(startDate)} → ${getDisplayFromValue(endDate)}`
143
180
  }
144
181
  return
145
182
  }
@@ -183,19 +220,34 @@
183
220
  </script>
184
221
 
185
222
  <div>
186
- <div class="relative">
187
- <button
188
- onclick={() => {
189
- isOpen = !isOpen
190
- }}
191
- class="{styles} datepicker-trigger w-full py-1.25 pl-7 pr-2 text-left border border-border-default-secondary rounded-lg text-foreground placeholder-foreground text-base cursor-pointer"
223
+ <button
224
+ use:floatingRef
225
+ onclick={() => {
226
+ isOpen = !isOpen
227
+ }}
228
+ class="{styles} {isStacked
229
+ ? 'h-7 py-1.5'
230
+ : 'py-1.5'} datepicker-trigger flex items-center w-full {resolvedIcon
231
+ ? 'pl-7'
232
+ : 'pl-2'} pr-2 text-left rounded-lg bg-background cursor-pointer relative overflow-hidden"
233
+ >
234
+ {#if resolvedIcon}
235
+ <Icon
236
+ src={resolvedIcon}
237
+ theme={iconTheme}
238
+ class="h-4 w-4 absolute top-1.5 left-2 text-foreground-default-secondary"
239
+ />
240
+ {/if}
241
+ <span
242
+ class="flex-1 text-base truncate {hasSelectedDates
243
+ ? 'text-foreground'
244
+ : 'text-foreground-default-secondary'}"
192
245
  >
193
246
  {selectedLabel}
194
- </button>
195
- <Icon src={Calendar} class="h-4 w-4 absolute top-2 left-2 text-foreground-default-secondary" />
196
- </div>
247
+ </span>
248
+ </button>
197
249
 
198
- <div class="relative">
250
+ {#if isOpen}
199
251
  <Transition
200
252
  show={isOpen}
201
253
  enter="transition ease-out duration-100"
@@ -207,9 +259,9 @@
207
259
  >
208
260
  <!-- @ts-ignore -->
209
261
  <div
210
- class:left-0={position === 'left'}
211
- class:right-0={position === 'right'}
212
- class="bg-background inline-flex flex-col shadow-lg rounded-xl absolute right-0 top-2 z-40 border border-border"
262
+ use:portal
263
+ use:floatingContent
264
+ class="bg-background inline-flex flex-col shadow-lg rounded-xl absolute z-1001 border border-border"
213
265
  use:clickOutside
214
266
  onclick_outside={() => {
215
267
  if (!isOpen) return
@@ -239,5 +291,5 @@
239
291
  </div>
240
292
  </div>
241
293
  </Transition>
242
- </div>
294
+ {/if}
243
295
  </div>
@@ -76,10 +76,9 @@
76
76
  function handleClick(val: AnyProp) {
77
77
  value = val
78
78
 
79
- onSelect?.(value)
80
-
81
79
  if (multiple) return
82
80
 
81
+ onSelect?.(value)
83
82
  selectDropdown?.toggle()
84
83
  }
85
84
 
@@ -90,6 +89,7 @@
90
89
  if (isEqual(value, val)) return
91
90
 
92
91
  value = val
92
+ onSelect?.(value)
93
93
  }
94
94
  </script>
95
95
 
@@ -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
  }
@@ -21,7 +21,13 @@
21
21
 
22
22
  let currentPage = $derived(table.getState().pagination.pageIndex + 1)
23
23
  let totalPages = $derived(table.getPageCount())
24
- let totalItems = $derived(table.getFilteredRowModel().rows.length)
24
+ let totalItems = $derived.by(() => {
25
+ const rowCount = table.getRowCount?.()
26
+ if (table.options.manualPagination && rowCount !== undefined) {
27
+ return rowCount
28
+ }
29
+ return table.getFilteredRowModel().rows.length
30
+ })
25
31
  let rowsPerPage = $derived(table.getState().pagination.pageSize)
26
32
  let hasSelection = $derived(Object.keys(table.getState().rowSelection).length > 0)
27
33
 
@@ -145,7 +151,9 @@
145
151
  onchange={(value) => {
146
152
  const size = Number(value)
147
153
  table.setPageSize(size)
154
+ table.setPageIndex(0)
148
155
  onPageSizeChange?.(size)
156
+ onPageChange?.(1)
149
157
  }}
150
158
  placeholder="Rows per page"
151
159
  disablePlaceholder={true}
@@ -53,6 +53,10 @@ export function createSvelteTable(options) {
53
53
  }
54
54
  updateOptions();
55
55
  $effect.pre(() => {
56
+ // Access data and columns to track them - this reads but doesn't write
57
+ // so it won't cause infinite loops
58
+ void options.data;
59
+ void options.columns;
56
60
  updateOptions();
57
61
  });
58
62
  return table;
@@ -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[];
@@ -56,8 +57,12 @@ export interface DataTableProps<TData> {
56
57
  filters?: Snippet;
57
58
  paginationSelectedSlot?: Snippet;
58
59
  paginationUnselectedSlot?: Snippet;
60
+ manualPagination?: boolean;
61
+ pageCount?: number;
62
+ rowCount?: number;
59
63
  onPageChange?: (pageIndex: number) => void;
60
64
  onPageSizeChange?: (pageSize: number) => void;
65
+ onSortingChange?: (columnId: string, direction: 'asc' | 'desc') => void;
61
66
  }
62
67
  export interface DataTablePaginationProps<T> {
63
68
  table: Table<T>;
@@ -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 = {
@@ -45,8 +46,12 @@
45
46
  filters,
46
47
  paginationSelectedSlot,
47
48
  paginationUnselectedSlot,
49
+ manualPagination = false,
50
+ pageCount,
51
+ rowCount,
48
52
  onPageChange,
49
- onPageSizeChange
53
+ onPageSizeChange,
54
+ onSortingChange
50
55
  }: DataTableProps<TData> = $props()
51
56
 
52
57
  const enableSelection = !disableSelection
@@ -70,7 +75,7 @@
70
75
 
71
76
  // Build TanStack columns from config
72
77
  const columns = $derived.by(() =>
73
- buildColumns<TData>(columnConfig, enableSelection, RowActions, rowActions.length > 0)
78
+ buildColumns<TData>(columnConfig, enableSelection, RowActions, getRowActions !== undefined || rowActions.length > 0)
74
79
  )
75
80
 
76
81
  // Calculate initial column sizes based on available width
@@ -95,14 +100,13 @@
95
100
  })
96
101
 
97
102
  const table = setupTable({
98
- get data() {
99
- return data
100
- },
101
- get columns() {
102
- return columns
103
- },
103
+ getData: () => data,
104
+ getColumns: () => columns,
104
105
  enableSelection,
105
106
  enablePagination,
107
+ manualPagination,
108
+ pageCount,
109
+ getRowCount: () => rowCount,
106
110
  getRowSelection: () => rowSelection,
107
111
  getColumnVisibility: () => columnVisibility,
108
112
  getSorting: () => sorting,
@@ -129,8 +133,8 @@
129
133
  })}
130
134
  <div
131
135
  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' : ''
136
+ 'h-10 flex items-center relative group-hover/row:bg-background-default-secondary group-data-[state=selected]/row:bg-background-selected',
137
+ align === 'right' ? 'justify-end pl-3 pr-6' : 'pl-6 pr-3'
134
138
  )}
135
139
  >
136
140
  <div class="relative z-10">
@@ -141,7 +145,7 @@
141
145
 
142
146
  {#snippet RowActions({ row }: { row: Row<TData> })}
143
147
  <BaseTableActions
144
- actions={rowActions}
148
+ actions={getRowActions ? getRowActions(row.original) : rowActions}
145
149
  onclick={(action) => {
146
150
  if (onRowAction) {
147
151
  onRowAction(action, row.original)
@@ -180,7 +184,12 @@
180
184
  <BaseTableHeaderOrderBy
181
185
  sortDirection={column.getIsSorted() === 'asc' ? 'asc' : 'desc'}
182
186
  isActive={column.getIsSorted() !== false}
183
- onOrderBy={(direction) => column.toggleSorting(direction === 'desc')}
187
+ onOrderBy={(direction) => {
188
+ column.toggleSorting(direction === 'desc')
189
+ if (onSortingChange) {
190
+ onSortingChange(column.id, direction)
191
+ }
192
+ }}
184
193
  onHide={() => column.toggleVisibility(false)}
185
194
  />
186
195
  </BaseDropdown>
@@ -188,128 +197,126 @@
188
197
  {/if}
189
198
  {/snippet}
190
199
 
191
- <div class="flex flex-col gap-4">
200
+ <div class="flex flex-col h-full">
192
201
  <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}
202
+ <div class="flex-1 overflow-hidden flex flex-col">
203
+ <div bind:this={containerRef} class="relative bg-background flex-1 overflow-auto">
204
+ <Table.Root>
205
+ <Table.Header>
206
+ {#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
207
+ <Table.Row class="hover:!bg-transparent border-b border-border">
208
+ {#each headerGroup.headers as header, index (header.id)}
209
+ {@const isLastScrollable = index === headerGroup.headers.length - 2}
210
+ <Table.Head
211
+ colspan={header.colSpan}
212
+ style={getHeaderStyle(header, isLastScrollable)}
213
+ class={getHeaderClasses(header, isLastScrollable)}
214
+ >
215
+ {#if !header.isPlaceholder}
216
+ {#if typeof header.column.columnDef.header === 'string'}
217
+ {@render ColumnHeader({
218
+ column: header.column as Column<TData>,
219
+ title: header.column.columnDef.header as string
220
+ })}
221
+ {:else}
222
+ <FlexRender
223
+ content={header.column.columnDef.header}
224
+ context={header.getContext()}
225
+ />
219
226
  {/if}
220
- {#if header.column.getCanResize()}
221
- <!-- Always visible vertical border -->
227
+ {/if}
228
+ {#if header.column.getCanResize()}
229
+ <!-- Always visible vertical border -->
230
+ <div
231
+ class={cn(
232
+ 'absolute right-0 top-1/2 -translate-y-1/2 h-3 w-px bg-background-default-tertiary',
233
+ header.column.getIsResizing() && 'opacity-0'
234
+ )}
235
+ ></div>
236
+ <!-- Resize handler (larger interactive area, enhanced on hover) -->
237
+ <div
238
+ role="button"
239
+ tabindex="0"
240
+ aria-label="Resize column"
241
+ class="absolute right-0 top-0 h-full w-3 cursor-col-resize select-none touch-none group -mr-1.5"
242
+ onmousedown={header.getResizeHandler()}
243
+ ontouchstart={header.getResizeHandler()}
244
+ >
222
245
  <div
223
246
  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'
247
+ 'absolute right-1.5 top-0 h-full w-0.5 bg-border-default-secondary transition-opacity opacity-0',
248
+ !header.column.getIsResizing() && 'group-hover:opacity-100'
226
249
  )}
227
250
  ></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}
251
+ </div>
252
+ {/if}
253
+ </Table.Head>
254
+ {/each}
255
+ </Table.Row>
256
+ {/each}
257
+ </Table.Header>
258
+ <Table.Body>
259
+ {#each table.getRowModel().rows as row (row.id)}
260
+ <Table.Row
261
+ data-state={row.getIsSelected() ? 'selected' : undefined}
262
+ class="border-b border-border"
263
+ onclick={() => onRowClick?.(row.original as TData)}
264
+ >
265
+ {#each row.getVisibleCells() as cell, index (cell.id)}
266
+ {@const isLastScrollable = index === row.getVisibleCells().length - 2}
267
+ {@const visibleCells = row.getVisibleCells()}
268
+ {@const firstDataColumnIndex = visibleCells.findIndex(
269
+ (c) => c.column.id !== 'select' && c.column.id !== 'actions'
270
+ )}
271
+ {@const isFirstDataColumn = index === firstDataColumnIndex}
272
+ <Table.Cell
273
+ style={getCellStyle(cell, isLastScrollable)}
274
+ class={getCellClasses(cell, isLastScrollable, isFirstDataColumn)}
275
+ >
276
+ {#if cell.column.id === 'actions'}
277
+ {@render StickyCellWrapper({
278
+ align: 'right',
279
+ children: CellContent
280
+ })}
281
+ {#snippet CellContent()}
291
282
  <FlexRender
292
283
  content={cell.column.columnDef.cell}
293
284
  context={cell.getContext()}
294
285
  />
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
- />
286
+ {/snippet}
287
+ {:else if cell.column.id === 'select'}
288
+ {@render StickyCellWrapper({
289
+ align: 'left',
290
+ children: CellContent
291
+ })}
292
+ {#snippet CellContent()}
293
+ <FlexRender
294
+ content={cell.column.columnDef.cell}
295
+ context={cell.getContext()}
296
+ />
297
+ {/snippet}
298
+ {:else}
299
+ <FlexRender
300
+ content={cell.column.columnDef.cell}
301
+ context={cell.getContext()}
302
+ />
303
+ {/if}
307
304
  </Table.Cell>
308
- </Table.Row>
309
- {/each}
310
- </Table.Body>
311
- </Table.Root>
312
- </div>
305
+ {/each}
306
+ </Table.Row>
307
+ {:else}
308
+ <Table.Row class="hover:!bg-transparent">
309
+ <Table.Cell colspan={columns.length} class="h-48">
310
+ <EmptyState
311
+ iconSource={emptyState.iconSource}
312
+ title={emptyState.title}
313
+ description={emptyState.description}
314
+ />
315
+ </Table.Cell>
316
+ </Table.Row>
317
+ {/each}
318
+ </Table.Body>
319
+ </Table.Root>
313
320
  </div>
314
321
  {#if enablePagination}
315
322
  <DataTablePagination
@@ -11,6 +11,11 @@ export declare function buildColumns<TData>(columnConfig: DataTableColumn<TData>
11
11
  interface TableSetupOptions<TData> {
12
12
  enableSelection: boolean;
13
13
  enablePagination: boolean;
14
+ manualPagination?: boolean;
15
+ pageCount?: number;
16
+ getRowCount?: () => number | undefined;
17
+ getData?: () => TData[];
18
+ getColumns?: () => any[];
14
19
  getRowSelection: () => RowSelectionState;
15
20
  getColumnVisibility: () => VisibilityState;
16
21
  getSorting: () => SortingState;
@@ -29,8 +34,5 @@ interface TableSetupOptions<TData> {
29
34
  /**
30
35
  * Create the TanStack table instance with all configuration
31
36
  */
32
- export declare function setupTable<TData>(options: TableSetupOptions<TData> & {
33
- data?: TData[];
34
- columns?: any[];
35
- }): import("@tanstack/table-core").Table<unknown>;
37
+ export declare function setupTable<TData>(options: TableSetupOptions<TData>): import("@tanstack/table-core").Table<unknown>;
36
38
  export {};
@@ -55,6 +55,12 @@ export function setupTable(options) {
55
55
  return options.getColumnOrder();
56
56
  }
57
57
  },
58
+ get data() {
59
+ return options.getData?.() ?? [];
60
+ },
61
+ get columns() {
62
+ return options.getColumns?.() ?? [];
63
+ },
58
64
  enableRowSelection: options.enableSelection,
59
65
  enableColumnResizing: true,
60
66
  columnResizeMode: 'onChange',
@@ -116,15 +122,22 @@ export function setupTable(options) {
116
122
  }
117
123
  },
118
124
  getCoreRowModel: getCoreRowModel(),
119
- getPaginationRowModel: options.enablePagination ? getPaginationRowModel() : undefined,
120
- getSortedRowModel: getSortedRowModel()
125
+ getPaginationRowModel: options.enablePagination && !options.manualPagination ? getPaginationRowModel() : undefined,
126
+ getSortedRowModel: getSortedRowModel(),
127
+ // Manual pagination configuration
128
+ manualPagination: options.manualPagination,
129
+ get pageCount() {
130
+ // Calculate pageCount from rowCount and current pageSize
131
+ const rowCount = options.getRowCount?.();
132
+ if (rowCount !== undefined) {
133
+ const pageSize = options.getPagination().pageSize;
134
+ return Math.ceil(rowCount / pageSize);
135
+ }
136
+ return options.pageCount ?? -1;
137
+ },
138
+ get rowCount() {
139
+ return options.getRowCount?.();
140
+ }
121
141
  };
122
- // Add data and columns as getters if provided
123
- if (options.data !== undefined) {
124
- tableOptions.data = options.data;
125
- }
126
- if (options.columns !== undefined) {
127
- tableOptions.columns = options.columns;
128
- }
129
142
  return createSvelteTable(tableOptions);
130
143
  }
@@ -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/dist/types.d.ts CHANGED
@@ -368,13 +368,17 @@ export interface DataListItemProps {
368
368
  }
369
369
  export interface DatePickerProps {
370
370
  label?: string;
371
- position?: 'left' | 'right';
371
+ placement?: Placement;
372
372
  from?: string;
373
373
  to?: string;
374
374
  onSelect?: (date: {
375
375
  from: string;
376
376
  to: string;
377
377
  }) => void;
378
+ stackLeft?: boolean;
379
+ stackRight?: boolean;
380
+ icon?: IconSource | string;
381
+ iconTheme?: IconTheme;
378
382
  }
379
383
  export interface DrawerContextProps {
380
384
  items?: DrawerOption[];
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.10",
5
5
  "repository": {
6
6
  "url": "https://github.com/invopop/popui"
7
7
  },