جدول بيانات
جداول قابلة للفرز والتصفية وتقسيمها إلى صفحات، مخصصة للوحات التحكم الإدارية ولوحات المعلومات وقوائم الطلبات — مع تطبيق أسلوب «النيوبروتاليست» في تصميم الحدود والظلال.
جدول البيانات (data table) دليل لا مكوّن جاهز: تركّب جدولك من مكوّن <Table /> وTanStack Table، محرك بلا واجهة للفرز والتصفية والترقيم واختيار الصفوف — مع وصفة النيوبروتالية من حدود سميكة وظلال حادة وخط عريض للمظهر.
استخدمه عندما:
- لوحات الإدارة — طلبات ومستخدمون وفواتير بأعمدة قابلة للفرز ومرشحات وقوائم إجراءات لكل صف.
- لوحات التحكم — جداول دفع أو نشاط بخلايا منسّقة وأعمدة حالة وبيانات API مرقّمة.
- سير عمل جماعي — اختيار صفوف بمربعات لتصدير أو أرشفة أو حذف سجلات كثيرة دفعة واحدة.
مقدمة#
كل جدول بيانات أو شبكة بيانات أنشأتها كان فريدًا. جميعها تتصرف بشكل مختلف، ولها متطلبات فرز وتصفية محددة، وتعمل مع مصادر بيانات مختلفة.
ليس من المنطقي دمج كل هذه الاختلافات في مكوّن واحد. إذا فعلنا ذلك، فسنفقد المرونة التي يوفرها headless UI.
لذلك، بدلًا من مكوّن data-table، رأيت أنه سيكون من الأفيد تقديم دليل حول كيفية بناء مكوّنك الخاص.
سنبدأ بالمكوّن الأساسي <Table /> ونبني جدول بيانات معقّدًا من الصفر.
نصيحة: إذا وجدت نفسك تستخدم الجدول نفسه في أماكن متعددة في تطبيقك، فيمكنك دائمًا استخراجه إلى مكوّن قابل لإعادة الاستخدام.
جدول المحتويات#
سيوضّح لك هذا الدليل كيفية استخدام TanStack Table والمكوّن <Table /> لبناء جدول بيانات مخصص خاص بك. سنغطّي المواضيع التالية:
- جدول أساسي
- إجراءات الصف
- ترقيم الصفحات
- الفرز
- التصفية
- الظهور
- تحديد الصفوف
- مكونات قابلة لإعادة الاستخدام
التثبيت#
- أضف المكوّن
<Table />إلى مشروعك:
pnpm dlx shadcn@latest add tablenpx shadcn@latest add tableyarn dlx shadcn@latest add tablebunx --bun shadcn@latest add table
- أضف التبعية
tanstack/react-table:
pnpm add @tanstack/react-tablenpm install @tanstack/react-tableyarn add @tanstack/react-tablebun 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(مكوّن خادم) هو المكان الذي سنجلب فيه البيانات ونعرض جدولنا.
جدول أساسي#
لنبدأ ببناء جدول أساسي.
تعريفات الأعمدة#
أولًا، سنعرّف أعمدتنا.
"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 /> لعرض جدولنا.
"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>
)
}نصيحة: إذا وجدت نفسك تستخدم <DataTable /> في أماكن متعددة، فهذا هو المكوّن الذي يمكنك جعله قابلًا لإعادة الاستخدام باستخراجه إلى components/ui/data-table.tsx.
<DataTable columns={columns} data={data} />
عرض الجدول#
أخيرًا، سنعرض جدولنا داخل مكوّن الصفحة لدينا.
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 للمبلغ على النحو التالي:
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 />.
"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>#
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().
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>#
"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 لإضافة عناصر تحكم في الفرز.
"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>#
"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>#
"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>
)
}يضيف هذا قائمة منسدلة يمكنك استخدامها لتبديل ظهور الأعمدة.
تحديد الصفوف#
بعد ذلك، سنضيف تحديد الصفوف إلى جدولنا.
تحديث تعريفات الأعمدة#
"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>#
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.
رأس العمود#
اجعل أي رأس عمود قابلًا للفرز وللإخفاء.
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 بلا واجهة — لا يعرض ترميزًا، فإمكانية الوصول تأتي من عناصر هذا الدليل. يخرج مكوّن <Table /> عناصر <table> و<th> و<td> الأصلية، فتعطي قارئات الشاشة سياق الصف والعمود وفق نمط WAI-ARIA Table. أمران للحفاظ عليهما أثناء البناء: عيّن aria-sort على رأس العمود المفرز حاليًا، واحتفظ بـ aria-label على مربعات اختيار التحديد وبنص sr-only على المشغّلات بأيقونة فقط (المقتطفات أعلاه تفعل الأمرين).
كل عنصر تحكم في هذا الدليل زر أو مربع اختيار أو مدخل أصلي، فسلوك لوحة المفاتيح هو افتراضي المتصفح:
| المفتاح | الإجراء |
|---|---|
Tab / Shift + Tab | نقل التركيز عبر عناصر التحكم في الجدول — مدخل التصفية، رؤوس الفرز، قوائم الصفوف، الترقيم |
Space / Enter | تفعيل زر الفرز أو الترقيم المركَّز |
Space | تبديل مربع اختيار الصف المركَّز أو تحديد الكل |