Skip to content

Commit

Permalink
Merge pull request #76 from AElfProject/feature/common-table-component
Browse files Browse the repository at this point in the history
feat: made common table component
  • Loading branch information
aelf-lxy authored Dec 5, 2024
2 parents f1ee124 + 17b4b95 commit 67d5b00
Show file tree
Hide file tree
Showing 4 changed files with 17,237 additions and 40 deletions.
317 changes: 317 additions & 0 deletions components/common-table/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,317 @@
"use client";

import * as React from "react";
import {
ColumnDef,
ColumnFiltersState,
SortingState,
VisibilityState,
flexRender,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
useReactTable,
} from "@tanstack/react-table";
import { ArrowUpDown, ChevronDown, MoreHorizontal } from "lucide-react";

import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox";
import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Input } from "@/components/ui/input";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";

type ActionData = {
title: React.ReactNode; // Allows any valid React node, including JSX like <div>Hello</div>
options: {
title: React.ReactNode; // Allows any valid React node
onClick: () => void; // A function that takes no arguments and returns nothing
}[];
};

type DataTableProps<TData> = {
data: TData[];
columns: ColumnDef<TData>[];
filterPlaceholder?: string;
searchKey?: string;
isActionable?: boolean;
actionData?: ActionData;
};

export type Payment = {
id: string;
amount: number;
status: "pending" | "processing" | "success" | "failed";
email: string;
};

const generateColumns = (
cols: any[],
isActionable?: boolean,
actionData?: ActionData
) => {
const DATA = cols.map((data: any) => {
return {
accessorKey: data.accessorKey,
header: ({ column }: any) => {
return data.isSorting ? (
<Button
variant={"ghost"}
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
{data.header}
<ArrowUpDown size={15} className="mx-2" />
</Button>
) : (
<div className="lowercase">{data.header}</div>
);
},
cell: ({ row }: any) => (
<div className="lowercase">{row.getValue(data.accessorKey)}</div>
),
};
});
return isActionable && actionData
? [
{
id: "select",
header: ({ table }: any) => (
<Checkbox
checked={
table.getIsAllPageRowsSelected() ||
(table.getIsSomePageRowsSelected() && "indeterminate")
}
onCheckedChange={(value) =>
table.toggleAllPageRowsSelected(!!value)
}
aria-label="Select all"
/>
),
cell: ({ row }: any) => (
<Checkbox
checked={row.getIsSelected()}
onCheckedChange={(value) => row.toggleSelected(!!value)}
aria-label="Select row"
/>
),
enableSorting: false,
enableHiding: false,
},
...DATA,
{
id: "actions",
enableHiding: false,
cell: () => {
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" className="h-8 w-8 p-0">
<span className="sr-only">{actionData.title}</span>
<MoreHorizontal />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuLabel>{actionData.title}</DropdownMenuLabel>
{actionData.options.map((data, i) => (
<DropdownMenuItem onClick={data.onClick} key={i}>
{data.title}
</DropdownMenuItem>
))}
{/* <DropdownMenuSeparator /> */}
</DropdownMenuContent>
</DropdownMenu>
);
},
},
]
: DATA;
};

const PAGE_SIZE_OPTIONS = [5, 10, 15, 20, 25, 30];

export function DataTable<TData>({
data,
columns,
filterPlaceholder = "Filter...",
searchKey = "",
isActionable,
actionData,
}: DataTableProps<TData>) {
const [sorting, setSorting] = React.useState<SortingState>([]);
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
[]
);
const [columnVisibility, setColumnVisibility] =
React.useState<VisibilityState>({});
const [rowSelection, setRowSelection] = React.useState({});
const [pageSize, setPageSize] = React.useState(PAGE_SIZE_OPTIONS[0]); // Default page size
const [pageIndex, setPageIndex] = React.useState(0); // Default page size

const table = useReactTable({
data,
columns: generateColumns(columns, isActionable, actionData),
onSortingChange: setSorting,
onColumnFiltersChange: setColumnFilters,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel(),
onColumnVisibilityChange: setColumnVisibility,
onRowSelectionChange: setRowSelection,
state: {
sorting,
columnFilters,
columnVisibility,
rowSelection,
pagination: {
pageIndex: pageIndex,
pageSize: pageSize,
},
},
});

return (
<div className="w-full">
<div className="flex items-center py-4">
<Input
placeholder={filterPlaceholder}
value={(table.getColumn(searchKey)?.getFilterValue() as string) ?? ""}
onChange={(event) =>
table.getColumn(searchKey)?.setFilterValue(event.target.value)
}
className="max-w-sm"
/>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" className="ml-auto">
Columns <ChevronDown />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
{table
.getAllColumns()
.filter((column) => column.getCanHide())
.map((column) => (
<DropdownMenuCheckboxItem
key={column.id}
className="capitalize"
checked={column.getIsVisible()}
onCheckedChange={(value) => column.toggleVisibility(!!value)}
>
{column.id}
</DropdownMenuCheckboxItem>
))}
</DropdownMenuContent>
</DropdownMenu>
</div>
<div className="rounded-md border">
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<TableHead key={header.id}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext()
)}
</TableHead>
))}
</TableRow>
))}
</TableHeader>
<TableBody>
{table.getRowModel().rows?.length ? (
table.getRowModel().rows.map((row) => (
<TableRow
key={row.id}
data-state={row.getIsSelected() && "selected"}
>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
{flexRender(
cell.column.columnDef.cell,
cell.getContext()
)}
</TableCell>
))}
</TableRow>
))
) : (
<TableRow>
<TableCell
colSpan={columns.length}
className="h-24 text-center"
>
No results.
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
<div className="flex items-center justify-between space-x-2 py-4">
<div>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" className="ml-auto">
Rows per page: {pageSize}
<ChevronDown />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
{PAGE_SIZE_OPTIONS.map((size) => (
<DropdownMenuCheckboxItem
key={size}
className="capitalize"
checked={pageSize === size}
onCheckedChange={() => setPageSize(size)}
>
{size} rows
</DropdownMenuCheckboxItem>
))}
</DropdownMenuContent>
</DropdownMenu>
</div>
<div className="space-x-2">
<Button
variant="outline"
size="sm"
onClick={() => setPageIndex((prev) => prev - 1)}
disabled={!table.getCanPreviousPage()}
>
Previous
</Button>
<Button
variant="outline"
size="sm"
onClick={() => setPageIndex((prev) => prev + 1)}
disabled={!table.getCanNextPage()}
>
Next
</Button>
<span>
Page {table.getState().pagination.pageIndex + 1} of{" "}
{table.getPageCount()}
</span>
</div>
</div>
</div>
);
}
Loading

0 comments on commit 67d5b00

Please sign in to comment.