Skip to content

Commit dac3259

Browse files
feat: Table Components (#50)
1 parent 21fce18 commit dac3259

File tree

9 files changed

+355
-0
lines changed

9 files changed

+355
-0
lines changed

.changeset/fluffy-hairs-trade.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@zenml-io/react-component-library": minor
3+
---
4+
5+
add table components

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@
8787
"@radix-ui/react-avatar": "^1.0.4",
8888
"@radix-ui/react-dropdown-menu": "^2.0.6",
8989
"@radix-ui/react-slot": "^1.0.2",
90+
"@tanstack/react-table": "^8.15.3",
9091
"class-variance-authority": "^0.7.0",
9192
"clsx": "^2.1.0",
9293
"tailwind-merge": "^2.2.2"

pnpm-lock.yaml

Lines changed: 20 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import { Meta } from "@storybook/react";
2+
import { StoryObj } from "@storybook/react";
3+
import React from "react";
4+
import { DataTable } from "./index";
5+
import { ColumnDef } from "@tanstack/react-table";
6+
7+
type DummyData = {
8+
id: number;
9+
name: string;
10+
age: number;
11+
};
12+
13+
const cols: ColumnDef<DummyData, unknown>[] = [
14+
{
15+
id: "id",
16+
header: "ID",
17+
accessorKey: "id"
18+
},
19+
{
20+
id: "name",
21+
header: "Name",
22+
accessorKey: "name"
23+
},
24+
{
25+
id: "age",
26+
header: "Age",
27+
accessorKey: "age"
28+
}
29+
];
30+
31+
const data: DummyData[] = [
32+
{
33+
id: 1,
34+
name: "John Doe",
35+
age: 25
36+
},
37+
{
38+
id: 2,
39+
name: "Jane Doe",
40+
age: 24
41+
},
42+
{
43+
id: 3,
44+
name: "John Smith",
45+
age: 26
46+
},
47+
{
48+
id: 4,
49+
name: "Jane Smith",
50+
age: 27
51+
}
52+
];
53+
54+
const meta = {
55+
title: "Elements/Table/DataTable",
56+
component: DataTable,
57+
58+
parameters: {
59+
layout: "centered"
60+
},
61+
decorators: [
62+
(Story) => (
63+
<div className="w-[550px]">
64+
<Story />
65+
</div>
66+
)
67+
],
68+
tags: ["autodocs"]
69+
} satisfies Meta<typeof DataTable>;
70+
71+
export default meta;
72+
73+
type Story = StoryObj<typeof meta>;
74+
75+
export const DefaultVariant: Story = {
76+
name: "Default",
77+
args: {
78+
// @ts-expect-error Something with the type seems off here, it renders correctly though
79+
columns: cols,
80+
data: data
81+
}
82+
};

src/components/Table/DataTable.tsx

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
"use client";
2+
3+
import { ColumnDef, flexRender, getCoreRowModel, useReactTable } from "@tanstack/react-table";
4+
import React, { useState } from "react";
5+
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "./Table";
6+
7+
interface DataTableProps<TData, TValue> {
8+
columns: ColumnDef<TData, TValue>[];
9+
data: TData[];
10+
}
11+
12+
export function DataTable<TData, TValue>({ columns, data }: DataTableProps<TData, TValue>) {
13+
const [rowSelection, setRowSelection] = useState({});
14+
const table = useReactTable({
15+
data,
16+
columns,
17+
onRowSelectionChange: setRowSelection,
18+
getCoreRowModel: getCoreRowModel(),
19+
state: {
20+
rowSelection
21+
}
22+
});
23+
24+
return (
25+
<div className="overflow-hidden overflow-x-auto rounded-md border">
26+
<Table className="min-w-[500px]">
27+
<TableHeader className="bg-theme-surface-tertiary">
28+
{table.getHeaderGroups().map((headerGroup) => (
29+
<TableRow key={headerGroup.id}>
30+
{headerGroup.headers.map((header) => {
31+
return (
32+
<TableHead
33+
className="text-theme-text-secondary"
34+
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 }}
37+
>
38+
{header.isPlaceholder
39+
? null
40+
: flexRender(header.column.columnDef.header, header.getContext())}
41+
</TableHead>
42+
);
43+
})}
44+
</TableRow>
45+
))}
46+
</TableHeader>
47+
<TableBody>
48+
{table.getRowModel().rows?.length ? (
49+
table.getRowModel().rows.map((row) => (
50+
<TableRow
51+
className="bg-theme-surface-primary"
52+
key={row.id}
53+
data-state={row.getIsSelected() && "selected"}
54+
>
55+
{row.getVisibleCells().map((cell) => (
56+
<TableCell className="font-medium text-theme-text-primary" key={cell.id}>
57+
{flexRender(cell.column.columnDef.cell, cell.getContext())}
58+
</TableCell>
59+
))}
60+
</TableRow>
61+
))
62+
) : (
63+
<TableRow>
64+
<TableCell
65+
colSpan={columns.length}
66+
className="h-24 bg-theme-surface-primary p-9 text-center"
67+
>
68+
No results.
69+
</TableCell>
70+
</TableRow>
71+
)}
72+
</TableBody>
73+
</Table>
74+
</div>
75+
);
76+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { Meta } from "@storybook/react";
2+
import { StoryObj } from "@storybook/react";
3+
import React from "react";
4+
import { Table, TableHeader, TableBody, TableHead, TableRow, TableCell } from "./index";
5+
6+
const rows = ["ID", "Name", "Age"];
7+
const values = [
8+
["1", "John Doe", "25"],
9+
["2", "Jane Doe", "24"],
10+
["3", "John Smith", "26"],
11+
["4", "Jane Smith", "27"]
12+
];
13+
14+
const meta = {
15+
title: "Elements/Table/Table",
16+
component: Table,
17+
18+
parameters: {
19+
layout: "centered"
20+
},
21+
decorators: [
22+
(Story) => (
23+
<div className="w-[500px]">
24+
<Story />
25+
</div>
26+
)
27+
],
28+
tags: ["autodocs"]
29+
} satisfies Meta<typeof Table>;
30+
31+
export default meta;
32+
33+
type Story = StoryObj<typeof meta>;
34+
35+
export const DefaultVariant: Story = {
36+
name: "Default",
37+
render() {
38+
return (
39+
<div className="overflow-hidden overflow-x-auto rounded-md border">
40+
<Table>
41+
<TableHeader className="bg-theme-surface-tertiary">
42+
<TableRow>
43+
{rows.map((rows, index) => {
44+
return (
45+
<TableHead className="text-theme-text-secondary" key={index}>
46+
{rows}
47+
</TableHead>
48+
);
49+
})}
50+
</TableRow>
51+
</TableHeader>
52+
<TableBody>
53+
{values.map((value, index) => {
54+
return (
55+
<TableRow key={index}>
56+
{value.map((val, i) => {
57+
return (
58+
<TableCell className="bg-theme-surface-primary" key={i}>
59+
{val}
60+
</TableCell>
61+
);
62+
})}
63+
</TableRow>
64+
);
65+
})}
66+
</TableBody>
67+
</Table>
68+
</div>
69+
);
70+
}
71+
};

src/components/Table/Table.tsx

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import * as React from "react";
2+
import { cn } from "../../utilities";
3+
4+
const Table = React.forwardRef<HTMLTableElement, React.HTMLAttributes<HTMLTableElement>>(
5+
({ className, ...props }, ref) => (
6+
<div className="w-full overflow-auto">
7+
<table ref={ref} className={cn("w-full text-text-sm", className)} {...props} />
8+
</div>
9+
)
10+
);
11+
Table.displayName = "Table";
12+
13+
const TableHeader = React.forwardRef<
14+
HTMLTableSectionElement,
15+
React.HTMLAttributes<HTMLTableSectionElement>
16+
>(({ className, ...props }, ref) => (
17+
<thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
18+
));
19+
TableHeader.displayName = "TableHeader";
20+
21+
const TableBody = React.forwardRef<
22+
HTMLTableSectionElement,
23+
React.HTMLAttributes<HTMLTableSectionElement>
24+
>(({ className, ...props }, ref) => (
25+
<tbody ref={ref} className={cn("[&_tr:last-child]:border-0", className)} {...props} />
26+
));
27+
TableBody.displayName = "TableBody";
28+
29+
const TableFooter = React.forwardRef<
30+
HTMLTableSectionElement,
31+
React.HTMLAttributes<HTMLTableSectionElement>
32+
>(({ className, ...props }, ref) => (
33+
<tfoot
34+
ref={ref}
35+
className={cn("bg-primary text-primary-foreground font-medium", className)}
36+
{...props}
37+
/>
38+
));
39+
TableFooter.displayName = "TableFooter";
40+
41+
const TableRow = React.forwardRef<HTMLTableRowElement, React.HTMLAttributes<HTMLTableRowElement>>(
42+
({ className, ...props }, ref) => (
43+
<tr
44+
ref={ref}
45+
className={cn(
46+
"hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors",
47+
className
48+
)}
49+
{...props}
50+
/>
51+
)
52+
);
53+
TableRow.displayName = "TableRow";
54+
55+
const TableHead = React.forwardRef<
56+
HTMLTableCellElement,
57+
React.ThHTMLAttributes<HTMLTableCellElement>
58+
>(({ className, ...props }, ref) => (
59+
<th
60+
ref={ref}
61+
className={cn(
62+
"h-8 px-4 py-2 text-left align-middle font-medium [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
63+
className
64+
)}
65+
{...props}
66+
/>
67+
));
68+
TableHead.displayName = "TableHead";
69+
70+
const TableCell = React.forwardRef<
71+
HTMLTableCellElement,
72+
React.TdHTMLAttributes<HTMLTableCellElement>
73+
>(({ className, ...props }, ref) => (
74+
<td
75+
ref={ref}
76+
className={cn(
77+
"h-9 px-4 py-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
78+
className
79+
)}
80+
{...props}
81+
/>
82+
));
83+
TableCell.displayName = "TableCell";
84+
85+
const TableCaption = React.forwardRef<
86+
HTMLTableCaptionElement,
87+
React.HTMLAttributes<HTMLTableCaptionElement>
88+
>(({ className, ...props }, ref) => (
89+
<caption
90+
ref={ref}
91+
className={cn("text-muted-foreground mt-4 text-text-sm", className)}
92+
{...props}
93+
/>
94+
));
95+
TableCaption.displayName = "TableCaption";
96+
97+
export { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption };

src/components/Table/index.tsx

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

src/components/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ export * from "./Box";
55
export * from "./Avatar";
66
export * from "./Skeleton";
77
export * from "./Dropdown";
8+
export * from "./Table";

0 commit comments

Comments
 (0)