Skip to content

Commit 2668553

Browse files
committed
refactor ColumnPinningTable to use createColumnHelper for improved type safety. Enhance TableGrid with stricter typing for columns and update fuzzy search implementation for better key handling.
1 parent 7ca0b77 commit 2668553

File tree

10 files changed

+268
-101
lines changed

10 files changed

+268
-101
lines changed

bun.lockb

-29 Bytes
Binary file not shown.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"dependencies": {
1212
"clsx": "^2.1.1",
1313
"fuse.js": "^7.0.0",
14+
"jotai": "^2.11.0",
1415
"next": "15.1.2",
1516
"react": "^19.0.0",
1617
"react-dom": "^19.0.0",

src/app/page.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@ import FuzzySearchFilter from "@/components/containers/fuzzy-search-filter";
55
import ColumnVisibilityTable from "@/components/containers/column-visibility-table";
66
import ColumnPinningTable from "@/components/containers/toggle-column-pinning-table";
77
import CustomizedTable from "@/components/containers/customized-table";
8+
import ColumnResizingTable from "@/components/containers/column-resizing-table";
89

910
export default function Home() {
1011
return (
1112
<main className="min-h-screen p-4">
13+
<ColumnResizingTable />
1214
<div className="space-y-8">
1315
<div className="border rounded-lg">
1416
<CustomizedTable />

src/components/containers/column-pinning-table.tsx

Lines changed: 20 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
"use client";
22
import TableGrid from "@/components/ui/table-grid/table-grid";
33
import dummyData from "@/data/dummy.json";
4-
import type { Column } from "@/components/ui/table-grid/table-grid";
54
import { useTableGrid } from "@/hooks/use-table-grid";
5+
import { createColumnHelper } from "@/components/ui/table-grid/column-helper";
66

7-
interface DataItem extends Record<string, unknown> {
7+
interface DataItem {
88
id: number;
99
name: string;
1010
age: number;
@@ -18,75 +18,39 @@ interface DataItem extends Record<string, unknown> {
1818
phone: string;
1919
}
2020

21-
const columns: Column<DataItem>[] = [
22-
{
23-
id: "id",
21+
const columnHelper = createColumnHelper<DataItem>();
22+
23+
const columns = [
24+
columnHelper.accessor("id", {
2425
header: "ID",
25-
accessorKey: "id",
2626
sortable: true,
2727
pinned: "left",
28-
},
29-
{
30-
id: "name",
28+
}),
29+
columnHelper.accessor("name", {
3130
header: "Name",
32-
accessorKey: "name",
33-
sortable: true,
34-
},
35-
{
36-
id: "department",
37-
header: "Department",
38-
accessorKey: "department",
3931
sortable: true,
40-
},
41-
{
42-
id: "role",
43-
header: "Role",
44-
accessorKey: "role",
45-
sortable: true,
46-
},
47-
{
48-
id: "salary",
32+
}),
33+
columnHelper.accessor("salary", {
4934
header: "Salary",
50-
accessorKey: "salary",
5135
sortable: true,
5236
cell: ({ value }) => `$${(value as number).toLocaleString()}`,
53-
},
54-
{
55-
id: "status",
37+
}),
38+
columnHelper.accessor("status", {
5639
header: "Status",
57-
accessorKey: "status",
5840
sortable: true,
59-
},
60-
{
61-
id: "location",
41+
}),
42+
columnHelper.accessor("location", {
6243
header: "Location",
63-
accessorKey: "location",
64-
sortable: true,
65-
},
66-
{
67-
id: "age",
68-
header: "Age",
69-
accessorKey: "age",
7044
sortable: true,
71-
},
72-
{
73-
id: "email",
74-
header: "Email",
75-
accessorKey: "email",
45+
}),
46+
columnHelper.accessor("joinDate", {
47+
header: "Join Date",
7648
sortable: true,
77-
},
78-
{
79-
id: "phone",
49+
}),
50+
columnHelper.accessor("phone", {
8051
header: "Phone",
81-
accessorKey: "phone",
82-
sortable: true,
83-
},
84-
{
85-
id: "joinDate",
86-
header: "Join Date",
87-
accessorKey: "joinDate",
8852
sortable: true,
89-
},
53+
}),
9054
];
9155

9256
const ColumnPinningTable = () => {
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
"use client";
2+
import { useMemo } from "react";
3+
import TableGrid from "@/components/ui/table-grid/table-grid";
4+
import dummyData from "@/data/dummy.json";
5+
import { useTableGrid } from "@/hooks/use-table-grid";
6+
import { createColumnHelper } from "@/components/ui/table-grid/column-helper";
7+
8+
interface DataItem {
9+
id: number;
10+
name: string;
11+
age: number;
12+
email: string;
13+
department: string;
14+
role: string;
15+
salary: number;
16+
status: string;
17+
location: string;
18+
joinDate: string;
19+
phone: string;
20+
[key: string]: unknown;
21+
}
22+
23+
const columnHelper = createColumnHelper<DataItem>();
24+
25+
const ColumnResizingTable = () => {
26+
const columns = useMemo(
27+
() => [
28+
columnHelper.accessor("id", {
29+
header: "ID",
30+
sortable: true,
31+
}),
32+
columnHelper.accessor("name", {
33+
header: "Name",
34+
sortable: true,
35+
}),
36+
columnHelper.accessor("email", {
37+
header: "Email",
38+
sortable: true,
39+
}),
40+
columnHelper.accessor("department", {
41+
header: "Department",
42+
sortable: true,
43+
}),
44+
columnHelper.accessor("role", {
45+
header: "Role",
46+
sortable: true,
47+
}),
48+
columnHelper.accessor("salary", {
49+
header: "Salary",
50+
sortable: true,
51+
}),
52+
],
53+
[]
54+
);
55+
56+
const { filteredData, handleSort, sortColumn, sortDirection } =
57+
useTableGrid<DataItem>({
58+
data: dummyData,
59+
columns,
60+
initialState: {
61+
sortColumn: "name",
62+
sortDirection: "asc",
63+
},
64+
onStateChange: (state) => {
65+
console.log("Table state changed:", state);
66+
},
67+
});
68+
69+
return (
70+
<div className="p-4">
71+
<div className="flex justify-between items-center mb-4">
72+
<h2 className="text-2xl font-bold">Column Resizing Table</h2>
73+
</div>
74+
75+
<TableGrid<DataItem>
76+
columns={columns}
77+
data={filteredData}
78+
gridTemplateColumns="1fr 1fr 1fr 1fr 1fr 1fr"
79+
maxHeight="400px"
80+
variant="classic"
81+
onSort={handleSort}
82+
sortColumn={sortColumn}
83+
sortDirection={sortDirection}
84+
/>
85+
</div>
86+
);
87+
};
88+
89+
export default ColumnResizingTable;
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import type { Column } from './types'
2+
3+
export type DisplayColumnDef<TData, TValue = unknown> = Omit<Column<TData>, 'id' | 'accessorKey'> & {
4+
id?: keyof TData
5+
cell?: (props: { value: TValue; row: TData }) => React.ReactNode
6+
}
7+
8+
export type GroupColumnDef<TData, TValue = unknown> = DisplayColumnDef<TData, TValue> & {
9+
columns?: Column<TData>[]
10+
}
11+
12+
export interface ColumnHelper<TData> {
13+
accessor: <TKey extends keyof TData>(
14+
accessorKey: TKey,
15+
columnDef?: DisplayColumnDef<TData, TData[TKey]>
16+
) => Column<TData>
17+
18+
display: (columnDef: DisplayColumnDef<TData>) => Column<TData>
19+
20+
group: (columnDef: GroupColumnDef<TData>) => Column<TData>
21+
}
22+
23+
export function createColumnHelper<TData>(): ColumnHelper<TData> {
24+
return {
25+
accessor: <TKey extends keyof TData>(
26+
accessorKey: TKey,
27+
columnDef: Partial<DisplayColumnDef<TData, TData[TKey]>> = {}
28+
): Column<TData> => ({
29+
id: accessorKey,
30+
accessorKey,
31+
header: columnDef.header ?? String(accessorKey),
32+
sortable: columnDef.sortable ?? true,
33+
className: columnDef.className,
34+
width: columnDef.width,
35+
group: columnDef.group,
36+
pinned: columnDef.pinned,
37+
cell: columnDef.cell,
38+
}),
39+
40+
display: (columnDef) => ({
41+
id: columnDef.id ?? String(Math.random()) as keyof TData,
42+
accessorKey: '' as keyof TData,
43+
...columnDef,
44+
}),
45+
46+
group: (columnDef) => ({
47+
id: columnDef.id ?? String(Math.random()) as keyof TData,
48+
accessorKey: '' as keyof TData,
49+
...columnDef,
50+
}),
51+
}
52+
}

src/components/ui/table-grid/table-grid.tsx

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,10 @@ function TableGridComponent<T extends Record<string, unknown>>(
7878
// Fuzzy search setup
7979
const fuse = useMemo(() => {
8080
if (!enableFuzzySearch) return null
81+
const searchKeys = (fuzzySearchKeys || columns.map(col => col.accessorKey))
82+
.map(key => String(key)) as string[]
8183
return new Fuse(data, {
82-
keys: fuzzySearchKeys || columns.map(col => col.accessorKey as string),
84+
keys: searchKeys,
8385
threshold: fuzzySearchThreshold,
8486
})
8587
}, [data, enableFuzzySearch, fuzzySearchKeys, columns, fuzzySearchThreshold])
@@ -173,29 +175,26 @@ function TableGridComponent<T extends Record<string, unknown>>(
173175
}
174176

175177
if (column.cell) {
176-
const defaultUpdateData: UpdateDataFn<T> = () => undefined
177-
const updateData = meta?.updateData || onRowChange || defaultUpdateData
178+
const defaultUpdateData: UpdateDataFn<T> = () => undefined;
179+
const updateData = meta?.updateData || onRowChange || defaultUpdateData;
178180

179181
return column.cell({
180182
value: getRowValue(row, column.accessorKey),
183+
row,
181184
onChange: (value) => {
182-
updateData(rowIndex, column.accessorKey, value)
185+
updateData(rowIndex, column.accessorKey, value);
183186
},
184187
onDelete: () => onRowDelete?.(rowIndex),
185-
row: {
186-
original: row,
187-
index: rowIndex,
188-
},
189-
table: {
188+
table: meta && {
190189
options: {
191190
meta: {
192191
updateData,
193192
},
194193
},
195194
},
196-
})
195+
});
197196
}
198-
return String(getRowValue(row, column.accessorKey))
197+
return String(getRowValue(row, column.accessorKey));
199198
}
200199

201200
const renderEmptyState = () => {
@@ -306,7 +305,7 @@ function TableGridComponent<T extends Record<string, unknown>>(
306305
{/* Left Pinned Columns */}
307306
{columns.filter(col => col.pinned === 'left').map((column) => (
308307
<div
309-
key={`pin-left-${column.id}`}
308+
key={`pin-left-${String(column.id)}`}
310309
className={cn(
311310
styles.headerCell(),
312311
'sticky left-0 z-20 bg-gray-100 dark:bg-gray-700',
@@ -321,7 +320,7 @@ function TableGridComponent<T extends Record<string, unknown>>(
321320
{/* Unpinned Columns */}
322321
{columns.filter(col => !col.pinned).map((column) => (
323322
<div
324-
key={column.id}
323+
key={String(column.id)}
325324
className={cn(
326325
styles.headerCell(),
327326
column.className,
@@ -335,7 +334,7 @@ function TableGridComponent<T extends Record<string, unknown>>(
335334
{/* Right Pinned Columns */}
336335
{columns.filter(col => col.pinned === 'right').map((column) => (
337336
<div
338-
key={`pin-right-${column.id}`}
337+
key={`pin-right-${String(column.id)}`}
339338
className={cn(
340339
styles.headerCell(),
341340
'sticky right-0 z-20 bg-gray-100 dark:bg-gray-700',
@@ -362,7 +361,7 @@ function TableGridComponent<T extends Record<string, unknown>>(
362361
{/* Left Pinned Cells */}
363362
{columns.filter(col => col.pinned === 'left').map((column) => (
364363
<div
365-
key={`pin-left-${column.id}`}
364+
key={`pin-left-${String(column.id)}`}
366365
className={cn(
367366
styles.cell(),
368367
'sticky left-0 z-10 bg-white dark:bg-gray-800',
@@ -377,7 +376,7 @@ function TableGridComponent<T extends Record<string, unknown>>(
377376
{/* Unpinned Cells */}
378377
{columns.filter(col => !col.pinned).map((column) => (
379378
<div
380-
key={`cell-${column.id}`}
379+
key={`cell-${String(column.id)}`}
381380
className={cn(
382381
styles.cell(),
383382
column.className,
@@ -391,7 +390,7 @@ function TableGridComponent<T extends Record<string, unknown>>(
391390
{/* Right Pinned Cells */}
392391
{columns.filter(col => col.pinned === 'right').map((column) => (
393392
<div
394-
key={`pin-right-${column.id}`}
393+
key={`pin-right-${String(column.id)}`}
395394
className={cn(
396395
styles.cell(),
397396
'sticky right-0 z-10 bg-white dark:bg-gray-800',
@@ -415,7 +414,8 @@ function TableGridComponent<T extends Record<string, unknown>>(
415414
)
416415
}
417416

418-
const TableGrid = forwardRef(TableGridComponent) as unknown as (<T extends Record<string, unknown>>(
417+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-constraint, @typescript-eslint/no-explicit-any
418+
const TableGrid = forwardRef(TableGridComponent) as unknown as (<T extends any>(
419419
props: TableProps<T> & { ref?: React.ForwardedRef<HTMLDivElement> }
420420
) => ReactNode) & { displayName?: string }
421421

0 commit comments

Comments
 (0)