0
# Component Utilities
1
2
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.
3
4
## Capabilities
5
6
### useDraggable
7
8
Hook that provides a simplified interface for making elements draggable with pre-configured behavior and state management.
9
10
```typescript { .api }
11
/**
12
* Simplified hook for making elements draggable
13
* Provides pre-configured draggable state and refs
14
* @param props - Configuration options for draggable behavior
15
* @returns Draggable state with refs and status
16
*/
17
export function useDraggable(props: UseDndNodeOptions): DraggableState;
18
19
export type DraggableState = {
20
/** Whether the element is currently being dragged */
21
isDragging: boolean;
22
/** Reference to the draggable preview element */
23
previewRef: React.RefObject<HTMLDivElement | null>;
24
/** Reference function for the drag handle element */
25
handleRef: (
26
elementOrNode:
27
| Element
28
| React.ReactElement<any>
29
| React.RefObject<any>
30
| null
31
) => void;
32
};
33
```
34
35
**Usage Examples:**
36
37
```typescript
38
import { useDraggable } from "@udecode/plate-dnd";
39
import { useElement } from "@udecode/plate/react";
40
41
function DraggableBlockWithHandle({ children }) {
42
const element = useElement();
43
44
const { isDragging, previewRef, handleRef } = useDraggable({
45
element,
46
orientation: 'vertical'
47
});
48
49
return (
50
<div
51
ref={previewRef}
52
style={{ opacity: isDragging ? 0.5 : 1 }}
53
>
54
<div
55
ref={handleRef}
56
style={{
57
cursor: 'grab',
58
padding: '4px',
59
background: '#f0f0f0',
60
marginBottom: '4px'
61
}}
62
>
63
⋮⋮ Drag Handle
64
</div>
65
{children}
66
</div>
67
);
68
}
69
70
// With custom drop handler
71
function CustomDraggableBlock({ children }) {
72
const element = useElement();
73
74
const { isDragging, previewRef, handleRef } = useDraggable({
75
element,
76
onDropHandler: (editor, { id }) => {
77
console.log('Block dropped:', id);
78
return false; // Allow default behavior
79
}
80
});
81
82
return (
83
<div ref={previewRef}>
84
<button ref={handleRef} disabled={isDragging}>
85
{isDragging ? 'Dragging...' : 'Drag me'}
86
</button>
87
{children}
88
</div>
89
);
90
}
91
```
92
93
### useDropLine
94
95
Hook for managing visual drop line indicators that show where dragged items will be dropped.
96
97
```typescript { .api }
98
/**
99
* Hook for managing drop line visual indicators
100
* Shows visual feedback for drop zones during drag operations
101
* @param options - Configuration for drop line behavior
102
* @returns Object containing the current drop line direction
103
*/
104
export function useDropLine(options?: {
105
/** The id of the element to show the dropline for */
106
id?: string;
107
/** Orientation of the drop line */
108
orientation?: 'horizontal' | 'vertical';
109
}): {
110
dropLine?: DropLineDirection;
111
};
112
113
export type DropLineDirection = '' | 'bottom' | 'left' | 'right' | 'top';
114
```
115
116
**Usage Examples:**
117
118
```typescript
119
import { useDropLine } from "@udecode/plate-dnd";
120
import { useElement } from "@udecode/plate/react";
121
122
function BlockWithDropLine({ children }) {
123
const element = useElement();
124
const { dropLine } = useDropLine({
125
id: element.id as string,
126
orientation: 'vertical'
127
});
128
129
return (
130
<div style={{ position: 'relative' }}>
131
{/* Drop line indicator */}
132
{dropLine && (
133
<div
134
style={{
135
position: 'absolute',
136
height: '2px',
137
backgroundColor: '#007acc',
138
left: 0,
139
right: 0,
140
[dropLine]: dropLine === 'top' ? '-1px' :
141
dropLine === 'bottom' ? 'calc(100% - 1px)' : '50%'
142
}}
143
/>
144
)}
145
{children}
146
</div>
147
);
148
}
149
150
// Horizontal drop lines
151
function HorizontalBlock({ children }) {
152
const element = useElement();
153
const { dropLine } = useDropLine({
154
orientation: 'horizontal'
155
});
156
157
return (
158
<div style={{
159
position: 'relative',
160
display: 'inline-block',
161
margin: '0 4px'
162
}}>
163
{dropLine && (
164
<div
165
style={{
166
position: 'absolute',
167
width: '2px',
168
backgroundColor: '#007acc',
169
top: 0,
170
bottom: 0,
171
[dropLine]: dropLine === 'left' ? '-1px' :
172
dropLine === 'right' ? 'calc(100% - 1px)' : '50%'
173
}}
174
/>
175
)}
176
{children}
177
</div>
178
);
179
}
180
181
// Custom drop line styling
182
function CustomDropLine({ children }) {
183
const { dropLine } = useDropLine();
184
185
const getDropLineStyle = () => {
186
if (!dropLine) return {};
187
188
const baseStyle = {
189
position: 'absolute' as const,
190
backgroundColor: '#ff6b6b',
191
zIndex: 1000,
192
borderRadius: '2px'
193
};
194
195
switch (dropLine) {
196
case 'top':
197
return { ...baseStyle, top: '-2px', left: 0, right: 0, height: '4px' };
198
case 'bottom':
199
return { ...baseStyle, bottom: '-2px', left: 0, right: 0, height: '4px' };
200
case 'left':
201
return { ...baseStyle, left: '-2px', top: 0, bottom: 0, width: '4px' };
202
case 'right':
203
return { ...baseStyle, right: '-2px', top: 0, bottom: 0, width: '4px' };
204
default:
205
return {};
206
}
207
};
208
209
return (
210
<div style={{ position: 'relative' }}>
211
{dropLine && <div style={getDropLineStyle()} />}
212
{children}
213
</div>
214
);
215
}
216
```
217
218
### Integration Example
219
220
Combining both utilities for a complete drag-and-drop block component:
221
222
```typescript
223
import { useDraggable, useDropLine } from "@udecode/plate-dnd";
224
import { useElement } from "@udecode/plate/react";
225
226
function CompleteDragDropBlock({ children }) {
227
const element = useElement();
228
229
// Draggable functionality
230
const { isDragging, previewRef, handleRef } = useDraggable({
231
element,
232
orientation: 'vertical'
233
});
234
235
// Drop line indicators
236
const { dropLine } = useDropLine({
237
id: element.id as string,
238
orientation: 'vertical'
239
});
240
241
return (
242
<div
243
ref={previewRef}
244
style={{
245
position: 'relative',
246
opacity: isDragging ? 0.5 : 1,
247
padding: '8px',
248
border: '1px solid #ddd',
249
borderRadius: '4px',
250
margin: '4px 0'
251
}}
252
>
253
{/* Drop line indicator */}
254
{dropLine === 'top' && (
255
<div style={{
256
position: 'absolute',
257
top: '-2px',
258
left: 0,
259
right: 0,
260
height: '4px',
261
backgroundColor: '#007acc',
262
borderRadius: '2px'
263
}} />
264
)}
265
266
{dropLine === 'bottom' && (
267
<div style={{
268
position: 'absolute',
269
bottom: '-2px',
270
left: 0,
271
right: 0,
272
height: '4px',
273
backgroundColor: '#007acc',
274
borderRadius: '2px'
275
}} />
276
)}
277
278
{/* Drag handle */}
279
<div
280
ref={handleRef}
281
style={{
282
position: 'absolute',
283
left: '-30px',
284
top: '50%',
285
transform: 'translateY(-50%)',
286
cursor: isDragging ? 'grabbing' : 'grab',
287
padding: '4px',
288
backgroundColor: '#f9f9f9',
289
border: '1px solid #ddd',
290
borderRadius: '4px',
291
fontSize: '12px'
292
}}
293
>
294
⋮⋮
295
</div>
296
297
{children}
298
</div>
299
);
300
}
301
```
302
303
## Types
304
305
```typescript { .api }
306
export interface UseDndNodeOptions {
307
element: TElement;
308
nodeRef?: any;
309
canDropNode?: CanDropCallback;
310
type?: string;
311
drag?: Partial<UseDragNodeOptions>;
312
drop?: Partial<UseDropNodeOptions>;
313
orientation?: 'horizontal' | 'vertical';
314
preview?: {
315
disable?: boolean;
316
ref?: any;
317
};
318
onDropHandler?: (
319
editor: PlateEditor,
320
props: {
321
id: string;
322
dragItem: DragItemNode;
323
monitor: DropTargetMonitor<DragItemNode, unknown>;
324
nodeRef: any;
325
}
326
) => boolean | void;
327
}
328
```