@suphark/ui 0.1.0 → 0.2.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/CHANGELOG.md +26 -0
- package/README.md +5 -1
- package/package.json +16 -2
- package/src/components/data-table/data-table-column-header.tsx +71 -0
- package/src/components/data-table/data-table-date-range-filter.tsx +25 -0
- package/src/components/data-table/data-table-faceted-filter.tsx +181 -0
- package/src/components/data-table/data-table-pagination.tsx +114 -0
- package/src/components/data-table/data-table-row-actions.tsx +166 -0
- package/src/components/data-table/data-table-toolbar.tsx +207 -0
- package/src/components/data-table/data-table-view-option.tsx +59 -0
- package/src/components/data-table/data-table.tsx +399 -0
- package/src/components/data-table/index.ts +9 -0
- package/src/components/data-table/types.ts +106 -0
- package/src/components/data-table/use-data-table.ts +229 -0
- package/src/components/design-system/sections/showcase-data-display.tsx +71 -0
- package/src/components/design-system/sections/showcase-guidelines-recipes.tsx +8 -8
- package/src/components/shared/button/delete-many-button.tsx +85 -0
- package/src/components/shared/resource-property-filter.tsx +399 -0
- package/src/components/ui/native-select.tsx +54 -0
- package/src/data-table.ts +21 -0
- package/src/hooks/use-table-search-params.ts +147 -0
- package/src/index.ts +1 -0
- package/src/next.ts +5 -0
- package/src/components/shared/__tests__/multi-select-combobox.test.tsx +0 -180
- package/src/components/shared/__tests__/name-id-fields.test.ts +0 -24
- package/src/components/shared/form/__tests__/simple-select.test.tsx +0 -72
- package/src/components/ui/__tests__/combobox.test.tsx +0 -40
- package/src/components/ui/__tests__/date-picker.test.tsx +0 -53
- package/src/components/ui/__tests__/slider.test.tsx +0 -18
- package/src/components/ui/__tests__/tabs.test.tsx +0 -33
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
closestCenter,
|
|
5
|
+
DndContext,
|
|
6
|
+
type DragEndEvent,
|
|
7
|
+
KeyboardSensor,
|
|
8
|
+
PointerSensor,
|
|
9
|
+
useSensor,
|
|
10
|
+
useSensors,
|
|
11
|
+
} from "@dnd-kit/core";
|
|
12
|
+
import {
|
|
13
|
+
arrayMove,
|
|
14
|
+
SortableContext,
|
|
15
|
+
sortableKeyboardCoordinates,
|
|
16
|
+
useSortable,
|
|
17
|
+
verticalListSortingStrategy,
|
|
18
|
+
} from "@dnd-kit/sortable";
|
|
19
|
+
import { CSS } from "@dnd-kit/utilities";
|
|
20
|
+
import {
|
|
21
|
+
TableBody,
|
|
22
|
+
TableCell,
|
|
23
|
+
TableHead,
|
|
24
|
+
TableHeader,
|
|
25
|
+
TableRow,
|
|
26
|
+
Table as UiTable,
|
|
27
|
+
} from "@suphark/ui/components/ui/table";
|
|
28
|
+
import { cn } from "@suphark/ui/lib/utils";
|
|
29
|
+
import { flexRender, type Row } from "@tanstack/react-table";
|
|
30
|
+
import type { CSSProperties } from "react";
|
|
31
|
+
import * as React from "react";
|
|
32
|
+
import { toast } from "sonner";
|
|
33
|
+
import { DataTablePagination } from "./data-table-pagination";
|
|
34
|
+
import { DataTableToolbar } from "./data-table-toolbar";
|
|
35
|
+
import type { DataTableProps } from "./types";
|
|
36
|
+
import { useDataTable } from "./use-data-table";
|
|
37
|
+
|
|
38
|
+
interface DraggableRowProps<TData> {
|
|
39
|
+
row: Row<TData>;
|
|
40
|
+
enableColumnResizing?: boolean;
|
|
41
|
+
getRowClassName?: (row: Row<TData>) => string;
|
|
42
|
+
stickyEdgeColumns?: boolean;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function DraggableRow<TData>({
|
|
46
|
+
row,
|
|
47
|
+
enableColumnResizing,
|
|
48
|
+
getRowClassName,
|
|
49
|
+
stickyEdgeColumns,
|
|
50
|
+
}: DraggableRowProps<TData>) {
|
|
51
|
+
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
|
|
52
|
+
id: row.id,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const style: CSSProperties = {
|
|
56
|
+
transform: CSS.Transform.toString(transform),
|
|
57
|
+
transition,
|
|
58
|
+
opacity: isDragging ? 0.8 : 1,
|
|
59
|
+
zIndex: isDragging ? 1 : 0,
|
|
60
|
+
position: "relative",
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<TableRow
|
|
65
|
+
ref={setNodeRef}
|
|
66
|
+
style={style}
|
|
67
|
+
data-state={row.getIsSelected() && "selected"}
|
|
68
|
+
className={cn(stickyEdgeColumns && "group/row", getRowClassName?.(row))}
|
|
69
|
+
>
|
|
70
|
+
{row.getVisibleCells().map((cell) => {
|
|
71
|
+
const isDndColumn = cell.column.id === "dnd-handle";
|
|
72
|
+
return (
|
|
73
|
+
<TableCell
|
|
74
|
+
key={cell.id}
|
|
75
|
+
{...(isDndColumn ? { ...attributes, ...listeners } : {})}
|
|
76
|
+
style={{
|
|
77
|
+
width: enableColumnResizing ? cell.column.getSize() : undefined,
|
|
78
|
+
}}
|
|
79
|
+
className={cn(
|
|
80
|
+
stickyEdgeColumns &&
|
|
81
|
+
cell.column.id === "select" &&
|
|
82
|
+
"sticky left-0 z-10 bg-card group-hover/row:bg-muted group-data-[state=selected]/row:bg-muted",
|
|
83
|
+
stickyEdgeColumns &&
|
|
84
|
+
cell.column.id === "actions" &&
|
|
85
|
+
"sticky right-0 z-10 bg-card group-hover/row:bg-muted group-data-[state=selected]/row:bg-muted",
|
|
86
|
+
)}
|
|
87
|
+
>
|
|
88
|
+
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
|
89
|
+
</TableCell>
|
|
90
|
+
);
|
|
91
|
+
})}
|
|
92
|
+
</TableRow>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* DataTable
|
|
98
|
+
* @initialState {{ expanded: true }} -> (ขยายทั้งหมด)
|
|
99
|
+
* @initialState {{ expanded: {} }} -> (หุบทั้งหมด)
|
|
100
|
+
*/
|
|
101
|
+
export function DataTable<TData, TValue>({
|
|
102
|
+
columns,
|
|
103
|
+
data,
|
|
104
|
+
className,
|
|
105
|
+
containerClassName,
|
|
106
|
+
tableContainerClassName,
|
|
107
|
+
stickyHeader,
|
|
108
|
+
stickyPagination,
|
|
109
|
+
stickyEdgeColumns,
|
|
110
|
+
emptyStateComponent,
|
|
111
|
+
facetedFilters,
|
|
112
|
+
actionsComponent,
|
|
113
|
+
globalFilterPlaceholder,
|
|
114
|
+
initialVisibility = {},
|
|
115
|
+
globalFilterFn,
|
|
116
|
+
globalFilterKeys,
|
|
117
|
+
getRowClassName,
|
|
118
|
+
getFacetedUniqueValues: getFacetedUniqueValuesProp,
|
|
119
|
+
initialColumnFilters = [],
|
|
120
|
+
columnFilters,
|
|
121
|
+
getSubRows,
|
|
122
|
+
getRowId,
|
|
123
|
+
initialPageSize = 10,
|
|
124
|
+
meta,
|
|
125
|
+
initialState,
|
|
126
|
+
pageCount,
|
|
127
|
+
rowCount,
|
|
128
|
+
pagination,
|
|
129
|
+
sorting,
|
|
130
|
+
globalFilter,
|
|
131
|
+
onGlobalFilterChange,
|
|
132
|
+
manualPagination,
|
|
133
|
+
manualSorting,
|
|
134
|
+
manualFiltering,
|
|
135
|
+
onPaginationChange,
|
|
136
|
+
onPageSizeChange,
|
|
137
|
+
onSortingChange,
|
|
138
|
+
onColumnFiltersChange,
|
|
139
|
+
enableColumnResizing = false,
|
|
140
|
+
enableRowSelection = true,
|
|
141
|
+
isLoading,
|
|
142
|
+
pageSizeOptions,
|
|
143
|
+
toolbarFilters,
|
|
144
|
+
preservedFilterIds,
|
|
145
|
+
onReorder,
|
|
146
|
+
renderCustomView,
|
|
147
|
+
}: DataTableProps<TData, TValue>) {
|
|
148
|
+
const { table } = useDataTable({
|
|
149
|
+
columns,
|
|
150
|
+
data,
|
|
151
|
+
className,
|
|
152
|
+
containerClassName,
|
|
153
|
+
stickyHeader,
|
|
154
|
+
emptyStateComponent,
|
|
155
|
+
initialVisibility,
|
|
156
|
+
globalFilterFn,
|
|
157
|
+
globalFilterKeys,
|
|
158
|
+
getFacetedUniqueValues: getFacetedUniqueValuesProp,
|
|
159
|
+
initialColumnFilters,
|
|
160
|
+
columnFilters,
|
|
161
|
+
getSubRows,
|
|
162
|
+
getRowId,
|
|
163
|
+
initialPageSize,
|
|
164
|
+
meta,
|
|
165
|
+
initialState,
|
|
166
|
+
pageCount,
|
|
167
|
+
rowCount,
|
|
168
|
+
pagination,
|
|
169
|
+
sorting,
|
|
170
|
+
globalFilter,
|
|
171
|
+
onGlobalFilterChange,
|
|
172
|
+
manualPagination,
|
|
173
|
+
manualSorting,
|
|
174
|
+
manualFiltering,
|
|
175
|
+
onPaginationChange,
|
|
176
|
+
onSortingChange,
|
|
177
|
+
onColumnFiltersChange,
|
|
178
|
+
enableColumnResizing,
|
|
179
|
+
enableRowSelection,
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
const sensors = useSensors(
|
|
183
|
+
useSensor(PointerSensor, {
|
|
184
|
+
activationConstraint: {
|
|
185
|
+
distance: 5,
|
|
186
|
+
},
|
|
187
|
+
}),
|
|
188
|
+
useSensor(KeyboardSensor, {
|
|
189
|
+
coordinateGetter: sortableKeyboardCoordinates,
|
|
190
|
+
}),
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
function handleDragEnd(event: DragEndEvent) {
|
|
194
|
+
if (!onReorder) return;
|
|
195
|
+
|
|
196
|
+
const state = table.getState();
|
|
197
|
+
const isFiltered = state.columnFilters.length > 0 || state.globalFilter;
|
|
198
|
+
const isSorted = state.sorting.length > 0;
|
|
199
|
+
|
|
200
|
+
if (isFiltered || isSorted) {
|
|
201
|
+
toast.warning("Cannot reorder while filtering or sorting is active.");
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const { active, over } = event;
|
|
206
|
+
if (over && active.id !== over.id) {
|
|
207
|
+
const oldIndex = data.findIndex((item) => {
|
|
208
|
+
const id = getRowId ? getRowId(item) : (item as { id?: string | number }).id;
|
|
209
|
+
return id === active.id;
|
|
210
|
+
});
|
|
211
|
+
const newIndex = data.findIndex((item) => {
|
|
212
|
+
const id = getRowId ? getRowId(item) : (item as { id?: string | number }).id;
|
|
213
|
+
return id === over.id;
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
if (oldIndex !== -1 && newIndex !== -1) {
|
|
217
|
+
const newData = arrayMove(data, oldIndex, newIndex);
|
|
218
|
+
onReorder(newData);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const tableRows = table.getRowModel().rows;
|
|
224
|
+
const rowIds = React.useMemo(() => tableRows.map((row) => row.id), [tableRows]);
|
|
225
|
+
|
|
226
|
+
const tableContent = (
|
|
227
|
+
<UiTable
|
|
228
|
+
className={cn(
|
|
229
|
+
"w-full border-collapse",
|
|
230
|
+
enableColumnResizing ? "table-fixed" : "table-auto",
|
|
231
|
+
className,
|
|
232
|
+
)}
|
|
233
|
+
// กล่องชั้นในสุดคือ scroll container ที่ sticky header ยึด จึงต้องกำหนดความสูง
|
|
234
|
+
// ที่กล่องนี้ ไม่ใช่กล่องชั้นนอก
|
|
235
|
+
containerClassName={tableContainerClassName}
|
|
236
|
+
style={enableColumnResizing ? { width: table.getTotalSize() } : {}}
|
|
237
|
+
>
|
|
238
|
+
<TableHeader
|
|
239
|
+
className={stickyHeader ? "sticky top-0 z-10 bg-background shadow-sm" : undefined}
|
|
240
|
+
>
|
|
241
|
+
{table.getHeaderGroups().map((headerGroup) => (
|
|
242
|
+
<TableRow key={headerGroup.id}>
|
|
243
|
+
{headerGroup.headers.map((header) => {
|
|
244
|
+
return (
|
|
245
|
+
<TableHead
|
|
246
|
+
key={header.id}
|
|
247
|
+
colSpan={header.colSpan}
|
|
248
|
+
style={{
|
|
249
|
+
width: enableColumnResizing ? header.getSize() : undefined,
|
|
250
|
+
}}
|
|
251
|
+
className={cn(
|
|
252
|
+
"group relative whitespace-nowrap",
|
|
253
|
+
stickyEdgeColumns &&
|
|
254
|
+
header.column.id === "select" &&
|
|
255
|
+
"sticky left-0 z-20 bg-background",
|
|
256
|
+
stickyEdgeColumns &&
|
|
257
|
+
header.column.id === "actions" &&
|
|
258
|
+
"sticky right-0 z-20 bg-background",
|
|
259
|
+
)}
|
|
260
|
+
>
|
|
261
|
+
{header.isPlaceholder
|
|
262
|
+
? null
|
|
263
|
+
: flexRender(header.column.columnDef.header, header.getContext())}
|
|
264
|
+
{header.column.getCanResize() && (
|
|
265
|
+
<div
|
|
266
|
+
onMouseDown={header.getResizeHandler()}
|
|
267
|
+
onTouchStart={header.getResizeHandler()}
|
|
268
|
+
className={`absolute top-0 right-0 h-full w-1 cursor-col-resize touch-none select-none bg-border opacity-0 hover:bg-primary group-hover:opacity-100 ${
|
|
269
|
+
header.column.getIsResizing() ? "bg-primary opacity-100" : ""
|
|
270
|
+
}`}
|
|
271
|
+
/>
|
|
272
|
+
)}
|
|
273
|
+
</TableHead>
|
|
274
|
+
);
|
|
275
|
+
})}
|
|
276
|
+
</TableRow>
|
|
277
|
+
))}
|
|
278
|
+
</TableHeader>
|
|
279
|
+
<TableBody>
|
|
280
|
+
{isLoading ? (
|
|
281
|
+
Array.from({ length: initialPageSize }).map((_, i) => (
|
|
282
|
+
// biome-ignore lint/suspicious/noArrayIndexKey: แถว skeleton ไม่มีตัวตน ใช้ตำแหน่งเป็น key ได้
|
|
283
|
+
<TableRow key={i}>
|
|
284
|
+
{columns.map((_col, j) => (
|
|
285
|
+
// biome-ignore lint/suspicious/noArrayIndexKey: เซลล์ skeleton เช่นกัน
|
|
286
|
+
<TableCell key={j}>
|
|
287
|
+
<div className="h-4 w-full animate-pulse rounded bg-muted" />
|
|
288
|
+
</TableCell>
|
|
289
|
+
))}
|
|
290
|
+
</TableRow>
|
|
291
|
+
))
|
|
292
|
+
) : table.getRowModel().rows?.length ? (
|
|
293
|
+
onReorder ? (
|
|
294
|
+
<SortableContext items={rowIds} strategy={verticalListSortingStrategy}>
|
|
295
|
+
{table.getRowModel().rows.map((row) => (
|
|
296
|
+
<DraggableRow
|
|
297
|
+
key={row.id}
|
|
298
|
+
row={row}
|
|
299
|
+
enableColumnResizing={enableColumnResizing}
|
|
300
|
+
getRowClassName={getRowClassName}
|
|
301
|
+
stickyEdgeColumns={stickyEdgeColumns}
|
|
302
|
+
/>
|
|
303
|
+
))}
|
|
304
|
+
</SortableContext>
|
|
305
|
+
) : (
|
|
306
|
+
table.getRowModel().rows.map((row) => (
|
|
307
|
+
<TableRow
|
|
308
|
+
key={row.id}
|
|
309
|
+
data-state={row.getIsSelected() && "selected"}
|
|
310
|
+
className={cn(stickyEdgeColumns && "group/row", getRowClassName?.(row))}
|
|
311
|
+
>
|
|
312
|
+
{row.getVisibleCells().map((cell) => {
|
|
313
|
+
return (
|
|
314
|
+
<TableCell
|
|
315
|
+
key={cell.id}
|
|
316
|
+
style={{
|
|
317
|
+
width: enableColumnResizing ? cell.column.getSize() : undefined,
|
|
318
|
+
}}
|
|
319
|
+
className={cn(
|
|
320
|
+
stickyEdgeColumns &&
|
|
321
|
+
cell.column.id === "select" &&
|
|
322
|
+
"sticky left-0 z-10 bg-card group-hover/row:bg-muted group-data-[state=selected]/row:bg-muted",
|
|
323
|
+
stickyEdgeColumns &&
|
|
324
|
+
cell.column.id === "actions" &&
|
|
325
|
+
"sticky right-0 z-10 bg-card group-hover/row:bg-muted group-data-[state=selected]/row:bg-muted",
|
|
326
|
+
)}
|
|
327
|
+
>
|
|
328
|
+
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
|
329
|
+
</TableCell>
|
|
330
|
+
);
|
|
331
|
+
})}
|
|
332
|
+
</TableRow>
|
|
333
|
+
))
|
|
334
|
+
)
|
|
335
|
+
) : (
|
|
336
|
+
<TableRow>
|
|
337
|
+
<TableCell colSpan={table.getVisibleLeafColumns().length} className="h-24 text-center">
|
|
338
|
+
{emptyStateComponent || "No results."}
|
|
339
|
+
</TableCell>
|
|
340
|
+
</TableRow>
|
|
341
|
+
)}
|
|
342
|
+
</TableBody>
|
|
343
|
+
</UiTable>
|
|
344
|
+
);
|
|
345
|
+
|
|
346
|
+
const customView = renderCustomView?.(table);
|
|
347
|
+
|
|
348
|
+
return (
|
|
349
|
+
<div className={cn("flex flex-col gap-4", containerClassName)}>
|
|
350
|
+
<DataTableToolbar
|
|
351
|
+
table={table}
|
|
352
|
+
facetedFilters={facetedFilters}
|
|
353
|
+
actionsComponent={actionsComponent?.(table)}
|
|
354
|
+
globalFilterPlaceholder={globalFilterPlaceholder}
|
|
355
|
+
toolbarFilters={toolbarFilters?.(table)}
|
|
356
|
+
preservedFilterIds={preservedFilterIds}
|
|
357
|
+
/>
|
|
358
|
+
{customView ? (
|
|
359
|
+
<>
|
|
360
|
+
{customView}
|
|
361
|
+
<DataTablePagination
|
|
362
|
+
table={table}
|
|
363
|
+
pageSizeOptions={pageSizeOptions}
|
|
364
|
+
onPageSizeChange={onPageSizeChange}
|
|
365
|
+
/>
|
|
366
|
+
</>
|
|
367
|
+
) : (
|
|
368
|
+
<>
|
|
369
|
+
<div className="overflow-x-auto rounded-lg border bg-card shadow-sm max-sm:-mx-2 max-sm:rounded-none max-sm:border-x-0">
|
|
370
|
+
{onReorder ? (
|
|
371
|
+
<DndContext
|
|
372
|
+
sensors={sensors}
|
|
373
|
+
collisionDetection={closestCenter}
|
|
374
|
+
onDragEnd={handleDragEnd}
|
|
375
|
+
>
|
|
376
|
+
{tableContent}
|
|
377
|
+
</DndContext>
|
|
378
|
+
) : (
|
|
379
|
+
tableContent
|
|
380
|
+
)}
|
|
381
|
+
</div>
|
|
382
|
+
<div
|
|
383
|
+
className={
|
|
384
|
+
stickyPagination
|
|
385
|
+
? "sticky bottom-0 z-20 border-t bg-background py-2 max-sm:-mx-2"
|
|
386
|
+
: undefined
|
|
387
|
+
}
|
|
388
|
+
>
|
|
389
|
+
<DataTablePagination
|
|
390
|
+
table={table}
|
|
391
|
+
pageSizeOptions={pageSizeOptions}
|
|
392
|
+
onPageSizeChange={onPageSizeChange}
|
|
393
|
+
/>
|
|
394
|
+
</div>
|
|
395
|
+
</>
|
|
396
|
+
)}
|
|
397
|
+
</div>
|
|
398
|
+
);
|
|
399
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from "./data-table";
|
|
2
|
+
export * from "./data-table-column-header";
|
|
3
|
+
export * from "./data-table-date-range-filter";
|
|
4
|
+
export * from "./data-table-faceted-filter";
|
|
5
|
+
export * from "./data-table-pagination";
|
|
6
|
+
export * from "./data-table-toolbar";
|
|
7
|
+
export * from "./data-table-view-option";
|
|
8
|
+
export * from "./types";
|
|
9
|
+
export * from "./use-data-table";
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ColumnDef,
|
|
3
|
+
ColumnFiltersState,
|
|
4
|
+
FilterFn,
|
|
5
|
+
OnChangeFn,
|
|
6
|
+
PaginationState,
|
|
7
|
+
Row,
|
|
8
|
+
SortingState,
|
|
9
|
+
Table,
|
|
10
|
+
TableState,
|
|
11
|
+
VisibilityState,
|
|
12
|
+
} from "@tanstack/react-table";
|
|
13
|
+
import type React from "react";
|
|
14
|
+
|
|
15
|
+
export type FacetedFilterConfig = {
|
|
16
|
+
columnId: string;
|
|
17
|
+
title: string;
|
|
18
|
+
compact?: boolean;
|
|
19
|
+
options: {
|
|
20
|
+
label: string;
|
|
21
|
+
value: string | number | boolean;
|
|
22
|
+
icon?: React.ComponentType<{ className?: string }>;
|
|
23
|
+
}[];
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export interface DataTableProps<TData, TValue> {
|
|
27
|
+
columns: ColumnDef<TData, TValue>[];
|
|
28
|
+
data: TData[];
|
|
29
|
+
|
|
30
|
+
// Styling Hooks
|
|
31
|
+
className?: string; // Appended to table wrap
|
|
32
|
+
containerClassName?: string; // Appended to outer wrapper
|
|
33
|
+
/**
|
|
34
|
+
* Appended to the scrollable box that wraps the table itself.
|
|
35
|
+
*
|
|
36
|
+
* Needed for `stickyHeader`: the box already scrolls horizontally, and CSS
|
|
37
|
+
* forces the vertical axis to `auto` alongside it, so it — not the page — is
|
|
38
|
+
* what a sticky header anchors to. Give it a height (e.g. `max-h-[70vh]`) or
|
|
39
|
+
* the header has nothing to stick within and scrolls away with the page.
|
|
40
|
+
*/
|
|
41
|
+
tableContainerClassName?: string;
|
|
42
|
+
/** ตรึงแถบเปลี่ยนหน้าไว้ล่างสุดของจอเสมอ ใช้คู่กับ `stickyHeader` */
|
|
43
|
+
stickyPagination?: boolean;
|
|
44
|
+
|
|
45
|
+
// Accessibility & UI
|
|
46
|
+
stickyHeader?: boolean;
|
|
47
|
+
stickyEdgeColumns?: boolean;
|
|
48
|
+
emptyStateComponent?: React.ReactNode;
|
|
49
|
+
|
|
50
|
+
// Filters & Toolbar
|
|
51
|
+
facetedFilters?: FacetedFilterConfig[];
|
|
52
|
+
actionsComponent?: (table: Table<TData>) => React.ReactNode;
|
|
53
|
+
globalFilterKeys?: string[];
|
|
54
|
+
globalFilterFn?: FilterFn<TData>;
|
|
55
|
+
globalFilterPlaceholder?: string;
|
|
56
|
+
toolbarFilters?: (table: Table<TData>) => React.ReactNode;
|
|
57
|
+
preservedFilterIds?: string[];
|
|
58
|
+
|
|
59
|
+
// State & Visibility
|
|
60
|
+
initialVisibility?: VisibilityState;
|
|
61
|
+
initialColumnFilters?: ColumnFiltersState;
|
|
62
|
+
columnFilters?: ColumnFiltersState;
|
|
63
|
+
initialState?: Partial<TableState>;
|
|
64
|
+
|
|
65
|
+
// Pagination & Sorting (Server-side support)
|
|
66
|
+
pageCount?: number;
|
|
67
|
+
rowCount?: number;
|
|
68
|
+
/**
|
|
69
|
+
* ควบคุม pagination จากภายนอก (ใช้คู่กับ manualPagination)
|
|
70
|
+
* จำเป็นเมื่อ state จริงอยู่ที่อื่น เช่นใน URL — ไม่งั้นเลขหน้าใน UI จะไม่ตรงกับข้อมูลที่ server ส่งมา
|
|
71
|
+
*/
|
|
72
|
+
pagination?: PaginationState;
|
|
73
|
+
/** ควบคุม sorting จากภายนอก (ใช้คู่กับ manualSorting) */
|
|
74
|
+
sorting?: SortingState;
|
|
75
|
+
/** ควบคุมคำค้นจากภายนอก (ใช้คู่กับ manualFiltering) */
|
|
76
|
+
globalFilter?: string;
|
|
77
|
+
/**
|
|
78
|
+
* เรียกเมื่อผู้ใช้พิมพ์ในช่องค้นหา
|
|
79
|
+
* ต้องส่งมาด้วยเมื่อเปิด manualFiltering ไม่งั้นช่องค้นหาจะไม่ทำอะไรเลย
|
|
80
|
+
* เพราะ TanStack ถูกสั่งไม่ให้กรองเองแล้ว
|
|
81
|
+
*/
|
|
82
|
+
onGlobalFilterChange?: (value: string) => void;
|
|
83
|
+
manualPagination?: boolean;
|
|
84
|
+
manualSorting?: boolean;
|
|
85
|
+
manualFiltering?: boolean;
|
|
86
|
+
onPaginationChange?: OnChangeFn<PaginationState>;
|
|
87
|
+
onPageSizeChange?: (pageSize: number) => void;
|
|
88
|
+
onSortingChange?: OnChangeFn<SortingState>;
|
|
89
|
+
onColumnFiltersChange?: OnChangeFn<ColumnFiltersState>;
|
|
90
|
+
|
|
91
|
+
// Customization
|
|
92
|
+
getRowClassName?: (row: Row<TData>) => string;
|
|
93
|
+
getSubRows?: (originalRow: TData) => TData[] | undefined;
|
|
94
|
+
getRowId?: (originalRow: TData) => string;
|
|
95
|
+
initialPageSize?: number;
|
|
96
|
+
meta?: Record<string, unknown>;
|
|
97
|
+
isLoading?: boolean;
|
|
98
|
+
|
|
99
|
+
// Advanced
|
|
100
|
+
enableColumnResizing?: boolean;
|
|
101
|
+
enableRowSelection?: boolean | ((row: Row<TData>) => boolean);
|
|
102
|
+
getFacetedUniqueValues?: (table: Table<TData>, columnId: string) => () => Map<unknown, number>;
|
|
103
|
+
pageSizeOptions?: number[];
|
|
104
|
+
onReorder?: (newData: TData[]) => void;
|
|
105
|
+
renderCustomView?: (table: Table<TData>) => React.ReactNode;
|
|
106
|
+
}
|