דילוג לתוכן

טבלת נתונים

טבלאות הניתנות למיון, לסינון ולחלוקה לעמודים עבור לוחות בקרה, לוחות מחוונים ורשימות הזמנות — עם עיצוב בסגנון "ניאו-ברוטליסטי" הכולל מסגרות וצללים.

ה-data table הוא מדריך, לא רכיב ארוז: אתם מרכיבים טבלה משלכם מרכיב <Table /> ומ-TanStack Table, מנוע headless למיון, סינון, עימוד ובחירת שורות — כשהמתכון הנאו-ברוטליסטי של מסגרות עבות, צללים חדים וטיפוגרפיה מודגשת מטפל במראה.

השתמשו בו כש:

  • פאנלי ניהול — הזמנות, משתמשים וחשבוניות עם עמודות ממוינות, מסננים ותפריטי פעולה לשורה.
  • לוחות מחוונים — טבלאות תשלומים או פעילות עם תאים מעוצבים, עמודות סטטוס ונתוני API עם עימוד.
  • זרימות מרובות — בחירת שורות ב-checkbox לייצוא, ארכוב או מחיקה של רשומות רבות בבת אחת.

מבוא

כל טבלת נתונים או רשת נתונים שיצרתי הייתה ייחודית. כולן מתנהגות אחרת, יש להן דרישות מיון וסינון ספציפיות, והן עובדות עם מקורות נתונים שונים.

אין היגיון לשלב את כל הווריאציות האלה ברכיב יחיד. אם נעשה זאת, נאבד את הגמישות שheadless UI מספק.

לכן, במקום רכיב data-table, חשבתי שיהיה מועיל יותר לספק מדריך לבניית רכיב משלכם.

נתחיל מהרכיב הבסיסי <Table /> ונבנה טבלת נתונים מורכבת מאפס.

תוכן העניינים

מדריך זה יראה לכם כיצד להשתמש ב-TanStack Table וברכיב <Table /> כדי לבנות טבלת נתונים מותאמת אישית משלכם. נעסוק בנושאים הבאים:

התקנה

  1. הוסיפו את הרכיב <Table /> לפרויקט שלכם:
pnpm dlx shadcn@latest add table
npx shadcn@latest add table
yarn dlx shadcn@latest add table
bunx --bun shadcn@latest add table
  1. הוסיפו את התלות tanstack/react-table:
pnpm add @tanstack/react-table
npm install @tanstack/react-table
yarn add @tanstack/react-table
bun add @tanstack/react-table

דרישות מקדימות

אנחנו הולכים לבנות טבלה שתציג תשלומים אחרונים. כך נראים הנתונים שלנו:

type Payment = {
  id: string
  amount: number
  status: "pending" | "processing" | "success" | "failed"
  email: string
}
 
export const payments: Payment[] = [
  {
    id: "728ed52f",
    amount: 100,
    status: "pending",
    email: "[email protected]",
  },
  {
    id: "489e1d42",
    amount: 125,
    status: "processing",
    email: "[email protected]",
  },
  // ...
]

מבנה הפרויקט

התחילו ביצירת מבנה הקבצים הבא:

app
└── payments
    ├── columns.tsx
    ├── data-table.tsx
    └── page.tsx

אני משתמש כאן בדוגמה של Next.js, אבל זה עובד עם כל פריימוורק React אחר.

  • columns.tsx (רכיב לקוח) יכיל את הגדרות העמודות שלנו.
  • data-table.tsx (רכיב לקוח) יכיל את הרכיב <DataTable /> שלנו.
  • page.tsx (רכיב שרת) הוא המקום שבו נשלוף נתונים ונרנדר את הטבלה שלנו.

טבלה בסיסית

בואו נתחיל בבניית טבלה בסיסית.

הגדרות עמודות

ראשית, נגדיר את העמודות שלנו.

app/payments/columns.tsx
"use client"
 
import { ColumnDef } from "@tanstack/react-table"
 
// 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
}
 
export const columns: ColumnDef<Payment>[] = [
  {
    accessorKey: "status",
    header: "Status",
  },
  {
    accessorKey: "email",
    header: "Email",
  },
  {
    accessorKey: "amount",
    header: "Amount",
  },
]

רכיב <DataTable />

בהמשך, ניצור רכיב <DataTable /> כדי לרנדר את הטבלה שלנו.

app/payments/data-table.tsx
"use client"
 
import {
  ColumnDef,
  flexRender,
  getCoreRowModel,
  useReactTable,
} from "@tanstack/react-table"
 
import {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from "@/components/ui/table"
 
interface DataTableProps<TData, TValue> {
  columns: ColumnDef<TData, TValue>[]
  data: TData[]
}
 
export function DataTable<TData, TValue>({
  columns,
  data,
}: DataTableProps<TData, TValue>) {
  const table = useReactTable({
    data,
    columns,
    getCoreRowModel: getCoreRowModel(),
  })
 
  return (
    <div className="overflow-hidden rounded-md border">
      <Table>
        <TableHeader>
          {table.getHeaderGroups().map((headerGroup) => (
            <TableRow key={headerGroup.id}>
              {headerGroup.headers.map((header) => {
                return (
                  <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>
  )
}

רינדור הטבלה

לבסוף, נרנדר את הטבלה שלנו ברכיב הדף שלנו.

app/payments/page.tsx
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: "[email protected]",
    },
    // ...
  ]
}
 
export default async function DemoPage() {
  const data = await getData()
 
  return (
    <div className="container mx-auto py-10">
      <DataTable columns={columns} data={data} />
    </div>
  )
}

עיצוב תאים

בואו נעצב את תא הסכום כך שיציג את הסכום בדולרים. כמו כן ניישר את התא לימין.

עדכון הגדרת העמודות

עדכנו את הגדרות ה-header וה-cell עבור הסכום באופן הבא:

app/payments/columns.tsx
export const columns: ColumnDef<Payment>[] = [
  {
    accessorKey: "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>
    },
  },
]

אפשר להשתמש באותה גישה כדי לעצב תאים וכותרות אחרים.

פעולות שורה

בואו נוסיף פעולות שורה לטבלה שלנו. נשתמש לשם כך ברכיב <DropdownMenu />.

עדכון הגדרת העמודות

עדכנו את הגדרת העמודות שלנו כדי להוסיף עמודת actions חדשה. תא ה-actions מחזיר רכיב <DropdownMenu />.

app/payments/columns.tsx
"use client"
 
import { ColumnDef } from "@tanstack/react-table"
import { MoreHorizontal } from "lucide-react"
 
import { Button } from "@/components/ui/button"
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuLabel,
  DropdownMenuSeparator,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
 
export const columns: ColumnDef<Payment>[] = [
  // ...
  {
    id: "actions",
    cell: ({ row }) => {
      const payment = row.original
 
      return (
        <DropdownMenu>
          <DropdownMenuTrigger asChild>
            <Button variant="ghost" className="h-8 w-8 p-0">
              <span className="sr-only">Open menu</span>
              <MoreHorizontal className="h-4 w-4" />
            </Button>
          </DropdownMenuTrigger>
          <DropdownMenuContent align="end">
            <DropdownMenuLabel>Actions</DropdownMenuLabel>
            <DropdownMenuItem
              onClick={() => navigator.clipboard.writeText(payment.id)}
            >
              Copy payment ID
            </DropdownMenuItem>
            <DropdownMenuSeparator />
            <DropdownMenuItem>View customer</DropdownMenuItem>
            <DropdownMenuItem>View payment details</DropdownMenuItem>
          </DropdownMenuContent>
        </DropdownMenu>
      )
    },
  },
  // ...
]

אפשר לגשת לנתוני השורה באמצעות row.original בתוך הפונקציה cell. השתמשו בזה כדי לטפל בפעולות עבור השורה שלכם, לדוגמה השתמשו ב-id כדי לבצע קריאת DELETE ל-API שלכם.

עימוד

בהמשך, נוסיף עימוד לטבלה שלנו.

עדכון <DataTable>

app/payments/data-table.tsx
import {
  ColumnDef,
  flexRender,
  getCoreRowModel,
  getPaginationRowModel,
  useReactTable,
} from "@tanstack/react-table"
 
export function DataTable<TData, TValue>({
  columns,
  data,
}: DataTableProps<TData, TValue>) {
  const table = useReactTable({
    data,
    columns,
    getCoreRowModel: getCoreRowModel(),
    getPaginationRowModel: getPaginationRowModel(),
  })
 
  // ...
}

פעולה זו תחלק אוטומטית את השורות שלכם לעמודים של 10. למידע נוסף על התאמת גודל העמוד ומימוש עימוד ידני, עיינו בתיעוד העימוד.

הוספת פקדי עימוד

אפשר להוסיף פקדי עימוד לטבלה שלנו באמצעות הרכיב <Button /> ושיטות ה-API table.previousPage() ו-table.nextPage().

app/payments/data-table.tsx
import { Button } from "@/components/ui/button"
 
export function DataTable<TData, TValue>({
  columns,
  data,
}: DataTableProps<TData, TValue>) {
  const table = useReactTable({
    data,
    columns,
    getCoreRowModel: getCoreRowModel(),
    getPaginationRowModel: getPaginationRowModel(),
  })
 
  return (
    <div>
      <div className="overflow-hidden rounded-md border">
        <Table>
          { // .... }
        </Table>
      </div>
      <div className="flex items-center justify-end space-x-2 py-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>
  )
}

עיינו במקטע רכיבים לשימוש חוזר לקבלת רכיב עימוד מתקדם יותר.

מיון

בואו נהפוך את עמודת האימייל לניתנת למיון.

עדכון <DataTable>

app/payments/data-table.tsx
"use client"
 
import * as React from "react"
import {
  ColumnDef,
  SortingState,
  flexRender,
  getCoreRowModel,
  getPaginationRowModel,
  getSortedRowModel,
  useReactTable,
} from "@tanstack/react-table"
 
export function DataTable<TData, TValue>({
  columns,
  data,
}: DataTableProps<TData, TValue>) {
  const [sorting, setSorting] = React.useState<SortingState>([])
 
  const table = useReactTable({
    data,
    columns,
    getCoreRowModel: getCoreRowModel(),
    getPaginationRowModel: getPaginationRowModel(),
    onSortingChange: setSorting,
    getSortedRowModel: getSortedRowModel(),
    state: {
      sorting,
    },
  })
 
  return (
    <div>
      <div className="overflow-hidden rounded-md border">
        <Table>{ ... }</Table>
      </div>
    </div>
  )
}

הפיכת תא הכותרת לניתן למיון

כעת אפשר לעדכן את תא הכותרת email כדי להוסיף פקדי מיון.

app/payments/columns.tsx
"use client"
 
import { ColumnDef } from "@tanstack/react-table"
import { ArrowUpDown } from "lucide-react"
 
export const columns: ColumnDef<Payment>[] = [
  {
    accessorKey: "email",
    header: ({ column }) => {
      return (
        <Button
          variant="ghost"
          onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
        >
          Email
          <ArrowUpDown className="ml-2 h-4 w-4" />
        </Button>
      )
    },
  },
]

פעולה זו תמיין אוטומטית את הטבלה (בסדר עולה ויורד) כאשר המשתמש מחליף מצב בתא הכותרת.

סינון

בואו נוסיף שדה חיפוש לסינון כתובות האימייל בטבלה שלנו.

עדכון <DataTable>

app/payments/data-table.tsx
"use client"
 
import * as React from "react"
import {
  ColumnDef,
  ColumnFiltersState,
  SortingState,
  flexRender,
  getCoreRowModel,
  getFilteredRowModel,
  getPaginationRowModel,
  getSortedRowModel,
  useReactTable,
} from "@tanstack/react-table"
 
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
 
export function DataTable<TData, TValue>({
  columns,
  data,
}: DataTableProps<TData, TValue>) {
  const [sorting, setSorting] = React.useState<SortingState>([])
  const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
    []
  )
 
  const table = useReactTable({
    data,
    columns,
    onSortingChange: setSorting,
    getCoreRowModel: getCoreRowModel(),
    getPaginationRowModel: getPaginationRowModel(),
    getSortedRowModel: getSortedRowModel(),
    onColumnFiltersChange: setColumnFilters,
    getFilteredRowModel: getFilteredRowModel(),
    state: {
      sorting,
      columnFilters,
    },
  })
 
  return (
    <div>
      <div className="flex items-center py-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>
      <div className="overflow-hidden rounded-md border">
        <Table>{ ... }</Table>
      </div>
    </div>
  )
}

הסינון מופעל כעת עבור עמודת email. אפשר להוסיף מסננים גם לעמודות אחרות. למידע נוסף על התאמת המסננים, עיינו בתיעוד הסינון.

נראות

הוספת נראות עמודות היא פשוטה למדי בעזרת ה-API של הנראות של @tanstack/react-table.

עדכון <DataTable>

app/payments/data-table.tsx
"use client"
 
import * as React from "react"
import {
  ColumnDef,
  ColumnFiltersState,
  SortingState,
  VisibilityState,
  flexRender,
  getCoreRowModel,
  getFilteredRowModel,
  getPaginationRowModel,
  getSortedRowModel,
  useReactTable,
} from "@tanstack/react-table"
 
import { Button } from "@/components/ui/button"
import {
  DropdownMenu,
  DropdownMenuCheckboxItem,
  DropdownMenuContent,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
 
export function DataTable<TData, TValue>({
  columns,
  data,
}: DataTableProps<TData, TValue>) {
  const [sorting, setSorting] = React.useState<SortingState>([])
  const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
    []
  )
  const [columnVisibility, setColumnVisibility] =
    React.useState<VisibilityState>({})
 
  const table = useReactTable({
    data,
    columns,
    onSortingChange: setSorting,
    onColumnFiltersChange: setColumnFilters,
    getCoreRowModel: getCoreRowModel(),
    getPaginationRowModel: getPaginationRowModel(),
    getSortedRowModel: getSortedRowModel(),
    getFilteredRowModel: getFilteredRowModel(),
    onColumnVisibilityChange: setColumnVisibility,
    state: {
      sorting,
      columnFilters,
      columnVisibility,
    },
  })
 
  return (
    <div>
      <div className="flex items-center py-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 asChild>
            <Button variant="outline" className="ml-auto">
              Columns
            </Button>
          </DropdownMenuTrigger>
          <DropdownMenuContent align="end">
            {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>
                )
              })}
          </DropdownMenuContent>
        </DropdownMenu>
      </div>
      <div className="overflow-hidden rounded-md border">
        <Table>{ ... }</Table>
      </div>
    </div>
  )
}

פעולה זו מוסיפה תפריט נפתח שבו אפשר להשתמש כדי להחליף את נראות העמודות.

בחירת שורות

בהמשך, נוסיף בחירת שורות לטבלה שלנו.

עדכון הגדרות העמודות

app/payments/columns.tsx
"use client"
 
import { ColumnDef } from "@tanstack/react-table"
 
import { Badge } from "@/components/ui/badge"
import { Checkbox } from "@/components/ui/checkbox"
 
export const columns: ColumnDef<Payment>[] = [
  {
    id: "select",
    header: ({ table }) => (
      <Checkbox
        checked={
          table.getIsAllPageRowsSelected() ||
          (table.getIsSomePageRowsSelected() && "indeterminate")
        }
        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,
  },
]

עדכון <DataTable>

app/payments/data-table.tsx
export function DataTable<TData, TValue>({
  columns,
  data,
}: DataTableProps<TData, TValue>) {
  const [sorting, setSorting] = React.useState<SortingState>([])
  const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
    []
  )
  const [columnVisibility, setColumnVisibility] =
    React.useState<VisibilityState>({})
  const [rowSelection, setRowSelection] = React.useState({})
 
  const table = useReactTable({
    data,
    columns,
    onSortingChange: setSorting,
    onColumnFiltersChange: setColumnFilters,
    getCoreRowModel: getCoreRowModel(),
    getPaginationRowModel: getPaginationRowModel(),
    getSortedRowModel: getSortedRowModel(),
    getFilteredRowModel: getFilteredRowModel(),
    onColumnVisibilityChange: setColumnVisibility,
    onRowSelectionChange: setRowSelection,
    state: {
      sorting,
      columnFilters,
      columnVisibility,
      rowSelection,
    },
  })
 
  return (
    <div>
      <div className="overflow-hidden rounded-md border">
        <Table />
      </div>
    </div>
  )
}

פעולה זו מוסיפה תיבת סימון לכל שורה ותיבת סימון בכותרת כדי לבחור את כל השורות.

הצגת השורות שנבחרו

אפשר להציג את מספר השורות שנבחרו באמצעות ה-API table.getFilteredSelectedRowModel().

<div className="flex-1 text-sm text-muted-foreground">
  {table.getFilteredSelectedRowModel().rows.length} of{" "}
  {table.getFilteredRowModel().rows.length} row(s) selected.
</div>

רכיבים לשימוש חוזר

הנה כמה רכיבים שאפשר להשתמש בהם כדי לבנות את טבלאות הנתונים שלכם. הם מתוך הדמו Tasks.

כותרת עמודה

הפכו כל כותרת עמודה לניתנת למיון ולהסתרה.

components/data-table-column-header.tsx
import { type Column } from "@tanstack/react-table"
import { ArrowDown, ArrowUp, ChevronsUpDown, EyeOff } from "lucide-react"

import { cn } from "@/lib/utils"
import { Button } from "@/registry/base/ui/button"
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuSeparator,
  DropdownMenuTrigger,
} from "@/registry/base/ui/dropdown-menu"

interface DataTableColumnHeaderProps<
  TData,
  TValue,
> extends React.HTMLAttributes<HTMLDivElement> {
  column: Column<TData, TValue>
  title: string
}

export function DataTableColumnHeader<TData, TValue>({
  column,
  title,
  className,
}: DataTableColumnHeaderProps<TData, TValue>) {
  if (!column.getCanSort()) {
    return <div className={cn(className)}>{title}</div>
  }

  return (
    <div className={cn("flex items-center gap-2", className)}>
      <DropdownMenu>
        <DropdownMenuTrigger
          render={
            <Button
              variant="ghost"
              size="sm"
              className="-ml-3 h-8 data-popup-open:bg-accent"
            />
          }
        >
          <span>{title}</span>
          {column.getIsSorted() === "desc" ? (
            <ArrowDown />
          ) : column.getIsSorted() === "asc" ? (
            <ArrowUp />
          ) : (
            <ChevronsUpDown />
          )}
        </DropdownMenuTrigger>
        <DropdownMenuContent align="start">
          <DropdownMenuItem onClick={() => column.toggleSorting(false)}>
            <ArrowUp />
            Asc
          </DropdownMenuItem>
          <DropdownMenuItem onClick={() => column.toggleSorting(true)}>
            <ArrowDown />
            Desc
          </DropdownMenuItem>
          <DropdownMenuSeparator />
          <DropdownMenuItem onClick={() => column.toggleVisibility(false)}>
            <EyeOff />
            Hide
          </DropdownMenuItem>
        </DropdownMenuContent>
      </DropdownMenu>
    </div>
  )
}
export const columns = [
  {
    accessorKey: "email",
    header: ({ column }) => (
      <DataTableColumnHeader column={column} title="Email" />
    ),
  },
]

עימוד

הוסיפו פקדי עימוד לטבלה שלכם, כולל גודל עמוד ומספר הפריטים שנבחרו.

import { type Table } from "@tanstack/react-table"
import {
  ChevronLeft,
  ChevronRight,
  ChevronsLeft,
  ChevronsRight,
} from "lucide-react"

import { Button } from "@/registry/base/ui/button"
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/registry/base/ui/select"

const pageSizeItems = [10, 20, 25, 30, 40, 50].map((pageSize) => ({
  label: `${pageSize}`,
  value: `${pageSize}`,
}))

interface DataTablePaginationProps<TData> {
  table: Table<TData>
}

export function DataTablePagination<TData>({
  table,
}: DataTablePaginationProps<TData>) {
  return (
    <div className="flex items-center justify-between px-2">
      <div className="flex-1 text-sm text-muted-foreground">
        {table.getFilteredSelectedRowModel().rows.length} of{" "}
        {table.getFilteredRowModel().rows.length} row(s) selected.
      </div>
      <div className="flex items-center space-x-6 lg:space-x-8">
        <div className="flex items-center space-x-2">
          <p className="text-sm font-medium">Rows per page</p>
          <Select
            items={pageSizeItems}
            value={`${table.getState().pagination.pageSize}`}
            onValueChange={(value) => {
              if (value !== null) {
                table.setPageSize(Number(value))
              }
            }}
          >
            <SelectTrigger className="h-8 w-[70px]">
              <SelectValue />
            </SelectTrigger>
            <SelectContent side="top">
              {pageSizeItems.map((item) => (
                <SelectItem key={item.value} value={item.value}>
                  {item.label}
                </SelectItem>
              ))}
            </SelectContent>
          </Select>
        </div>
        <div className="flex w-[100px] items-center justify-center text-sm font-medium">
          Page {table.getState().pagination.pageIndex + 1} of{" "}
          {table.getPageCount()}
        </div>
        <div className="flex items-center space-x-2">
          <Button
            variant="outline"
            size="icon"
            className="hidden size-8 lg:flex"
            onClick={() => table.setPageIndex(0)}
            disabled={!table.getCanPreviousPage()}
          >
            <span className="sr-only">Go to first page</span>
            <ChevronsLeft />
          </Button>
          <Button
            variant="outline"
            size="icon"
            className="size-8"
            onClick={() => table.previousPage()}
            disabled={!table.getCanPreviousPage()}
          >
            <span className="sr-only">Go to previous page</span>
            <ChevronLeft />
          </Button>
          <Button
            variant="outline"
            size="icon"
            className="size-8"
            onClick={() => table.nextPage()}
            disabled={!table.getCanNextPage()}
          >
            <span className="sr-only">Go to next page</span>
            <ChevronRight />
          </Button>
          <Button
            variant="outline"
            size="icon"
            className="hidden size-8 lg: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} />

החלפת עמודות

רכיב להחלפת נראות העמודות.

"use client"

import { type Table } from "@tanstack/react-table"
import { Settings2 } from "lucide-react"

import { Button } from "@/registry/base/ui/button"
import {
  DropdownMenu,
  DropdownMenuCheckboxItem,
  DropdownMenuContent,
  DropdownMenuGroup,
  DropdownMenuLabel,
  DropdownMenuSeparator,
  DropdownMenuTrigger,
} from "@/registry/base/ui/dropdown-menu"

export function DataTableViewOptions<TData>({
  table,
}: {
  table: Table<TData>
}) {
  return (
    <DropdownMenu>
      <DropdownMenuTrigger
        render={
          <Button
            variant="outline"
            size="sm"
            className="ml-auto hidden h-8 lg:flex"
          />
        }
      >
        <Settings2 />
        View
      </DropdownMenuTrigger>
      <DropdownMenuContent align="end" className="w-[150px]">
        {/* GroupLabel (Base UI) needs a Group ancestor or it throws error #31. */}
        <DropdownMenuGroup>
          <DropdownMenuLabel>Toggle columns</DropdownMenuLabel>
        </DropdownMenuGroup>
        <DropdownMenuSeparator />
        {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>
            )
          })}
      </DropdownMenuContent>
    </DropdownMenu>
  )
}
<DataTableViewOptions table={table} />

RTL

כדי להפעיל RTL ב-Neobrutalism, עיינו במדריך ההגדרה של RTL.

נגישות

TanStack Table הוא headless — הוא לא מרנדר markup, אז הנגישות מגיעה מהאלמנטים במדריך הזה. רכיב <Table /> מוציא אלמנטי <table>, <th> ו-<td> מקוריים, שנותנים לקוראי מסך הקשר של שורה ועמודה לפי דפוס Table של WAI-ARIA. שני דברים לשמור כשבונים: הגדירו aria-sort על כותרת המיון הנוכחית, והשאירו את aria-label על checkboxes של בחירה ואת טקסט ה-sr-only על טריגרים עם אייקון בלבד (הקטעים למעלה עושים את שניהם).

כל בקרה במדריך הזה היא כפתור, checkbox או input מקורי, כך שהתנהגות המקלדת היא ברירת המחדל של הדפדפן:

מקשפעולה
Tab / Shift + Tabמעבירים מוקד דרך בקרות הטבלה — שדה מסנן, כותרות מיון, תפריטי שורה, עימוד
Space / Enterמפעילים את כפתור המיון או העימוד שבפוקוס
Spaceמחליפים את ה-checkbox של השורה שבפוקוס או select-all