Row & Cross-Row Drag
Enable drag-to-reorder entire tracks and drag action blocks between different rows.
The @keplar-404/react-timeline-editor introduces two powerful drag-and-drop features:
- Row Reordering (
enableRowDrag) — drag entire tracks vertically - Cross-Row Block Dragging (
enableCrossRowDrag) — move action blocks between different rows
Live Preview — Row Reordering
Row Reordering
Enable row drag sorting by setting enableRowDrag={true}. The engine calculates the new sorted order and provides it in onRowDragEnd:
import {
Timeline,
TimelineRow,
TimelineEffect,
} from "@keplar-404/react-timeline-editor";
import React, { useState } from "react";
const mockData: TimelineRow[] = [
{ id: "Track A", actions: [] },
{ id: "Track B", actions: [] },
{ id: "Track C", actions: [] },
];
export const EditorRowDrag = () => {
const [data, setData] = useState(mockData);
return (
<div style={{ width: "100%", height: "400px" }}>
<Timeline
editorData={data}
effects={{}}
onChange={setData}
enableRowDrag={true}
onRowDragStart={({ row }) => {
console.log("Started dragging row:", row.id);
}}
onRowDragEnd={({ row, editorData }) => {
// The engine provides the updated sorted array — just set it!
setData(editorData as TimelineRow[]);
}}
style={{ width: "100%", height: "100%" }}
/>
</div>
);
};The onRowDragEnd callback receives editorData — the new sorted array
after the drop. You don't need to manually reorder the array; just call
setData(editorData).
Cross-Row Block Dragging
Allow users to grab an action from one row and drop it into another:
export const EditorCrossRowDrag = () => {
const [data, setData] = useState(mockData);
return (
<div style={{ width: "100%", height: "400px" }}>
<Timeline
editorData={data}
effects={{}}
onChange={setData}
enableCrossRowDrag={true}
style={{ width: "100%", height: "100%" }}
/>
</div>
);
};During the drag, a ghost preview follows the cursor. By default it's a blue glowing box.
Custom Ghost Preview
Use getGhostPreview to render a fully custom ghost that matches your block's actual appearance:
<div style={{ width: "100%", height: "400px" }}>
<Timeline
editorData={data}
effects={{}}
onChange={setData}
enableCrossRowDrag={true}
getGhostPreview={({ action, row }) => (
<div className="custom-ghost-preview">
<span>Moving: {action.id}</span>
</div>
)}
style={{ width: "100%", height: "100%" }}
/>
</div>.custom-ghost-preview {
background: #1a3a5c;
border: 2px solid #3b82f6;
height: 100%;
border-radius: 4px;
padding: 0 8px;
display: flex;
align-items: center;
color: #3b82f6;
font-size: 12px;
}For the best user experience, render the same content in both
getActionRender and getGhostPreview. This creates a smooth visual
continuity between the real block and the ghost following the cursor.