{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "table-20",
  "title": "Complex data table",
  "description": "TanStack Table, combines sorting, column filter, pagination, row selection, column visibility toggle, and per-row action menus.",
  "dependencies": [
    "@tanstack/react-table"
  ],
  "registryDependencies": [
    "table",
    "button",
    "checkbox",
    "input",
    "badge",
    "dropdown-menu"
  ],
  "files": [
    {
      "path": "registry/patterns/table/table-20.tsx",
      "content": "'use client'\n\nimport { Badge } from '@/components/ui/badge'\nimport { Button } from '@/components/ui/button'\nimport { Checkbox } from '@/components/ui/checkbox'\nimport {\n  DropdownMenu,\n  DropdownMenuCheckboxItem,\n  DropdownMenuContent,\n  DropdownMenuGroup,\n  DropdownMenuItem,\n  DropdownMenuLabel,\n  DropdownMenuSeparator,\n  DropdownMenuTrigger,\n} from '@/components/ui/dropdown-menu'\nimport { Input } from '@/components/ui/input'\nimport {\n  Table,\n  TableBody,\n  TableCell,\n  TableHead,\n  TableHeader,\n  TableRow,\n} from '@/components/ui/table'\nimport {\n  type ColumnDef,\n  type ColumnFiltersState,\n  type RowSelectionState,\n  type SortingState,\n  type VisibilityState,\n  flexRender,\n  getCoreRowModel,\n  getFilteredRowModel,\n  getPaginationRowModel,\n  getSortedRowModel,\n  useReactTable,\n} from '@tanstack/react-table'\nimport {\n  ArrowUpDownIcon,\n  ChevronLeftIcon,\n  ChevronRightIcon,\n  MoreHorizontalIcon,\n  SlidersHorizontalIcon,\n} from 'lucide-react'\nimport { useState } from 'react'\n\ntype Payment = {\n  id: string\n  customer: string\n  email: string\n  status: 'paid' | 'pending' | 'failed'\n  amount: number\n}\n\nconst data: Payment[] = [\n  {\n    id: '1',\n    customer: 'Ken Adams',\n    email: 'ken99@example.com',\n    status: 'paid',\n    amount: 316,\n  },\n  {\n    id: '2',\n    customer: 'Abe Lincoln',\n    email: 'abe45@example.com',\n    status: 'pending',\n    amount: 242,\n  },\n  {\n    id: '3',\n    customer: 'Monserrat Diaz',\n    email: 'mon@example.com',\n    status: 'paid',\n    amount: 837,\n  },\n  {\n    id: '4',\n    customer: 'Silas Pena',\n    email: 'silas22@example.com',\n    status: 'failed',\n    amount: 874,\n  },\n  {\n    id: '5',\n    customer: 'Carmella Rau',\n    email: 'carmella@example.com',\n    status: 'paid',\n    amount: 721,\n  },\n  {\n    id: '6',\n    customer: 'Jason Bourne',\n    email: 'jason@example.com',\n    status: 'pending',\n    amount: 459,\n  },\n  {\n    id: '7',\n    customer: 'Nadia Hopper',\n    email: 'nadia@example.com',\n    status: 'paid',\n    amount: 612,\n  },\n  {\n    id: '8',\n    customer: 'Omar Vance',\n    email: 'omar@example.com',\n    status: 'failed',\n    amount: 188,\n  },\n]\n\nconst statusVariant: Record<\n  Payment['status'],\n  'secondary' | 'outline' | 'destructive'\n> = {\n  paid: 'secondary',\n  pending: 'outline',\n  failed: 'destructive',\n}\n\nconst columns: ColumnDef<Payment>[] = [\n  {\n    id: 'select',\n    header: ({ table }) => (\n      <Checkbox\n        aria-label=\"Select all\"\n        checked={table.getIsAllPageRowsSelected()}\n        indeterminate={table.getIsSomePageRowsSelected()}\n        onCheckedChange={(value) =>\n          table.toggleAllPageRowsSelected(value === true)\n        }\n      />\n    ),\n    cell: ({ row }) => (\n      <Checkbox\n        aria-label=\"Select row\"\n        checked={row.getIsSelected()}\n        onCheckedChange={(value) => row.toggleSelected(value === true)}\n      />\n    ),\n    enableSorting: false,\n    enableHiding: false,\n  },\n  {\n    accessorKey: 'customer',\n    header: ({ column }) => (\n      <Button\n        variant=\"ghost\"\n        size=\"sm\"\n        className=\"-ml-3\"\n        onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}\n      >\n        Customer\n        <ArrowUpDownIcon data-icon=\"inline-end\" />\n      </Button>\n    ),\n    cell: ({ row }) => (\n      <span className=\"font-medium\">{row.getValue('customer')}</span>\n    ),\n  },\n  {\n    accessorKey: 'email',\n    header: 'Email',\n    cell: ({ row }) => (\n      <span className=\"text-muted-foreground\">{row.getValue('email')}</span>\n    ),\n  },\n  {\n    accessorKey: 'status',\n    header: 'Status',\n    cell: ({ row }) => {\n      const status = row.getValue<Payment['status']>('status')\n      return (\n        <Badge variant={statusVariant[status]} className=\"capitalize\">\n          {status}\n        </Badge>\n      )\n    },\n  },\n  {\n    accessorKey: 'amount',\n    header: ({ column }) => (\n      <div className=\"text-right\">\n        <Button\n          variant=\"ghost\"\n          size=\"sm\"\n          className=\"-mr-3\"\n          onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}\n        >\n          Amount\n          <ArrowUpDownIcon data-icon=\"inline-end\" />\n        </Button>\n      </div>\n    ),\n    cell: ({ row }) => (\n      <div className=\"text-right font-medium tabular-nums\">\n        {new Intl.NumberFormat('en-US', {\n          style: 'currency',\n          currency: 'USD',\n        }).format(row.getValue<number>('amount'))}\n      </div>\n    ),\n  },\n  {\n    id: 'actions',\n    enableHiding: false,\n    cell: ({ row }) => (\n      <div className=\"text-right\">\n        <DropdownMenu>\n          <DropdownMenuTrigger\n            render={\n              <Button variant=\"ghost\" size=\"icon\" className=\"size-8\">\n                <MoreHorizontalIcon />\n                <span className=\"sr-only\">Open menu</span>\n              </Button>\n            }\n          />\n          <DropdownMenuContent align=\"end\">\n            <DropdownMenuGroup>\n              <DropdownMenuLabel>Actions</DropdownMenuLabel>\n            </DropdownMenuGroup>\n            <DropdownMenuItem\n              onClick={() => navigator.clipboard.writeText(row.original.id)}\n            >\n              Copy payment ID\n            </DropdownMenuItem>\n            <DropdownMenuSeparator />\n            <DropdownMenuItem>View customer</DropdownMenuItem>\n            <DropdownMenuItem variant=\"destructive\">Delete</DropdownMenuItem>\n          </DropdownMenuContent>\n        </DropdownMenu>\n      </div>\n    ),\n  },\n]\n\nexport function Table20() {\n  const [sorting, setSorting] = useState<SortingState>([])\n  const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([])\n  const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({})\n  const [rowSelection, setRowSelection] = useState<RowSelectionState>({})\n\n  const table = useReactTable({\n    data,\n    columns,\n    state: { sorting, columnFilters, columnVisibility, rowSelection },\n    onSortingChange: setSorting,\n    onColumnFiltersChange: setColumnFilters,\n    onColumnVisibilityChange: setColumnVisibility,\n    onRowSelectionChange: setRowSelection,\n    getCoreRowModel: getCoreRowModel(),\n    getSortedRowModel: getSortedRowModel(),\n    getFilteredRowModel: getFilteredRowModel(),\n    getPaginationRowModel: getPaginationRowModel(),\n    initialState: { pagination: { pageSize: 5 } },\n  })\n\n  return (\n    <div className=\"flex w-full max-w-3xl flex-col gap-3\">\n      <div className=\"flex items-center gap-2\">\n        <Input\n          placeholder=\"Filter customers...\"\n          value={\n            (table.getColumn('customer')?.getFilterValue() as string) ?? ''\n          }\n          onChange={(event) =>\n            table.getColumn('customer')?.setFilterValue(event.target.value)\n          }\n          className=\"max-w-xs\"\n        />\n        <DropdownMenu>\n          <DropdownMenuTrigger\n            render={\n              <Button variant=\"outline\" size=\"sm\" className=\"ml-auto\">\n                <SlidersHorizontalIcon data-icon=\"inline-start\" />\n                Columns\n              </Button>\n            }\n          />\n          <DropdownMenuContent align=\"end\">\n            {table\n              .getAllColumns()\n              .filter((column) => column.getCanHide())\n              .map((column) => (\n                <DropdownMenuCheckboxItem\n                  key={column.id}\n                  className=\"capitalize\"\n                  checked={column.getIsVisible()}\n                  onCheckedChange={(value) => column.toggleVisibility(!!value)}\n                  closeOnClick={false}\n                >\n                  {column.id}\n                </DropdownMenuCheckboxItem>\n              ))}\n          </DropdownMenuContent>\n        </DropdownMenu>\n      </div>\n      <div className=\"overflow-hidden rounded-lg border\">\n        <Table>\n          <TableHeader>\n            {table.getHeaderGroups().map((headerGroup) => (\n              <TableRow key={headerGroup.id}>\n                {headerGroup.headers.map((header) => (\n                  <TableHead key={header.id}>\n                    {header.isPlaceholder\n                      ? null\n                      : flexRender(\n                          header.column.columnDef.header,\n                          header.getContext(),\n                        )}\n                  </TableHead>\n                ))}\n              </TableRow>\n            ))}\n          </TableHeader>\n          <TableBody>\n            {table.getRowModel().rows.length ? (\n              table.getRowModel().rows.map((row) => (\n                <TableRow\n                  key={row.id}\n                  data-state={row.getIsSelected() ? 'selected' : undefined}\n                >\n                  {row.getVisibleCells().map((cell) => (\n                    <TableCell key={cell.id}>\n                      {flexRender(\n                        cell.column.columnDef.cell,\n                        cell.getContext(),\n                      )}\n                    </TableCell>\n                  ))}\n                </TableRow>\n              ))\n            ) : (\n              <TableRow>\n                <TableCell\n                  colSpan={columns.length}\n                  className=\"text-muted-foreground h-20 text-center\"\n                >\n                  No results.\n                </TableCell>\n              </TableRow>\n            )}\n          </TableBody>\n        </Table>\n      </div>\n      <div className=\"flex items-center justify-between gap-4\">\n        <span className=\"text-muted-foreground text-sm\">\n          {table.getFilteredSelectedRowModel().rows.length} of{' '}\n          {table.getFilteredRowModel().rows.length} row(s) selected.\n        </span>\n        <div className=\"flex items-center gap-1\">\n          <Button\n            variant=\"outline\"\n            size=\"icon\"\n            className=\"size-8\"\n            onClick={() => table.previousPage()}\n            disabled={!table.getCanPreviousPage()}\n            aria-label=\"Previous page\"\n          >\n            <ChevronLeftIcon />\n          </Button>\n          <Button\n            variant=\"outline\"\n            size=\"icon\"\n            className=\"size-8\"\n            onClick={() => table.nextPage()}\n            disabled={!table.getCanNextPage()}\n            aria-label=\"Next page\"\n          >\n            <ChevronRightIcon />\n          </Button>\n        </div>\n      </div>\n    </div>\n  )\n}\n",
      "type": "registry:component",
      "target": "components/flx/patterns/table/table-20.tsx"
    }
  ],
  "type": "registry:block"
}