Skip to content

Commit 8756863

Browse files
committed
port DataTable functionality from Pro
1 parent 326d2de commit 8756863

File tree

6 files changed

+210
-15
lines changed

6 files changed

+210
-15
lines changed

src/components/Table/DataTable.tsx

Lines changed: 109 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,77 @@
11
"use client";
22

3-
import { ColumnDef, flexRender, getCoreRowModel, useReactTable } from "@tanstack/react-table";
4-
import React, { useState } from "react";
3+
import React from "react";
4+
5+
import {
6+
ColumnDef,
7+
CoreOptions,
8+
ExpandedState,
9+
flexRender,
10+
getCoreRowModel,
11+
getExpandedRowModel,
12+
OnChangeFn,
13+
RowSelectionState,
14+
SortingState,
15+
useReactTable
16+
} from "@tanstack/react-table";
17+
518
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "./Table";
19+
import { SortableHeader } from "./Sorting";
20+
import { cn } from "../..";
621

722
interface DataTableProps<TData, TValue> {
823
columns: ColumnDef<TData, TValue>[];
924
data: TData[];
25+
filters?: Record<string, string>;
26+
resetFilters?: () => void;
27+
getRowId?: CoreOptions<TData>["getRowId"];
28+
rowSelection?: RowSelectionState;
29+
onRowSelectionChange?: OnChangeFn<RowSelectionState>;
30+
sorting?: SortingState;
31+
onSortingChange?: OnChangeFn<SortingState>;
32+
expanded?: ExpandedState;
33+
onExpandedChange?: OnChangeFn<ExpandedState>;
34+
getSubRows?: (row: TData) => TData[];
1035
}
1136

12-
export function DataTable<TData, TValue>({ columns, data }: DataTableProps<TData, TValue>) {
13-
const [rowSelection, setRowSelection] = useState({});
37+
export function DataTable<TData, TValue>({
38+
columns,
39+
data,
40+
filters,
41+
resetFilters,
42+
sorting,
43+
onSortingChange,
44+
expanded,
45+
onExpandedChange,
46+
getSubRows,
47+
getRowId,
48+
rowSelection = {},
49+
onRowSelectionChange
50+
}: DataTableProps<TData, TValue>) {
1451
const table = useReactTable({
1552
data,
16-
columns,
17-
onRowSelectionChange: setRowSelection,
53+
columns: columns.map((col) => {
54+
// if col.enableSorting is not defined, set it to false
55+
if (col.enableSorting === undefined) {
56+
col.enableSorting = false;
57+
}
58+
return col;
59+
}),
60+
manualSorting: true,
61+
onRowSelectionChange,
62+
onSortingChange,
63+
enableSortingRemoval: false,
64+
enableMultiSort: false,
65+
enableRowSelection: true,
66+
onExpandedChange,
67+
getRowId,
68+
getSubRows,
1869
getCoreRowModel: getCoreRowModel(),
70+
getExpandedRowModel: getExpandedRowModel(),
1971
state: {
20-
rowSelection
72+
rowSelection,
73+
sorting,
74+
expanded
2175
}
2276
});
2377

@@ -28,16 +82,29 @@ export function DataTable<TData, TValue>({ columns, data }: DataTableProps<TData
2882
{table.getHeaderGroups().map((headerGroup) => (
2983
<TableRow key={headerGroup.id}>
3084
{headerGroup.headers.map((header) => {
85+
const canSort = header.column.getCanSort();
3186
return (
3287
<TableHead
33-
className="text-theme-text-secondary"
88+
className={cn(
89+
header.column.columnDef.meta?.className,
90+
`${
91+
canSort ? "p-0" : ""
92+
} bg-theme-surface-tertiary font-semibold text-theme-text-secondary`
93+
)}
3494
key={header.id}
35-
// @ts-expect-error width doesnt exist on the type, and would need a global fragmentation
36-
style={{ width: header.column.columnDef.meta?.width || undefined }}
95+
style={{
96+
width: header.column.columnDef.meta?.width
97+
}}
3798
>
38-
{header.isPlaceholder
39-
? null
40-
: flexRender(header.column.columnDef.header, header.getContext())}
99+
{canSort ? (
100+
<SortableHeader header={header}>
101+
{header.isPlaceholder
102+
? null
103+
: flexRender(header.column.columnDef.header, header.getContext())}
104+
</SortableHeader>
105+
) : header.isPlaceholder ? null : (
106+
flexRender(header.column.columnDef.header, header.getContext())
107+
)}
41108
</TableHead>
42109
);
43110
})}
@@ -53,12 +120,40 @@ export function DataTable<TData, TValue>({ columns, data }: DataTableProps<TData
53120
data-state={row.getIsSelected() && "selected"}
54121
>
55122
{row.getVisibleCells().map((cell) => (
56-
<TableCell className="font-medium text-theme-text-primary" key={cell.id}>
123+
<TableCell
124+
className={cn(
125+
"font-medium text-theme-text-primary",
126+
cell.column.columnDef.meta?.className
127+
)}
128+
key={cell.id}
129+
>
57130
{flexRender(cell.column.columnDef.cell, cell.getContext())}
58131
</TableCell>
59132
))}
60133
</TableRow>
61134
))
135+
) : filters && Object.keys(filters).length ? (
136+
<TableRow>
137+
<TableCell
138+
colSpan={columns.length}
139+
className="h-24 bg-theme-surface-primary p-9 text-center"
140+
>
141+
<p className="text-text-lg text-theme-text-secondary">
142+
No items match your current selection.
143+
</p>
144+
<p className="text-text-lg text-theme-text-secondary">
145+
Refine your filters and try again.
146+
</p>
147+
<div className="mt-5">
148+
<p
149+
className="inline-block cursor-pointer text-text-lg text-theme-text-brand underline"
150+
onClick={resetFilters}
151+
>
152+
Clear filters
153+
</p>
154+
</div>
155+
</TableCell>
156+
</TableRow>
62157
) : (
63158
<TableRow>
64159
<TableCell
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import React, { useState, createContext, ReactNode, SetStateAction, Dispatch } from "react";
2+
import { RowSelectionState } from "@tanstack/react-table";
3+
4+
export function createDataTableConsumerContext<CtxProviderProps extends { children: ReactNode }>() {
5+
type CtxValue = {
6+
rowSelection: RowSelectionState;
7+
setRowSelection: Dispatch<SetStateAction<RowSelectionState>>;
8+
9+
selectedRowIDs: string[];
10+
selectedRowCount: number;
11+
};
12+
13+
const Context = createContext<CtxValue | null>(null);
14+
15+
function useContext() {
16+
const ctx = React.useContext(Context);
17+
if (!ctx) throw new Error("DataTableConsumerContext must be used within its Provider");
18+
return ctx;
19+
}
20+
21+
function ContextProvider({ children }: CtxProviderProps) {
22+
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
23+
24+
const selectedRowIDs = getSelectedRowIDs(rowSelection);
25+
const selectedRowCount = selectedRowIDs.length;
26+
27+
return (
28+
<Context.Provider value={{ rowSelection, setRowSelection, selectedRowIDs, selectedRowCount }}>
29+
{children}
30+
</Context.Provider>
31+
);
32+
}
33+
34+
return {
35+
Context,
36+
ContextProvider,
37+
useContext
38+
};
39+
}
40+
41+
export function countSelectedRows(rowSelection: RowSelectionState): number {
42+
return Object.values(rowSelection).reduce((acc, curr) => acc + Number(curr), 0);
43+
}
44+
45+
export function getSelectedRowIDs(rowSelection: RowSelectionState): string[] {
46+
return Object.entries(rowSelection)
47+
.filter(([, selected]) => selected)
48+
.map(([id]) => id);
49+
}

src/components/Table/Sorting.tsx

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import React from "react";
2+
import { Header, RowData /*, SortDirection*/ } from "@tanstack/react-table";
3+
// TODO FIXME: importing directly not allowed?
4+
// import ArrowUp from "../../assets/icons/arrow-up.svg";
5+
// import ArrowDown from "../../assets/icons/arrow-down.svg";
6+
import { PropsWithChildren } from "react";
7+
8+
/*
9+
type Props = {
10+
direction: SortDirection | false;
11+
};
12+
13+
function SortingArrow({ direction }: Props) {
14+
if (direction === false) return null;
15+
16+
const Comp = direction === "asc" ? ArrowUp : ArrowDown;
17+
18+
return <Comp className="h-4 w-4 shrink-0" />;
19+
}
20+
*/
21+
22+
interface HeaderProps<TData extends RowData> {
23+
header: Header<TData, unknown>;
24+
}
25+
26+
export function SortableHeader<TData extends RowData>({
27+
header,
28+
children
29+
}: PropsWithChildren<HeaderProps<TData>>) {
30+
return (
31+
<button
32+
className={`${
33+
header.column.getIsSorted() ? "text-theme-text-primary" : ""
34+
} flex h-full w-full items-center gap-1 px-4 py-2 text-text-sm transition-all duration-100 hover:bg-gray-200`}
35+
onClick={header.column.getToggleSortingHandler()}
36+
>
37+
<span>{children}</span>
38+
{/* <SortingArrow direction={header.column.getIsSorted()} /> */}
39+
</button>
40+
);
41+
}

src/components/Table/index.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
export * from "./Table";
22
export * from "./DataTable";
3+
export * from "./DataTableConsumerContext";
4+
export * from "./Sorting";

src/components/client.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
export * from "./Sidebar";
33
export * from "./Avatar";
44
export * from "./Dropdown";
5-
export * from "./Table/DataTable";
5+
export * from "./Table";
66
export * from "./Sheet";
77
export * from "./Tabs";
88
export * from "./Collapsible";

src/table.d.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import "@tanstack/react-table"; //or vue, svelte, solid, qwik, etc.
2+
3+
declare module "@tanstack/react-table" {
4+
interface ColumnMeta {
5+
width?: string;
6+
className?: string;
7+
}
8+
}

0 commit comments

Comments
 (0)