- Accordion
- Alert
- Alert Dialog
- Attachment
- Badge
- Breadcrumb
- Bubble
- Button
- Button Group
- Calendar
- Card
- Checkbox
- Collapsible
- Command
- Context Menu
- Data Table
- Dialog
- Drawer
- Dropdown Menu
- Empty
- Field
- Hover Card
- Input
- Input Group
- Item
- Kbd
- Label
- Marker
- Menubar
- Message
- Message Scroller
- Pagination
- Popover
- Progress
- Questionnaire
- Radio Group
- Scroll Area
- Select
- Separator
- Sheet
- Sidebar
- Skeleton
- Slider
- Sonner
- Spinner
- Switch
- Table
- Tabs
- Textarea
- Toggle
- Toggle Group
- Tooltip
| Status | Amount | |||
|---|---|---|---|---|
success | ken99@example.com | $316.00 | ||
success | Abe45@example.com | $242.00 | ||
processing | Monserrat44@example.com | $837.00 | ||
success | Silas22@example.com | $874.00 | ||
failed | carmella@example.com | $721.00 |
Introduction#
Every data table or datagrid is unique. They behave differently, have specific sorting and filtering requirements, and work with different data sources.
It doesn't make sense to combine all of these variations into a single component. If we do that, we lose the flexibility that headless UI provides.
So instead of a data-table component, this is a guide on how to build your own. We'll start with the basic <Table /> component and build a complex data table from scratch. The table brings the frame (the muted shell, the raised card for the rows, the hover and selected fills), so your data table inherits the design without any extra styling.
Tip: If you find yourself using the same table in multiple places in your app, you can always extract it into a reusable component.
Table of Contents#
This guide shows how to use TanStack Table and the <Table /> component to build your own data table. We'll cover the following topics:
- Set up Table Features
- Basic Table
- Row Actions
- Pagination
- Sorting
- Filtering
- Visibility
- Row Selection
- Reusable Components
Installation#
Add the <Table /> component to your project:
pnpm dlx shadcn@latest add https://ui.ikramhasan.com/r/table.json
Add the @tanstack/react-table dependency. This guide uses TanStack Table
v9:
pnpm add @tanstack/react-table
Prerequisites#
We are going to build a table to show recent payments. Here's what our data looks like:
type Payment = {
id: string
amount: number
status: "pending" | "processing" | "success" | "failed"
email: string
}
export const payments: Payment[] = [
{
id: "728ed52f",
amount: 100,
status: "pending",
email: "m@example.com",
},
{
id: "489e1d42",
amount: 125,
status: "processing",
email: "example@gmail.com",
},
// ...
]Project Structure#
Start by creating the following file structure:
app
└── payments
├── columns.tsx
├── data-table-features.ts
├── data-table.tsx
└── page.tsxThis example uses Next.js, but it works with any other React framework.
columns.tsx(client component) contains our column definitions.data-table-features.tscontains the sharedfeaturesobject that tells TanStack Table which behavior to enable.data-table.tsx(client component) contains our<DataTable />component.page.tsx(server component) is where we fetch data and render our table.
Set up Table Features#
TanStack Table v9 is feature-based: you opt into the behavior you want (sorting, filtering, pagination, and so on) by declaring it with tableFeatures(). Anything you don't list is tree-shaken out of your bundle. That includes the built-in filter and sort functions: register the ones your columns rely on under filterFns and sortFns (our email filter uses includesString, and string columns sort with text / alphanumeric).
import {
columnFilteringFeature,
columnVisibilityFeature,
createFilteredRowModel,
createPaginatedRowModel,
createSortedRowModel,
filterFn_includesString,
rowPaginationFeature,
rowSelectionFeature,
rowSortingFeature,
sortFn_alphanumeric,
sortFn_text,
tableFeatures,
} from "@tanstack/react-table"
export const features = tableFeatures({
columnFilteringFeature,
columnVisibilityFeature,
rowPaginationFeature,
rowSelectionFeature,
rowSortingFeature,
filteredRowModel: createFilteredRowModel(),
paginatedRowModel: createPaginatedRowModel(),
sortedRowModel: createSortedRowModel(),
filterFns: { includesString: filterFn_includesString },
sortFns: { alphanumeric: sortFn_alphanumeric, text: sortFn_text },
})
// Pass this as the first generic argument to `ColumnDef`, `Column`, `Table`,
// and `Row` so each type knows which feature APIs are available.
export type DataTableFeatures = typeof featuresNote: The core row model is always included, so you never register it yourself. Row models for optional features are created with create*RowModel() and registered on the features object. There are no get*RowModel table options.
Basic Table#
Let's start by building a basic table.
Column Definitions
First, we'll define our columns.
"use client"
import { createColumnHelper } from "@tanstack/react-table"
import { type DataTableFeatures } from "./data-table-features"
// This type is used to define the shape of our data.
// You can use a Zod schema here if you want.
export type Payment = {
id: string
amount: number
status: "pending" | "processing" | "success" | "failed"
email: string
}
// Use `accessor` for data columns and `display` for columns without one.
const columnHelper = createColumnHelper<DataTableFeatures, Payment>()
export const columns = columnHelper.columns([
columnHelper.accessor("status", {
header: "Status",
}),
columnHelper.accessor("email", {
header: "Email",
}),
columnHelper.accessor("amount", {
header: "Amount",
}),
])Columns are where you define the core of what your table will look like. They define the data that will be displayed, how it will be formatted, sorted and filtered.
<DataTable /> component
Next, we'll create a <DataTable /> component to render our table.
"use client"
import { useTable, type ColumnDef, type RowData } from "@tanstack/react-table"
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table"
import { features, type DataTableFeatures } from "./data-table-features"
interface DataTableProps<TData extends RowData> {
columns: ColumnDef<DataTableFeatures, TData>[]
data: TData[]
}
export function DataTable<TData extends RowData>({
columns,
data,
}: DataTableProps<TData>) {
const table = useTable({
features,
data,
columns,
})
return (
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<TableHead key={header.id}>
{header.isPlaceholder ? null : (
<table.FlexRender header={header} />
)}
</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}>
<table.FlexRender cell={cell} />
</TableCell>
))}
</TableRow>
))
) : (
<TableRow>
<TableCell
colSpan={columns.length}
className="h-24 text-center text-muted-foreground"
>
No results.
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
)
}The <Table /> is its own frame, so it isn't wrapped in a bordered div like upstream's guide.
<table.FlexRender /> vs flexRender: This guide uses v9's <table.FlexRender header={header} /> and <table.FlexRender cell={cell} />, available right on the table instance. The classic flexRender(component, context) helper still works too, if you prefer the function form.
Render the table
Finally, we'll render our table in our page component.
import { columns, Payment } from "./columns"
import { DataTable } from "./data-table"
async function getData(): Promise<Payment[]> {
// Fetch data from your API here.
return [
{
id: "728ed52f",
amount: 100,
status: "pending",
email: "m@example.com",
},
// ...
]
}
export default async function DemoPage() {
const data = await getData()
return (
<div className="container mx-auto py-10">
<DataTable columns={columns} data={data} />
</div>
)
}Cell Formatting#
Let's format the amount cell to display the dollar amount. We'll also align the cell to the right.
Update the header and cell definitions for amount as follows:
export const columns = columnHelper.columns([
columnHelper.accessor("amount", {
header: () => <div className="text-right">Amount</div>,
cell: ({ row }) => {
const amount = parseFloat(row.getValue("amount"))
const formatted = new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
}).format(amount)
return <div className="text-right font-medium">{formatted}</div>
},
}),
])You can use the same approach to format other cells and headers. Numbers line up on their own: the table sets tabular-nums.
Row Actions#
Let's add row actions to our table. We'll use a <DropdownMenu /> for this.
Add a new actions column whose cell returns a <DropdownMenu />:
"use client"
import { createColumnHelper } from "@tanstack/react-table"
import { MoreHorizontal } from "lucide-react"
import { Button } from "@/components/ui/button"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
export const columns = columnHelper.columns([
// ...
columnHelper.display({
id: "actions",
cell: ({ row }) => {
const payment = row.original
return (
<div className="-my-1 -mr-1.5 flex justify-end">
<DropdownMenu>
<DropdownMenuTrigger
render={<Button variant="ghost" size="icon-sm" />}
>
<span className="sr-only">Open menu</span>
<MoreHorizontal />
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuGroup>
<DropdownMenuLabel>Actions</DropdownMenuLabel>
<DropdownMenuItem
onClick={() => navigator.clipboard.writeText(payment.id)}
>
Copy payment ID
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>View customer</DropdownMenuItem>
<DropdownMenuItem>View payment details</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
</div>
)
},
}),
// ...
])The 28px button takes -my-1 so the row stays exactly 40px, and -mr-1.5 so the dots line up with the right edge of the text above them. The row keeps its hover fill while the menu is open.
You can access the row data using row.original in the cell function. Use this to handle actions for your row, e.g. use the id to make a DELETE call to your API.
Pagination#
Next, we'll add pagination to our table.
Pagination is already enabled
Because our features object includes rowPaginationFeature and createPaginatedRowModel(), the table paginates rows into pages of 10. There's nothing to add to useTable. See the pagination docs to customize the page size or paginate on the server.
Add pagination controls
Add controls with the <Button /> component and the table.previousPage() and table.nextPage() APIs.
import { Button } from "@/components/ui/button"
export function DataTable<TData extends RowData>({
columns,
data,
}: DataTableProps<TData>) {
const table = useTable({
features,
data,
columns,
})
return (
<div>
<Table>{/* ... */}</Table>
<div className="flex items-center justify-end gap-2 pt-4">
<Button
variant="outline"
size="sm"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
Previous
</Button>
<Button
variant="outline"
size="sm"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
Next
</Button>
</div>
</div>
)
}See Reusable Components for a more advanced pagination component.
Sorting#
Let's make the email column sortable. The rowSortingFeature and sorted row model are already registered in our features object, so all that's left is wiring up the sorting state.
Update <DataTable>
"use client"
import * as React from "react"
import {
useTable,
type ColumnDef,
type RowData,
type SortingState,
} from "@tanstack/react-table"
export function DataTable<TData extends RowData>({
columns,
data,
}: DataTableProps<TData>) {
const [sorting, setSorting] = React.useState<SortingState>([])
const table = useTable({
features,
data,
columns,
onSortingChange: setSorting,
state: {
sorting,
},
})
return (
<div>
<Table>{/* ... */}</Table>
</div>
)
}Make header cell sortable
We can now update the email header cell to add sorting controls.
"use client"
import { createColumnHelper } from "@tanstack/react-table"
import { ArrowUpDown } from "lucide-react"
export const columns = columnHelper.columns([
columnHelper.accessor("email", {
header: ({ column }) => {
return (
<Button
variant="ghost"
size="sm"
className="-my-1.5 -ml-[9px] hover:bg-foreground/5"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
Email
<ArrowUpDown data-icon="inline-end" />
</Button>
)
},
}),
])This sorts the table (asc and desc) when the user toggles the header cell.
The sm ghost button has the header's 13px type. -my-1.5 keeps the header band at its 36px, and -ml-[9px] cancels the button's padding and transparent border so "Email" lines up with the column's cells. The header sits on the muted shell, where the neutral accent hover would barely show, so the hover is a 5% foreground tint instead.
Filtering#
Let's add a search input to filter emails in our table. The columnFilteringFeature and filtered row model are already part of our features object, so we only need to wire up the filter state and render an input.
"use client"
import * as React from "react"
import {
useTable,
type ColumnDef,
type ColumnFiltersState,
type RowData,
type SortingState,
} from "@tanstack/react-table"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
export function DataTable<TData extends RowData>({
columns,
data,
}: DataTableProps<TData>) {
const [sorting, setSorting] = React.useState<SortingState>([])
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
[]
)
const table = useTable({
features,
data,
columns,
onSortingChange: setSorting,
onColumnFiltersChange: setColumnFilters,
state: {
sorting,
columnFilters,
},
})
return (
<div>
<div className="flex items-center pb-4">
<Input
placeholder="Filter emails..."
value={(table.getColumn("email")?.getFilterValue() as string) ?? ""}
onChange={(event) =>
table.getColumn("email")?.setFilterValue(event.target.value)
}
className="max-w-sm"
/>
</div>
<Table>{/* ... */}</Table>
</div>
)
}Filtering is now enabled for the email column. You can add filters to other columns as well. See the filtering docs for more information on customizing filters.
Visibility#
Adding column visibility is fairly simple using the @tanstack/react-table visibility API.
"use client"
import * as React from "react"
import {
useTable,
type ColumnDef,
type ColumnFiltersState,
type ColumnVisibilityState,
type RowData,
type SortingState,
} from "@tanstack/react-table"
import { ChevronDown } from "lucide-react"
import { Button } from "@/components/ui/button"
import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
export function DataTable<TData extends RowData>({
columns,
data,
}: DataTableProps<TData>) {
const [sorting, setSorting] = React.useState<SortingState>([])
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
[]
)
const [columnVisibility, setColumnVisibility] =
React.useState<ColumnVisibilityState>({})
const table = useTable({
features,
data,
columns,
onSortingChange: setSorting,
onColumnFiltersChange: setColumnFilters,
onColumnVisibilityChange: setColumnVisibility,
state: {
sorting,
columnFilters,
columnVisibility,
},
})
return (
<div>
<div className="flex items-center gap-2 pb-4">
<Input
placeholder="Filter emails..."
value={(table.getColumn("email")?.getFilterValue() as string) ?? ""}
onChange={(event) =>
table.getColumn("email")?.setFilterValue(event.target.value)
}
className="max-w-sm"
/>
<DropdownMenu>
<DropdownMenuTrigger
render={<Button variant="outline" className="ml-auto" />}
>
Columns <ChevronDown data-icon="inline-end" />
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuGroup>
{table
.getAllColumns()
.filter((column) => column.getCanHide())
.map((column) => {
return (
<DropdownMenuCheckboxItem
key={column.id}
className="capitalize"
checked={column.getIsVisible()}
onCheckedChange={(value) =>
column.toggleVisibility(!!value)
}
>
{column.id}
</DropdownMenuCheckboxItem>
)
})}
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
</div>
<Table>{/* ... */}</Table>
</div>
)
}This adds a dropdown menu that you can use to toggle column visibility.
Row Selection#
Next, we're going to add row selection to our table.
Update column definitions
"use client"
import { createColumnHelper } from "@tanstack/react-table"
import { Checkbox } from "@/components/ui/checkbox"
export const columns = columnHelper.columns([
columnHelper.display({
id: "select",
header: ({ table }) => (
<Checkbox
checked={table.getIsAllPageRowsSelected()}
indeterminate={
table.getIsSomePageRowsSelected() && !table.getIsAllPageRowsSelected()
}
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
aria-label="Select all"
/>
),
cell: ({ row }) => (
<Checkbox
checked={row.getIsSelected()}
onCheckedChange={(value) => row.toggleSelected(!!value)}
aria-label="Select row"
/>
),
enableSorting: false,
enableHiding: false,
}),
])Update <DataTable>
export function DataTable<TData extends RowData>({
columns,
data,
}: DataTableProps<TData>) {
const [sorting, setSorting] = React.useState<SortingState>([])
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
[]
)
const [columnVisibility, setColumnVisibility] =
React.useState<ColumnVisibilityState>({})
const [rowSelection, setRowSelection] = React.useState({})
const table = useTable({
features,
data,
columns,
onSortingChange: setSorting,
onColumnFiltersChange: setColumnFilters,
onColumnVisibilityChange: setColumnVisibility,
onRowSelectionChange: setRowSelection,
state: {
sorting,
columnFilters,
columnVisibility,
rowSelection,
},
})
return (
<div>
<Table>{/* ... */}</Table>
</div>
)
}This adds a checkbox to each row and a checkbox in the header to select all rows. Selected rows get the table's selected tint from data-state="selected", which the <DataTable /> already sets.
Show selected rows
You can show the number of selected rows using the table.getFilteredSelectedRowModel() API.
<div className="flex-1 text-sm text-muted-foreground">
{table.getFilteredSelectedRowModel().rows.length} of{" "}
{table.getFilteredRowModel().rows.length} row(s) selected.
</div>Reusable Components#
Here are some components you can use to build your data tables. They share the features object (and its DataTableFeatures type) from Set up Table Features.
| TASK-8700 | Fix the date picker focus | todo | low | |
| TASK-8737 | Write the migration guide | canceled | high | |
| TASK-8774 | Audit dark mode contrast | done | medium | |
| TASK-8811 | Add an upload loading state | in progress | low | |
| TASK-8848 | Remove legacy theme tokens | todo | high |
Rows per page
Column header#
Make any column header sortable and hideable.
"use client"
import { Subscribe, type Column, type RowData } from "@tanstack/react-table"
import { cn } from "cn"
import { ArrowDown, ArrowUp, ChevronsUpDown, EyeOff } from "lucide-react"
import { Button } from "@/components/ui/button"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import { type DataTableFeatures } from "./data-table-features"
interface DataTableColumnHeaderProps<TData extends RowData, TValue>
extends React.HTMLAttributes<HTMLDivElement> {
column: Column<DataTableFeatures, TData, TValue>
title: string
}
export function DataTableColumnHeader<TData extends RowData, TValue>({
column,
title,
className,
}: DataTableColumnHeaderProps<TData, TValue>) {
if (!column.getCanSort()) {
return <div className={cn(className)}>{title}</div>
}
return (
<div className={cn("flex items-center", className)}>
<DropdownMenu>
<DropdownMenuTrigger
render={
<Button
variant="ghost"
size="sm"
className="-my-1.5 -ml-[9px] hover:bg-foreground/5 aria-expanded:bg-foreground/5"
/>
}
>
{title}
{/* Read the sort through Subscribe: the column object is stable, so
a memoized header (React Compiler) would miss getIsSorted(). */}
<Subscribe
source={column.table.atoms.sorting}
selector={(sorting) =>
sorting.find((sort) => sort.id === column.id)?.desc
}
>
{(desc) =>
desc === undefined ? (
<ChevronsUpDown data-icon="inline-end" />
) : desc ? (
<ArrowDown data-icon="inline-end" />
) : (
<ArrowUp data-icon="inline-end" />
)
}
</Subscribe>
</DropdownMenuTrigger>
<DropdownMenuContent align="start">
<DropdownMenuGroup>
<DropdownMenuItem onClick={() => column.toggleSorting(false)}>
<ArrowUp />
Asc
</DropdownMenuItem>
<DropdownMenuItem onClick={() => column.toggleSorting(true)}>
<ArrowDown />
Desc
</DropdownMenuItem>
</DropdownMenuGroup>
{column.getCanHide() && (
<>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem
onClick={() => column.toggleVisibility(false)}
>
<EyeOff />
Hide
</DropdownMenuItem>
</DropdownMenuGroup>
</>
)}
</DropdownMenuContent>
</DropdownMenu>
</div>
)
}export const columns = columnHelper.columns([
columnHelper.accessor("email", {
header: ({ column }) => (
<DataTableColumnHeader column={column} title="Email" />
),
}),
])Pagination#
Add pagination controls to your table including page size and selection count.
"use client"
import { type ReactTable, type RowData } from "@tanstack/react-table"
import {
ChevronLeft,
ChevronRight,
ChevronsLeft,
ChevronsRight,
} from "lucide-react"
import { Button } from "@/components/ui/button"
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
import { type DataTableFeatures } from "./data-table-features"
const pageSizes = [5, 10, 20, 30, 50]
interface DataTablePaginationProps<TData extends RowData> {
table: ReactTable<DataTableFeatures, TData>
}
export function DataTablePagination<TData extends RowData>({
table,
}: DataTablePaginationProps<TData>) {
return (
<div className="@container/pagination flex items-center justify-between gap-4">
<div className="flex-1 text-sm whitespace-nowrap text-muted-foreground">
{table.getFilteredSelectedRowModel().rows.length} of{" "}
{table.getFilteredRowModel().rows.length} row(s) selected.
</div>
<div className="flex items-center gap-6">
<div className="hidden items-center gap-2 @lg/pagination:flex">
<p className="text-sm font-medium">Rows per page</p>
<Select
value={table.state.pagination.pageSize}
onValueChange={(value) => {
if (value !== null) {
table.setPageSize(value)
}
}}
>
<SelectTrigger size="sm" className="w-[4.5rem]">
<SelectValue />
</SelectTrigger>
<SelectContent side="top">
<SelectGroup>
{pageSizes.map((pageSize) => (
<SelectItem key={pageSize} value={pageSize}>
{pageSize}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</div>
<div className="text-sm font-medium whitespace-nowrap tabular-nums">
Page {table.state.pagination.pageIndex + 1} of{" "}
{Math.max(table.getPageCount(), 1)}
</div>
<div className="flex items-center gap-2">
<Button
variant="outline"
size="icon-sm"
className="hidden @2xl/pagination:inline-flex"
onClick={() => table.setPageIndex(0)}
disabled={!table.getCanPreviousPage()}
>
<span className="sr-only">Go to first page</span>
<ChevronsLeft />
</Button>
<Button
variant="outline"
size="icon-sm"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
<span className="sr-only">Go to previous page</span>
<ChevronLeft />
</Button>
<Button
variant="outline"
size="icon-sm"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
<span className="sr-only">Go to next page</span>
<ChevronRight />
</Button>
<Button
variant="outline"
size="icon-sm"
className="hidden @2xl/pagination:inline-flex"
onClick={() => table.setPageIndex(table.getPageCount() - 1)}
disabled={!table.getCanNextPage()}
>
<span className="sr-only">Go to last page</span>
<ChevronsRight />
</Button>
</div>
</div>
</div>
)
}<DataTablePagination table={table} />Column toggle#
A component to toggle column visibility.
"use client"
import { type ReactTable, type RowData } from "@tanstack/react-table"
import { Settings2 } from "lucide-react"
import { Button } from "@/components/ui/button"
import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import { type DataTableFeatures } from "./data-table-features"
export function DataTableViewOptions<TData extends RowData>({
table,
}: {
table: ReactTable<DataTableFeatures, TData>
}) {
return (
<DropdownMenu>
<DropdownMenuTrigger
render={<Button variant="outline" className="ml-auto" />}
>
<Settings2 data-icon="inline-start" />
View
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-40">
<DropdownMenuGroup>
<DropdownMenuLabel>Toggle columns</DropdownMenuLabel>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
{table
.getAllColumns()
.filter(
(column) =>
typeof column.accessorFn !== "undefined" && column.getCanHide()
)
.map((column) => {
return (
<DropdownMenuCheckboxItem
key={column.id}
className="capitalize"
checked={column.getIsVisible()}
onCheckedChange={(value) => column.toggleVisibility(!!value)}
>
{column.id}
</DropdownMenuCheckboxItem>
)
})}
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
)
}<DataTableViewOptions table={table} />