Openstatus
www.openstatus.dev
1"use client";
2
3import {
4 type ColumnDef,
5 type ColumnFiltersState,
6 type PaginationState,
7 type Row,
8 type SortingState,
9 type VisibilityState,
10 flexRender,
11 getCoreRowModel,
12 getExpandedRowModel,
13 getFacetedRowModel,
14 getFacetedUniqueValues,
15 getFilteredRowModel,
16 getPaginationRowModel,
17 getSortedRowModel,
18 useReactTable,
19} from "@tanstack/react-table";
20import * as React from "react";
21
22import {
23 Table,
24 TableBody,
25 TableCell,
26 TableHead,
27 TableHeader,
28 TableRow,
29} from "@/components/ui/table";
30import { Fragment } from "react";
31import type { DataTableActionBarProps } from "./data-table-action-bar";
32import type { DataTablePaginationProps } from "./data-table-pagination";
33import type { DataTableToolbarProps } from "./data-table-toobar";
34
35export interface DataTableProps<TData, TValue> {
36 columns: ColumnDef<TData, TValue>[];
37 data: TData[];
38 rowComponent?: React.ComponentType<{ row: Row<TData> }>;
39 toolbarComponent?: React.ComponentType<DataTableToolbarProps<TData>>;
40 actionBar?: React.ComponentType<DataTableActionBarProps<TData>>;
41 paginationComponent?: React.ComponentType<DataTablePaginationProps<TData>>;
42 onRowClick?: (row: Row<TData>) => void;
43 defaultSorting?: SortingState;
44 defaultColumnVisibility?: VisibilityState;
45 defaultColumnFilters?: ColumnFiltersState;
46 defaultPagination?: PaginationState;
47 autoResetPageIndex?: boolean;
48
49 /** access the state from the parent component */
50 columnFilters?: ColumnFiltersState;
51 setColumnFilters?: React.Dispatch<React.SetStateAction<ColumnFiltersState>>;
52 sorting?: SortingState;
53 setSorting?: React.Dispatch<React.SetStateAction<SortingState>>;
54 pagination?: PaginationState;
55 setPagination?: React.Dispatch<React.SetStateAction<PaginationState>>;
56}
57
58export function DataTable<TData, TValue>({
59 columns,
60 data,
61 rowComponent,
62 toolbarComponent,
63 actionBar,
64 paginationComponent,
65 onRowClick,
66 defaultSorting = [],
67 defaultColumnVisibility = {},
68 defaultColumnFilters = [],
69 defaultPagination = { pageIndex: 0, pageSize: 20 },
70 autoResetPageIndex = true,
71 columnFilters,
72 setColumnFilters,
73 sorting,
74 setSorting,
75 pagination,
76 setPagination,
77}: DataTableProps<TData, TValue>) {
78 // biome-ignore lint/suspicious/noExplicitAny: <explanation>
79 const [globalFilter, setGlobalFilter] = React.useState<any>();
80 const [rowSelection, setRowSelection] = React.useState({});
81 const [columnVisibility, setColumnVisibility] =
82 React.useState<VisibilityState>(defaultColumnVisibility);
83 const [internalPagination, setInternalPagination] =
84 React.useState<PaginationState>(defaultPagination);
85 const [internalColumnFilters, setInternalColumnFilters] =
86 React.useState<ColumnFiltersState>(defaultColumnFilters);
87 const [internalSorting, setInternalSorting] =
88 React.useState<SortingState>(defaultSorting);
89
90 // Use controlled or uncontrolled column filters
91 const columnFiltersState = columnFilters ?? internalColumnFilters;
92 const setColumnFiltersState = setColumnFilters ?? setInternalColumnFilters;
93 const sortingState = sorting ?? internalSorting;
94 const setSortingState = setSorting ?? setInternalSorting;
95 const paginationState = pagination ?? internalPagination;
96 const setPaginationState = setPagination ?? setInternalPagination;
97
98 const table = useReactTable({
99 data,
100 columns,
101 state: {
102 sorting: sortingState,
103 columnVisibility,
104 rowSelection,
105 pagination: paginationState,
106 columnFilters: columnFiltersState,
107 globalFilter,
108 },
109 enableRowSelection: true,
110 onRowSelectionChange: setRowSelection,
111 onSortingChange: setSortingState,
112 onColumnFiltersChange: setColumnFiltersState,
113 onColumnVisibilityChange: setColumnVisibility,
114 onPaginationChange: setPaginationState,
115 onGlobalFilterChange: setGlobalFilter,
116 getCoreRowModel: getCoreRowModel(),
117 getFilteredRowModel: getFilteredRowModel(),
118 getPaginationRowModel: getPaginationRowModel(),
119 getSortedRowModel: getSortedRowModel(),
120 getFacetedRowModel: getFacetedRowModel(),
121 getFacetedUniqueValues: getFacetedUniqueValues(),
122 getExpandedRowModel: getExpandedRowModel(),
123 autoResetPageIndex,
124 // @ts-expect-error as we have an id in the data
125 getRowCanExpand: (row) => Boolean(row.original.id),
126 });
127
128 return (
129 <div className="grid gap-2">
130 {toolbarComponent
131 ? React.createElement(toolbarComponent, { table })
132 : null}
133 <Table>
134 <TableHeader>
135 {table.getHeaderGroups().map((headerGroup) => (
136 <TableRow key={headerGroup.id}>
137 {headerGroup.headers.map((header) => {
138 return (
139 <TableHead
140 key={header.id}
141 colSpan={header.colSpan}
142 className={header.column.columnDef.meta?.headerClassName}
143 >
144 {header.isPlaceholder
145 ? null
146 : flexRender(
147 header.column.columnDef.header,
148 header.getContext(),
149 )}
150 </TableHead>
151 );
152 })}
153 </TableRow>
154 ))}
155 </TableHeader>
156 <TableBody>
157 {table.getRowModel().rows?.length ? (
158 table.getRowModel().rows.map((row) => (
159 <Fragment key={row.id}>
160 <TableRow
161 data-state={
162 (row.getIsSelected() || row.getIsExpanded()) && "selected"
163 }
164 onClick={() => onRowClick?.(row)}
165 className="data-[state=selected]:bg-muted/50"
166 >
167 {row.getVisibleCells().map((cell) => (
168 <TableCell
169 key={cell.id}
170 className={cell.column.columnDef.meta?.cellClassName}
171 >
172 {flexRender(
173 cell.column.columnDef.cell,
174 cell.getContext(),
175 )}
176 </TableCell>
177 ))}
178 </TableRow>
179 {row.getIsExpanded() && (
180 <TableRow className="hover:bg-background">
181 <TableCell
182 className="p-0"
183 colSpan={row.getVisibleCells().length}
184 >
185 {rowComponent
186 ? React.createElement(rowComponent, { row })
187 : null}
188 </TableCell>
189 </TableRow>
190 )}
191 </Fragment>
192 ))
193 ) : (
194 <TableRow>
195 <TableCell colSpan={columns.length} className="h-24 text-center">
196 No results.
197 </TableCell>
198 </TableRow>
199 )}
200 </TableBody>
201 {actionBar ? React.createElement(actionBar, { table }) : null}
202 </Table>
203 {paginationComponent
204 ? React.createElement(paginationComponent, { table })
205 : null}
206 </div>
207 );
208}