Skip to main content

Data Table

The DataTable component is a flexible, sortable, and paginated table built with @tanstack/react-table. It supports loading states, empty messages, custom styling, and row selection.

import { DataTable } from "tawuniya/components";

👉 View in Storybook

Props​

PropTypeDefaultDescription
columnsColumnDef<TData, TValue>[]-Table column definitions.
dataTData[]-Array of row data.
loadingbooleanfalseIf true, shows TableSkeleton.
classNameWrapperstring-Classes for the wrapper container.
classNameHeadRowstring-Classes for the header row.
classNameHeadCellstring-Classes for each header cell.
classNameBodyRowstring-Classes for each body row.
classNameBodyCellstring-Classes for each body cell.
classNamePaginationstring-Classes for pagination.
hidePaginationbooleanfalseHides pagination controls if true.
skeleton{ rowSpan?: number; colSpan?: number }-Customizes skeleton rows when loading.
empty{ icon?: ReactNode; title?: string; description?: string }-Custom empty state message.
paginationTotalnumber0Total number of items for pagination.
onSelectedRowsChangeDispatch<SetStateAction<TData[]>>[]Callback triggered when selected rows change.
resetSelectionobjectnullResets row selection when empty object or array.
onRowClick(tableRow: Row<TData>, rowItem: TData) => voidundefinedTriggered when a row is clicked.

Columns​

interface Expense {
id: string;
label: string;
note: string;
category: string;
type: string;
}

export const columns: ColumnDef<Expense>[] = [
{
id: "select",
size: 40,
header: ({ table }) => (
<Checkbox
checked={
table.getIsAllPageRowsSelected() ||
(table.getIsSomePageRowsSelected() && "indeterminate")
}
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
aria-label="Select all"
className="translate-y-0.5"
/>
),
cell: ({ row }) => (
<Checkbox
checked={row.getIsSelected()}
onCheckedChange={(value) => row.toggleSelected(!!value)}
aria-label="Select row"
className="translate-y-0.5"
/>
),
enableSorting: false,
enableHiding: false,
},
{
accessorKey: "label",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="Label" />
),
cell: ({ row }) => (
<div className="w-[150px] capitalize">{row.getValue("label")}</div>
),
},
{
accessorKey: "note",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="Note" />
),
cell: ({ row }) => {
return (
<div className="flex space-x-2">
<span className="max-w-125 truncate font-medium capitalize">
{row.getValue("note")}
</span>
</div>
);
},
enableSorting: false, // Disable sorting for this column
},
{
accessorKey: "category",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="Category" />
),
cell: ({ row }) => {
return (
<div className="flex w-25 items-center">
<span className="capitalize"> {row.getValue("category")}</span>
</div>
);
},
filterFn: (row, id, value) => {
return value.includes(row.getValue(id));
},
},
{
accessorKey: "type",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="Type" />
),
cell: ({ row }) => {
const type = row.getValue("type");
return (
<div className="flex w-25 items-center">
{type === "income" ? (
<TrendingUp size={20} className="mr-2 text-green-500" />
) : (
<TrendingDown size={20} className="mr-2 text-red-500" />
)}
<span className="capitalize"> {row.getValue("type")}</span>
</div>
);
},
filterFn: (row, id, value) => {
return value.includes(row.getValue(id));
},
},
{
id: "actions",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="Action" />
),
cell: () => (
<>
<Button size="sm" variant="smooth_destructive">
<Trash size={16} />
Delete
</Button>
</>
),
},
];

Usage​

Basic Example​

<DataTable
columns={columns}
data={[
{
id: "1",
label: "salary",
note: "monthly salary",
category: "income",
type: "income",
},
]}
/>

With Loading Skeleton​

<DataTable
columns={columns}
data={[
{
id: "1",
label: "salary",
note: "monthly salary",
category: "income",
type: "income",
},
]}
loading
skeleton={{ rowSpan: 5, colSpan: 1 }}
/>

With Empty State​

<DataTable
columns={columns}
data={[]}
empty={{
icon: <EmptyIcon />,
title: "No Records",
description: "There are no entries to display.",
}}
/>

With Pagination​

<DataTable
columns={columns}
data={[
{
id: "1",
label: "salary",
note: "monthly salary",
category: "income",
type: "income",
},
]}
paginationTotal={100}
/>

With Pagination Hidden​

<DataTable
columns={columns}
data={[
{
id: "1",
label: "salary",
note: "monthly salary",
category: "income",
type: "income",
},
]}
hidePagination={true}
/>

With Row Selection & Reset Selection​

const [selectedRows, setSelectedRows] = useState<any>([]);
const [resetSelectedRows, setResetSelectedRows] = useState<object | null>(null);

const onDelete = () => {
// Delete Rows Logic
setResetSelectedRows({});
};

const data = useMemo(() => {
return [
{
id: "1",
label: "salary",
note: "monthly salary",
category: "income",
type: "income",
},
]
}, [])

<div>
{selectedRows.length > 0 && <Button onClick={onDelete}>Delete</Button>}
<DataTable
columns={columns}
data={data}
onSelectedRowsChange={setSelectedRows}
resetSelection={resetSelectedRows}
/>
</div>;

With Row Click​

<DataTable
columns={columns}
data={[
{
id: "1",
label: "salary",
note: "monthly salary",
category: "income",
type: "income",
},
]}
onRowClick={(tableRow, rowItem) => console.log(tableRow, rowItem)}
/>

Features​

  • Sorting: Columns can be sorted with click or keyboard (Enter/Space).
  • Pagination: Supports manual pagination with a separate Pagination component.
  • Loading State: Displays skeleton rows when loading.
  • Empty State: Shows a customizable empty message when no data is available.
  • Custom Styling: Pass className props to table, rows, and cells.

Notes​

  • Empty messages and skeletons improve user experience during data fetching.