Skip to content

Commit 397c3b1

Browse files
fix(data-table): enhance column pinning styles and improve sorting synchronization
1 parent 7440283 commit 397c3b1

File tree

1 file changed

+74
-38
lines changed

1 file changed

+74
-38
lines changed

resources/js/components/table/data-table.tsx

Lines changed: 74 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { buildParams } from '@/lib/utils';
77
import { PaginationMeta } from '@/types';
88
import { Link, router } from '@inertiajs/react';
99
import {
10+
Column,
1011
ColumnDef,
1112
flexRender,
1213
getCoreRowModel,
@@ -23,17 +24,44 @@ interface DataTableProps<TData, TValue> {
2324
pagination: PaginationMeta<TData[]>;
2425
}
2526

27+
function getCommonPinningStyles<TData>({
28+
column,
29+
withBorder = false,
30+
}: {
31+
column: Column<TData>;
32+
withBorder?: boolean;
33+
}): React.CSSProperties {
34+
const isPinned = column.getIsPinned();
35+
const isLastLeftPinnedColumn = isPinned === 'left' && column.getIsLastColumn('left');
36+
const isFirstRightPinnedColumn = isPinned === 'right' && column.getIsFirstColumn('right');
37+
38+
const leftPinnedBoxShadow = '-4px 0 4px -4px var(--border) inset';
39+
const rightPinnedBoxShadow = '4px 0 4px -4px var(--border) inset';
40+
41+
const rightPinnedShadow = isFirstRightPinnedColumn ? rightPinnedBoxShadow : undefined;
42+
const pinnedColumnShadow = isLastLeftPinnedColumn ? leftPinnedBoxShadow : rightPinnedShadow;
43+
44+
return {
45+
boxShadow: withBorder ? pinnedColumnShadow : undefined,
46+
left: isPinned === 'left' ? `${column.getStart('left')}px` : undefined,
47+
right: isPinned === 'right' ? `${column.getAfter('right')}px` : undefined,
48+
opacity: isPinned ? 0.97 : 1,
49+
position: isPinned ? 'sticky' : 'relative',
50+
background: 'var(--background)',
51+
width: column.getSize(),
52+
zIndex: isPinned ? 1 : undefined,
53+
};
54+
}
55+
2656
export function DataTable<TData, TValue>({ columns, data, pagination }: Readonly<DataTableProps<TData, TValue>>) {
27-
const getInitialSorting = (): SortingState => {
57+
const [sorting, setSorting] = useState<SortingState>(() => {
2858
const params = new URLSearchParams(globalThis.location.search);
2959
const sort_by = params.get('sort_by');
3060
const sort_dir = params.get('sort_dir');
3161
if (!sort_by) return [];
3262

3363
return [{ id: sort_by, desc: sort_dir === 'desc' }];
34-
};
35-
36-
const [sorting, setSorting] = useState<SortingState>(getInitialSorting);
64+
});
3765

3866
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({});
3967

@@ -43,39 +71,48 @@ export function DataTable<TData, TValue>({ columns, data, pagination }: Readonly
4371
};
4472

4573
const [search, setSearch] = useState<string>(getInitialSearch());
74+
const [path] = useState<string | undefined>(pagination.path);
4675
const searchDebounce = useRef<NodeJS.Timeout | undefined>(undefined);
4776

48-
const path = pagination.path;
77+
const table = useReactTable({
78+
columns,
79+
data,
80+
getCoreRowModel: getCoreRowModel(),
81+
manualPagination: true, // turn off client-side pagination
82+
manualSorting: true, // turn off client-side sorting
83+
pageCount: pagination.last_page ?? Math.ceil((pagination.total ?? 0) / (pagination.per_page ?? 1)),
84+
initialState: {
85+
pagination: {
86+
pageIndex: Math.max((pagination.current_page ?? 1) - 1, 0),
87+
pageSize: pagination.per_page,
88+
},
89+
columnPinning: { left: ['id'], right: ['actions'] },
90+
},
91+
onSortingChange: setSorting,
92+
onColumnVisibilityChange: setColumnVisibility,
93+
state: { sorting, columnVisibility },
94+
});
4995

96+
// Sync sorting state with server via Inertia
5097
useEffect(() => {
5198
if (!path) return;
5299

53100
const params = new URLSearchParams(globalThis.location.search);
54101
const currentSortBy = params.get('sort_by');
55102
const currentSortDir = params.get('sort_dir');
56103

57-
if (!sorting || sorting.length === 0) {
58-
if (!currentSortBy && !currentSortDir) return;
59-
60-
router.get(path, buildParams({ sort_by: undefined, sort_dir: undefined }), {
61-
preserveState: true,
62-
replace: true,
63-
});
64-
return;
65-
}
104+
const sort = sorting?.[0];
105+
const desiredSortBy = sort ? String(sort.id) : undefined;
106+
let desiredSortDir: string | undefined = undefined;
66107

67-
const sort = sorting[0];
68-
const sortBy = String(sort.id);
69-
const sortDir = sort.desc ? 'desc' : 'asc';
108+
if (sort) desiredSortDir = sort.desc ? 'desc' : 'asc';
70109

71-
if (currentSortBy === sortBy && currentSortDir === sortDir) return;
110+
if (currentSortBy === desiredSortBy && currentSortDir === desiredSortDir) return;
72111

73-
if (sortBy) {
74-
router.get(path, buildParams({ sort_by: sortBy, sort_dir: sortDir }), {
75-
preserveState: true,
76-
replace: true,
77-
});
78-
}
112+
router.get(path, buildParams({ sort_by: desiredSortBy, sort_dir: desiredSortDir }), {
113+
preserveState: true,
114+
replace: true,
115+
});
79116
}, [sorting, path]);
80117

81118
useEffect(() => {
@@ -98,18 +135,6 @@ export function DataTable<TData, TValue>({ columns, data, pagination }: Readonly
98135
return () => globalThis.clearTimeout(searchDebounce.current);
99136
}, [search, path]);
100137

101-
const table = useReactTable({
102-
data,
103-
columns,
104-
getCoreRowModel: getCoreRowModel(),
105-
onSortingChange: setSorting,
106-
onColumnVisibilityChange: setColumnVisibility,
107-
state: {
108-
sorting,
109-
columnVisibility,
110-
},
111-
});
112-
113138
return (
114139
<div className="mx-auto w-full flex-col space-y-4">
115140
<div className="flex items-center py-4">
@@ -146,7 +171,13 @@ export function DataTable<TData, TValue>({ columns, data, pagination }: Readonly
146171
<TableRow key={headerGroup.id}>
147172
{headerGroup.headers.map((header) => {
148173
return (
149-
<TableHead key={header.id}>
174+
<TableHead
175+
key={header.id}
176+
colSpan={header.colSpan}
177+
style={{
178+
...getCommonPinningStyles({ column: header.column }),
179+
}}
180+
>
150181
{header.isPlaceholder
151182
? null
152183
: flexRender(header.column.columnDef.header, header.getContext())}
@@ -161,7 +192,12 @@ export function DataTable<TData, TValue>({ columns, data, pagination }: Readonly
161192
table.getRowModel().rows.map((row) => (
162193
<TableRow key={row.id} data-state={row.getIsSelected() && 'selected'}>
163194
{row.getVisibleCells().map((cell) => (
164-
<TableCell key={cell.id}>
195+
<TableCell
196+
key={cell.id}
197+
style={{
198+
...getCommonPinningStyles({ column: cell.column }),
199+
}}
200+
>
165201
{flexRender(cell.column.columnDef.cell, cell.getContext())}
166202
</TableCell>
167203
))}

0 commit comments

Comments
 (0)