React drag and drop plugin for Plate rich-text editor enabling block rearrangement and file drops
—
Pre-built components and hooks for common drag-and-drop UI patterns. These utilities provide higher-level abstractions for implementing draggable elements and visual drop indicators in your Plate editor components.
Hook that provides a simplified interface for making elements draggable with pre-configured behavior and state management.
/**
* Simplified hook for making elements draggable
* Provides pre-configured draggable state and refs
* @param props - Configuration options for draggable behavior
* @returns Draggable state with refs and status
*/
export function useDraggable(props: UseDndNodeOptions): DraggableState;
export type DraggableState = {
/** Whether the element is currently being dragged */
isDragging: boolean;
/** Reference to the draggable preview element */
previewRef: React.RefObject<HTMLDivElement | null>;
/** Reference function for the drag handle element */
handleRef: (
elementOrNode:
| Element
| React.ReactElement<any>
| React.RefObject<any>
| null
) => void;
};Usage Examples:
import { useDraggable } from "@udecode/plate-dnd";
import { useElement } from "@udecode/plate/react";
function DraggableBlockWithHandle({ children }) {
const element = useElement();
const { isDragging, previewRef, handleRef } = useDraggable({
element,
orientation: 'vertical'
});
return (
<div
ref={previewRef}
style={{ opacity: isDragging ? 0.5 : 1 }}
>
<div
ref={handleRef}
style={{
cursor: 'grab',
padding: '4px',
background: '#f0f0f0',
marginBottom: '4px'
}}
>
⋮⋮ Drag Handle
</div>
{children}
</div>
);
}
// With custom drop handler
function CustomDraggableBlock({ children }) {
const element = useElement();
const { isDragging, previewRef, handleRef } = useDraggable({
element,
onDropHandler: (editor, { id }) => {
console.log('Block dropped:', id);
return false; // Allow default behavior
}
});
return (
<div ref={previewRef}>
<button ref={handleRef} disabled={isDragging}>
{isDragging ? 'Dragging...' : 'Drag me'}
</button>
{children}
</div>
);
}Hook for managing visual drop line indicators that show where dragged items will be dropped.
/**
* Hook for managing drop line visual indicators
* Shows visual feedback for drop zones during drag operations
* @param options - Configuration for drop line behavior
* @returns Object containing the current drop line direction
*/
export function useDropLine(options?: {
/** The id of the element to show the dropline for */
id?: string;
/** Orientation of the drop line */
orientation?: 'horizontal' | 'vertical';
}): {
dropLine?: DropLineDirection;
};
export type DropLineDirection = '' | 'bottom' | 'left' | 'right' | 'top';Usage Examples:
import { useDropLine } from "@udecode/plate-dnd";
import { useElement } from "@udecode/plate/react";
function BlockWithDropLine({ children }) {
const element = useElement();
const { dropLine } = useDropLine({
id: element.id as string,
orientation: 'vertical'
});
return (
<div style={{ position: 'relative' }}>
{/* Drop line indicator */}
{dropLine && (
<div
style={{
position: 'absolute',
height: '2px',
backgroundColor: '#007acc',
left: 0,
right: 0,
[dropLine]: dropLine === 'top' ? '-1px' :
dropLine === 'bottom' ? 'calc(100% - 1px)' : '50%'
}}
/>
)}
{children}
</div>
);
}
// Horizontal drop lines
function HorizontalBlock({ children }) {
const element = useElement();
const { dropLine } = useDropLine({
orientation: 'horizontal'
});
return (
<div style={{
position: 'relative',
display: 'inline-block',
margin: '0 4px'
}}>
{dropLine && (
<div
style={{
position: 'absolute',
width: '2px',
backgroundColor: '#007acc',
top: 0,
bottom: 0,
[dropLine]: dropLine === 'left' ? '-1px' :
dropLine === 'right' ? 'calc(100% - 1px)' : '50%'
}}
/>
)}
{children}
</div>
);
}
// Custom drop line styling
function CustomDropLine({ children }) {
const { dropLine } = useDropLine();
const getDropLineStyle = () => {
if (!dropLine) return {};
const baseStyle = {
position: 'absolute' as const,
backgroundColor: '#ff6b6b',
zIndex: 1000,
borderRadius: '2px'
};
switch (dropLine) {
case 'top':
return { ...baseStyle, top: '-2px', left: 0, right: 0, height: '4px' };
case 'bottom':
return { ...baseStyle, bottom: '-2px', left: 0, right: 0, height: '4px' };
case 'left':
return { ...baseStyle, left: '-2px', top: 0, bottom: 0, width: '4px' };
case 'right':
return { ...baseStyle, right: '-2px', top: 0, bottom: 0, width: '4px' };
default:
return {};
}
};
return (
<div style={{ position: 'relative' }}>
{dropLine && <div style={getDropLineStyle()} />}
{children}
</div>
);
}Combining both utilities for a complete drag-and-drop block component:
import { useDraggable, useDropLine } from "@udecode/plate-dnd";
import { useElement } from "@udecode/plate/react";
function CompleteDragDropBlock({ children }) {
const element = useElement();
// Draggable functionality
const { isDragging, previewRef, handleRef } = useDraggable({
element,
orientation: 'vertical'
});
// Drop line indicators
const { dropLine } = useDropLine({
id: element.id as string,
orientation: 'vertical'
});
return (
<div
ref={previewRef}
style={{
position: 'relative',
opacity: isDragging ? 0.5 : 1,
padding: '8px',
border: '1px solid #ddd',
borderRadius: '4px',
margin: '4px 0'
}}
>
{/* Drop line indicator */}
{dropLine === 'top' && (
<div style={{
position: 'absolute',
top: '-2px',
left: 0,
right: 0,
height: '4px',
backgroundColor: '#007acc',
borderRadius: '2px'
}} />
)}
{dropLine === 'bottom' && (
<div style={{
position: 'absolute',
bottom: '-2px',
left: 0,
right: 0,
height: '4px',
backgroundColor: '#007acc',
borderRadius: '2px'
}} />
)}
{/* Drag handle */}
<div
ref={handleRef}
style={{
position: 'absolute',
left: '-30px',
top: '50%',
transform: 'translateY(-50%)',
cursor: isDragging ? 'grabbing' : 'grab',
padding: '4px',
backgroundColor: '#f9f9f9',
border: '1px solid #ddd',
borderRadius: '4px',
fontSize: '12px'
}}
>
⋮⋮
</div>
{children}
</div>
);
}export interface UseDndNodeOptions {
element: TElement;
nodeRef?: any;
canDropNode?: CanDropCallback;
type?: string;
drag?: Partial<UseDragNodeOptions>;
drop?: Partial<UseDropNodeOptions>;
orientation?: 'horizontal' | 'vertical';
preview?: {
disable?: boolean;
ref?: any;
};
onDropHandler?: (
editor: PlateEditor,
props: {
id: string;
dragItem: DragItemNode;
monitor: DropTargetMonitor<DragItemNode, unknown>;
nodeRef: any;
}
) => boolean | void;
}Install with Tessl CLI
npx tessl i tessl/npm-udecode--plate-dnd