Skip to content

Commit 1d0027e

Browse files
Drag-and-Drop feature (#59)
This PR is related to an implementation of draggability rows feature on the current table. 1. It relies on `dnd-kit` package 2. It exposes a `dragAndDropOptions` object to the `ReactDataTableProps`, which could be also enriched in the future with more properties. 3. It allows to externally define the "draggable" column, so that it can be moved and styled as the consumer wish.
1 parent aae4a67 commit 1d0027e

10 files changed

Lines changed: 956 additions & 164 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Added
1111

12+
- option `dragAndDropOptions` to enable drag-and-drop rows via `@dnd-kit` package.
13+
14+
### Added
15+
1216
- option `cellClassName` added to the column meta to add class names to every cell of a column
1317
- option `headerClassName` added to the column meta to add class names to every header of a column
1418
- option `footerStyle` added to the column meta to add styles to every footer of a column

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@
4646
"tsc": "tsc"
4747
},
4848
"dependencies": {
49+
"@dnd-kit/core": "^6.1.0",
50+
"@dnd-kit/modifiers": "^7.0.0",
51+
"@dnd-kit/sortable": "^8.0.0",
4952
"@neolution-ch/react-pattern-ui": "^3.4.0",
5053
"@tanstack/react-table": "^8.10.7",
5154
"react-loading-skeleton": "^3.3.1"
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { DragEndEvent } from "@dnd-kit/core/dist/types/events";
2+
3+
/**
4+
* The props for the dragAndDropOptions
5+
*/
6+
export interface DragAndDropOptions {
7+
/**
8+
* enable or disable the drag-and-drop feature.
9+
*/
10+
enableDragAndDrop: boolean;
11+
/**
12+
* the handle drag end method to be called once the row drag has fulfilled
13+
* @param event the drag end event
14+
*/
15+
onDragEnd(event: DragEndEvent): void;
16+
}

src/lib/ReactDataTable/ReactDataTable.tsx

Lines changed: 200 additions & 162 deletions
Large diffs are not rendered by default.

src/lib/ReactDataTable/ReactDataTableProps.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { ColumnFiltersState, Table } from "@tanstack/react-table";
22
import { CSSProperties } from "react";
3+
import { DragAndDropOptions } from "./DragAndDropOptions";
34

45
/**
56
* The props for the ReactDataTable component
@@ -72,7 +73,12 @@ export interface ReactDataTableProps<TData> {
7273
withoutHeaders?: boolean;
7374

7475
/**
75-
* To draw the table without headers filters
76+
* to draw the table without headers filters
7677
*/
7778
withoutHeaderFilters?: boolean;
79+
80+
/**
81+
* to define drag-and-drop options
82+
*/
83+
dragAndDropOptions?: DragAndDropOptions;
7884
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { useSortable } from "@dnd-kit/sortable";
2+
import { Row, flexRender } from "@tanstack/react-table";
3+
import { CSSProperties } from "react";
4+
import { CSS } from "@dnd-kit/utilities";
5+
6+
interface TableRowProps<TData> {
7+
row: Row<TData>;
8+
rowStyle?: CSSProperties;
9+
setNodeRef?: (node: HTMLElement | null) => void;
10+
}
11+
12+
const InternalTableRow = <TData,>(props: TableRowProps<TData>) => {
13+
const { row, rowStyle, setNodeRef } = props;
14+
return (
15+
<tr key={row.id} ref={setNodeRef} style={rowStyle}>
16+
{row.getVisibleCells().map((cell) => (
17+
<td key={cell.id} style={cell.column.columnDef.meta?.cellStyle} className={cell.column.columnDef.meta?.cellClassName}>
18+
{flexRender(cell.column.columnDef.cell, cell.getContext())}
19+
</td>
20+
))}
21+
</tr>
22+
);
23+
};
24+
25+
const DraggableRow = <TData,>(props: TableRowProps<TData>) => {
26+
const { row, rowStyle } = props;
27+
const { transform, transition, setNodeRef, isDragging } = useSortable({ id: row.id });
28+
const draggableStyle: CSSProperties = {
29+
transform: CSS.Transform.toString(transform),
30+
transition: transition,
31+
opacity: isDragging ? 0.8 : 1,
32+
zIndex: isDragging ? 1 : 0,
33+
position: "relative",
34+
};
35+
return <InternalTableRow row={row} setNodeRef={setNodeRef} rowStyle={rowStyle ? { ...rowStyle, ...draggableStyle } : draggableStyle} />;
36+
};
37+
38+
export { InternalTableRow, DraggableRow };

src/lib/utils/createReactDataTableColumnHelper.ts renamed to src/lib/utils/createReactDataTableColumnHelper.tsx

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,60 @@
1-
import { ColumnDef, ColumnHelper, DeepKeys, RowData, createColumnHelper } from "@tanstack/react-table";
1+
import { useSortable } from "@dnd-kit/sortable";
2+
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
3+
import { ColumnDef, ColumnHelper, DeepKeys, DisplayColumnDef, RowData, createColumnHelper } from "@tanstack/react-table";
4+
import { ReactNode } from "react";
25
import { DropdownColumnFilterOption } from "src/react-table";
6+
import { faGripLines } from "@fortawesome/free-solid-svg-icons";
37

48
interface ReactDataTableColumnHelper<TData extends RowData> extends ColumnHelper<TData> {
59
createEnumColumn: <TEnum extends string | number>(
610
columnKey: DeepKeys<TData>,
711
enumTranslations: Record<TEnum, string> | DropdownColumnFilterOption[],
812
columndDef?: Partial<ColumnDef<TData, TEnum>>,
913
) => ColumnDef<TData, TEnum>;
14+
createDraggableColumn: (
15+
columnKey: DeepKeys<TData>,
16+
columndDef: Omit<DisplayColumnDef<TData>, "id" | "cell">,
17+
isEnabled?: boolean,
18+
draggableElement?: ReactNode,
19+
) => ColumnDef<TData>;
1020
}
1121

1222
const createReactDataTableColumnHelper = <TData extends RowData>(): ReactDataTableColumnHelper<TData> => {
1323
const columnHelper = createColumnHelper<TData>();
1424

1525
columnHelper.accessor;
1626

27+
const createDraggableColumn = (
28+
columnKey: DeepKeys<TData>,
29+
columndDef: Omit<DisplayColumnDef<TData>, "id" | "cell">,
30+
isEnabled?: boolean,
31+
draggableElement?: ReactNode,
32+
) => {
33+
const RowDragHandleCell = ({ rowId }: { rowId: string }) => {
34+
const { attributes, listeners, isDragging } = useSortable({ id: rowId });
35+
36+
return (
37+
<span
38+
{...attributes}
39+
{...listeners}
40+
className={isEnabled === false ? "opacity-25" : "opacity-100"}
41+
style={{ cursor: isEnabled === false ? "auto" : isDragging ? "grabbing" : "grab" }}
42+
>
43+
{draggableElement ?? <FontAwesomeIcon icon={faGripLines} />}
44+
</span>
45+
);
46+
};
47+
48+
return columnHelper.display({
49+
...columndDef,
50+
id: columnKey as string,
51+
cell: ({ row }) => <RowDragHandleCell rowId={row.id} />,
52+
});
53+
};
54+
1755
const res: ReactDataTableColumnHelper<TData> = {
1856
...columnHelper,
57+
createDraggableColumn,
1958
createEnumColumn: (columnKey, enumTranslations, columndDef) =>
2059
// eslint-disable-next-line @typescript-eslint/no-explicit-any
2160
columnHelper.accessor(columnKey as any, {

test/DataTable/DataTable.test.tsx

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,4 +184,37 @@ describe("DataTable", () => {
184184

185185
expect(container).toMatchSnapshot();
186186
});
187+
188+
test("renders with draggable-column correctly", () => {
189+
const {
190+
result: {
191+
current: { table },
192+
},
193+
} = renderHook(() =>
194+
useReactDataTable({
195+
data: dataDynamic,
196+
isLoading: false,
197+
columns,
198+
reactTableOptions: {
199+
enableSortingRemoval: false,
200+
getRowId: (row) => row.id,
201+
},
202+
}),
203+
);
204+
const { container } = render(
205+
<ReactDataTable
206+
table={table}
207+
showPaging
208+
totalRecords={dataDynamic?.length}
209+
isFetching={false}
210+
isLoading={false}
211+
dragAndDropOptions={{
212+
enableDragAndDrop: true,
213+
onDragEnd: () => true,
214+
}}
215+
/>,
216+
);
217+
218+
expect(container).toMatchSnapshot();
219+
});
187220
});

0 commit comments

Comments
 (0)