جدول البيانات: الخطاف بلا واجهة
هذه الصفحة غير مترجمة بعد، وهي معروضة بالإنجليزية.
useDataTable is the data table without its markup: it holds the state, builds the TanStack Table instance and runs row reorder, and you render every element yourself. <DataTable> calls it for you; use it directly when no slot replacement gets you the markup you need.
| Title | Artist | Length | |
|---|---|---|---|
| Morning Light | The Harbour | 3:41 | |
| Paper Boats | Mina Sol | 4:05 | |
| Slow Tide | The Harbour | 5:12 | |
| North Road | Kai Brenner | 3:28 | |
| Last Train | Mina Sol | 4:47 |
import { flexRender } from "@tanstack/react-table";
import { useState, type CSSProperties } from "react";
import { useDataTable, type DataTableColumnDef, type DataTableLabels } from "slotsmith/data-table";
type Track = { id: string; title: string; artist: string; length: string };
const playlist: Track[] = [
{ id: "1", title: "Morning Light", artist: "The Harbour", length: "3:41" },
{ id: "2", title: "Paper Boats", artist: "Mina Sol", length: "4:05" },
{ id: "3", title: "Slow Tide", artist: "The Harbour", length: "5:12" },
{ id: "4", title: "North Road", artist: "Kai Brenner", length: "3:28" },
{ id: "5", title: "Last Train", artist: "Mina Sol", length: "4:47" },
];
const columns: DataTableColumnDef<Track>[] = [
{ accessorKey: "title", header: "Title" },
{ accessorKey: "artist", header: "Artist" },
{ accessorKey: "length", header: "Length" },
];
// The hook reads the reorder labels; any left out stay English.
const labels: Partial<DataTableLabels> = {
reorderRow: "Move track",
reorderInstructions: "Press space to pick the track up, the arrow keys to move it, space to put it down, escape to cancel.",
reorderCancelled: "Move cancelled.",
};
const cellStyle: CSSProperties = { padding: "0.375rem 0.5rem", borderBlockEnd: "1px solid", textAlign: "start" };
// No `outline` here: the handle keeps the browser's focus ring, so keyboard users can see which handle has focus.
const handleStyle: CSSProperties = { font: "inherit", color: "inherit", background: "none", border: 0, padding: "0 0.25rem" };
const hidden: CSSProperties = { position: "absolute", width: 1, height: 1, overflow: "hidden", clipPath: "inset(50%)", whiteSpace: "nowrap" };
export default function HeadlessHook() {
const [tracks, setTracks] = useState(playlist);
const { table, reorder } = useDataTable({
data: tracks,
columns,
getRowId: (track) => track.id,
enablePagination: false,
enableRowReorder: true,
onRowOrderChange: (change) => setTracks(change.data),
labels,
});
return (
<>
<table style={{ width: "100%", borderCollapse: "collapse" }}>
<thead>
{table.getHeaderGroups().map((group) => (
<tr key={group.id}>
<th style={cellStyle}>
<span style={hidden}>Order</span>
</th>
{group.headers.map((header) => (
<th key={header.id} style={cellStyle}>
{flexRender(header.column.columnDef.header, header.getContext())}
</th>
))}
</tr>
))}
</thead>
<tbody>
{table.getRowModel().rows.map((row) => {
// data-row-id always; data-dragging and data-drop-edge during a drag.
const attributes = reorder.getRowProps(row.id);
const handle = reorder.getHandleProps(row.id);
const lifted = attributes["data-dragging"] !== undefined;
const edge = attributes["data-drop-edge"];
return (
<tr
key={row.id}
{...attributes}
style={{
position: lifted ? "relative" : undefined,
background: lifted ? "Canvas" : undefined,
boxShadow: edge === "before" ? "inset 0 2px 0 0 currentColor" : edge === "after" ? "inset 0 -2px 0 0 currentColor" : undefined,
}}
>
<td style={cellStyle}>
<button type="button" {...handle} style={{ ...handle.style, ...handleStyle, cursor: lifted ? "grabbing" : "grab" }}>
⠿
</button>
</td>
{row.getAllCells().map((cell) => (
<td key={cell.id} style={cellStyle}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</td>
))}
</tr>
);
})}
</tbody>
</table>
{/* Every handle's aria-describedby points here. */}
<div id={reorder.instructionsId} hidden>
{labels.reorderInstructions}
</div>
{/* Always rendered: a live region added at the moment it changes is not read. */}
<div role="status" aria-live="assertive" aria-atomic="true" style={hidden}>
{reorder.announcement}
</div>
</>
);
}The demo renders a plain <table> with no slotsmith class or stylesheet. Its rows still reorder by pointer, touch and keyboard, with the handle named “Move track” from its own labels.
Options
useDataTable takes the same data and state options as the DataTable props: data, columns, getRowId, sorting, pagination, selection, expansion, loading, error, enableRowReorder and onRowOrderChange, each controlled or not in the same way. What it leaves out is everything about the markup: slots, slotProps, the size and locale.
One option is the hook’s own: labels. It reads the six reorder keys, the handle’s name, its instructions and the announcements; a key you leave out stays English. <DataTable> passes its own labels and locale to the hook instead.
What it returns
table: the TanStack Table instance. Render the header groups, rows and cells from it withflexRenderfrom@tanstack/react-table, as the demo does.status:"loading","error","empty"or"ready", for what the body should show;errorandonRetryare the options as passed.selection: the selected rows, including rows on other server pages, andgetRowKey, the id of any row.selectable,expandable,paginationEnabledandpageSizeOptions: what the options turned on.reorder,reorderableandreorderHandleColumn: row reorder, below.
Row reorder in your own markup
reorder is the engine <DataTable> uses. Wire it up in four places:
- Spread
reorder.getRowProps(rowId)on each body row. It givesdata-row-idalways, and during a dragdata-dragging,data-dragging-child,data-drop-positionanddata-drop-edge; the demo reads them to draw the lifted row and the drop line. - Spread
reorder.getHandleProps(rowId)on a<button>in each row. It carries the handlers,aria-label,aria-describedby,aria-pressed,disabledandtouch-action: none. - Render an element with the id
reorder.instructionsId, hidden, holding the instructions text. - Render
reorder.announcementin a live region (role="status",aria-live="assertive") that is always in the page: a live region added at the moment it changes is not read.
The rows must be siblings in one parent element, such as a <tbody>, because the engine measures and moves them there. reorder.draggingId and reorder.target give the drag state, and reorder.enabled says whether a drag can start now. Storing the order works as in the row reorder guide.
Between the two
To keep most of the table and replace one region, there is a middle way: DataTable.Provider runs the hook and shares its result, DataTable.Root, DataTable.Table and DataTable.Pagination render the parts, and useDataTableContext() reads the same model inside any component you put among them.