Wie implementiert man die Neuordnung mehrerer Zeilen per Drag-and-Drop in einer React TanStack-Tabellenkomponente?
Posted: 06 Jan 2025, 21:42
Ich habe eine React Table.js-Komponente, die die Möglichkeit hat, Zeilen auszuwählen und einzelne Zeilen per Drag-and-Drop neu anzuordnen:
https://codesandbox.io/p/sandbox/working-row-selecton- reorder-add-pagination-search-col-sort-forked-7y95lv?workspaceId=ws_F2QxELJDBKDDSAxdPEPbQt
In einigen Fällen verfügt mein Benutzer möglicherweise über Hunderte von Dateien und möchte eine Gruppe von 20 Zeilen neu anordnen. Der aktuelle Prozess der manuellen Neuordnung jeder Zeile ist sehr mühsam. Ich möchte einem Benutzer die Möglichkeit zum Ziehen hinzufügen und mehrere Zeilen auf einmal löschen und alle neu anordnen.
Ich habe einen guten UX-Beitrag mit Bildern dazu gefunden, wie das gemacht werden kann, und ich möchte versuchen, es zu implementieren meine React Table.js Komponente:
https://ux.stackexchange.com/questions/ ... ble-and-be -able-to-reorder-the-selec
Wenn ein Benutzer also mehrere Zeilen auswählt:

Sie können per Drag-and-Drop alle aktuell ausgewählten Zeilen mit einem speziellen UI-Popup auf der linken Seite neu anordnen der Tabelle:

Derzeit in Mein codesandbox.io-Projekt kann der Benutzer mehrere Dateien auswählen und immer nur jeweils eine Zeile neu anordnen:

Ich habe versucht, dies hier zu implementieren:
https://codesandbox.io/p/sandbox/drag-a ... SAxdPEPbQt
Aber da Es gibt keine Geisterzeilen-Benutzeroberfläche, die angibt, wie viele Zeilen ich neu anordne, was den Prozess nicht intuitiv oder sauber macht:
https://codesandbox.io/p/sandbox/working-row-selecton- reorder-add-pagination-search-col-sort-forked-7y95lv?workspaceId=ws_F2QxELJDBKDDSAxdPEPbQt
In einigen Fällen verfügt mein Benutzer möglicherweise über Hunderte von Dateien und möchte eine Gruppe von 20 Zeilen neu anordnen. Der aktuelle Prozess der manuellen Neuordnung jeder Zeile ist sehr mühsam. Ich möchte einem Benutzer die Möglichkeit zum Ziehen hinzufügen und mehrere Zeilen auf einmal löschen und alle neu anordnen.
Ich habe einen guten UX-Beitrag mit Bildern dazu gefunden, wie das gemacht werden kann, und ich möchte versuchen, es zu implementieren meine React Table.js Komponente:
https://ux.stackexchange.com/questions/ ... ble-and-be -able-to-reorder-the-selec
Wenn ein Benutzer also mehrere Zeilen auswählt:

Sie können per Drag-and-Drop alle aktuell ausgewählten Zeilen mit einem speziellen UI-Popup auf der linken Seite neu anordnen der Tabelle:

Derzeit in Mein codesandbox.io-Projekt kann der Benutzer mehrere Dateien auswählen und immer nur jeweils eine Zeile neu anordnen:

Ich habe versucht, dies hier zu implementieren:
https://codesandbox.io/p/sandbox/drag-a ... SAxdPEPbQt
Aber da Es gibt keine Geisterzeilen-Benutzeroberfläche, die angibt, wie viele Zeilen ich neu anordne, was den Prozess nicht intuitiv oder sauber macht:
Code: Select all
//Table.js
// Table.js
import React, { useState } from "react";
import {
ColumnDef,
getCoreRowModel,
useReactTable,
flexRender,
getSortedRowModel,
getFilteredRowModel,
getPaginationRowModel,
} from "@tanstack/react-table";
import { DndContext, closestCenter } from "@dnd-kit/core";
import {
useSortable,
SortableContext,
verticalListSortingStrategy,
arrayMove,
} from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
import styles from "./Table.module.css";
// Indeterminate Checkbox Component
function IndeterminateCheckbox({ indeterminate, className = "", ...rest }) {
const ref = React.useRef(null);
React.useEffect(() => {
if (typeof indeterminate === "boolean") {
ref.current.indeterminate = indeterminate;
}
}, [indeterminate]);
return (
);
}
// Drag handle for rows
function DragHandle({ row }) {
const { attributes, listeners } = useSortable({ id: row.original.id });
return (
🟰
);
}
// Row Component
function Row({ row }) {
const { setNodeRef, transform, transition } = useSortable({
id: row.original.id,
});
const isSelected = row.getIsSelected();
const style = {
transform: CSS.Transform.toString(transform),
transition,
backgroundColor: isSelected ? "#e0f7fa" : "inherit", // Highlight selected rows
};
return (
{row.getVisibleCells().map((cell, index) => (
{index === 1 ? : null}
{flexRender(cell.column.columnDef.cell, cell.getContext())}
))}
);
}
// Table Component
function Table({ data, setData, columns, rowSelection, setRowSelection }) {
const [globalFilter, setGlobalFilter] = useState("");
const [sorting, setSorting] = useState([]);
const tableColumns = React.useMemo(() => [
{
id: "select",
header: ({ table }) => (
),
cell: ({ row }) => (
),
},
{ accessorKey: "draggable", header: "Drag" },
{ accessorKey: "fileName", header: "File Name" },
{ accessorKey: "duration", header: "Duration" },
]);
const table = useReactTable({
data,
columns: tableColumns,
getRowId: (row) => row.id,
state: { rowSelection, globalFilter, sorting },
onRowSelectionChange: setRowSelection,
onSortingChange: setSorting,
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel(),
getPaginationRowModel: getPaginationRowModel(),
});
const handleDragEnd = (event) => {
const { active, over } = event;
if (active && over && active.id !== over.id) {
// Get the IDs of all selected rows
const selectedRowIds = Object.keys(rowSelection).filter(
(id) => rowSelection[id]
);
if (selectedRowIds.length > 0) {
// Find the indices of the selected rows
const selectedRows = data.filter((item) =>
selectedRowIds.includes(item.id)
);
const otherRows = data.filter(
(item) => !selectedRowIds.includes(item.id)
);
// Determine the target index in `otherRows` where the first selected row is dropped
const targetIndex = otherRows.findIndex((item) => item.id === over.id);
// Rebuild the data array with the selected rows inserted at the target index
const newData = [
...otherRows.slice(0, targetIndex),
...selectedRows,
...otherRows.slice(targetIndex),
];
setData(newData);
} else {
// Single row drag and drop
const oldIndex = data.findIndex((item) => item.id === active.id);
const newIndex = data.findIndex((item) => item.id === over.id);
if (oldIndex !== -1 && newIndex !== -1) {
const newData = arrayMove([...data], oldIndex, newIndex);
setData(newData);
}
}
}
};
return (
setGlobalFilter(e.target.value)}
placeholder="Search..."
className={styles.search}
/>
row.id)}
strategy={verticalListSortingStrategy}
>
{table.getHeaderGroups().map((headerGroup) => (
{headerGroup.headers.map((header) => (
header.column.toggleSorting()
: undefined
}
>
{flexRender(
header.column.columnDef.header,
header.getContext()
)}
{header.column.getIsSorted() === "asc" ? " 🔼" : ""}
{header.column.getIsSorted() === "desc" ? " 🔽" : ""}
))}
))}
{table.getRowModel().rows.map((row) => (
))}
table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
Previous
Page {table.getState().pagination.pageIndex + 1} of{" "}
{table.getPageCount()}
table.nextPage()}
disabled={!table.getCanNextPage()}
>
Next
{Object.keys(rowSelection).length} of {data.length} rows selected
);
}
export default Table;