0
# Sortable Handle
1
2
Higher-order component that creates designated drag areas within sortable elements. Enables fine-grained control over drag initiation by restricting sorting to specific UI elements.
3
4
## Capabilities
5
6
### SortableHandle HOC
7
8
Creates a drag handle component that can initiate sorting when used with `useDragHandle={true}` on the SortableContainer.
9
10
```typescript { .api }
11
/**
12
* Higher-order component that creates a drag handle for sortable elements
13
* @param wrappedComponent - The React component to enhance as a drag handle
14
* @param config - Optional configuration object
15
* @returns Enhanced React component that serves as a drag handle
16
*/
17
function SortableHandle<P>(
18
wrappedComponent: WrappedComponent<P>,
19
config?: Config
20
): React.ComponentClass<P>;
21
22
interface Config {
23
withRef: boolean;
24
}
25
```
26
27
**Usage Examples:**
28
29
```typescript
30
import React from 'react';
31
import { SortableHandle, SortableElement, SortableContainer } from 'react-sortable-hoc';
32
33
// Simple drag handle
34
const DragHandle = SortableHandle(() => (
35
<span className="drag-handle">≡</span>
36
));
37
38
// Icon-based drag handle
39
const IconDragHandle = SortableHandle(() => (
40
<div className="drag-handle-icon">
41
<svg width="12" height="12" viewBox="0 0 12 12">
42
<circle cx="2" cy="2" r="1" />
43
<circle cx="6" cy="2" r="1" />
44
<circle cx="10" cy="2" r="1" />
45
<circle cx="2" cy="6" r="1" />
46
<circle cx="6" cy="6" r="1" />
47
<circle cx="10" cy="6" r="1" />
48
<circle cx="2" cy="10" r="1" />
49
<circle cx="6" cy="10" r="1" />
50
<circle cx="10" cy="10" r="1" />
51
</svg>
52
</div>
53
));
54
55
// Usage in sortable element
56
const SortableItem = SortableElement(({ value }) => (
57
<li className="sortable-item">
58
<DragHandle />
59
<span>{value}</span>
60
</li>
61
));
62
```
63
64
### Handle Integration
65
66
#### Container Configuration
67
68
To use drag handles, set `useDragHandle={true}` on the SortableContainer:
69
70
```typescript
71
const SortableList = SortableContainer(({ items }) => (
72
<ul>
73
{items.map((value, index) => (
74
<SortableItem key={`item-${value}`} index={index} value={value} />
75
))}
76
</ul>
77
));
78
79
// Enable drag handles
80
<SortableList
81
items={items}
82
onSortEnd={handleSortEnd}
83
useDragHandle={true} // Required for handles to work
84
/>
85
```
86
87
#### Element Structure
88
89
Place the drag handle component anywhere within your sortable element:
90
91
```typescript
92
const SortableCard = SortableElement(({ title, content, onEdit }) => (
93
<div className="card">
94
<div className="card-header">
95
<DragHandle /> {/* Handle at the top */}
96
<h3>{title}</h3>
97
</div>
98
<div className="card-body">
99
<p>{content}</p>
100
</div>
101
<div className="card-footer">
102
<button onClick={onEdit}>Edit</button>
103
</div>
104
</div>
105
));
106
```
107
108
### Handle Styling
109
110
#### CSS Styling
111
112
Style drag handles to provide clear visual feedback:
113
114
```css
115
.drag-handle {
116
display: inline-block;
117
width: 20px;
118
height: 20px;
119
cursor: grab;
120
color: #999;
121
font-size: 16px;
122
line-height: 20px;
123
text-align: center;
124
user-select: none;
125
}
126
127
.drag-handle:hover {
128
color: #666;
129
}
130
131
.drag-handle:active {
132
cursor: grabbing;
133
}
134
135
.card-header .drag-handle {
136
float: right;
137
margin-left: 10px;
138
}
139
```
140
141
#### Interactive Handle States
142
143
```typescript
144
const InteractiveDragHandle = SortableHandle(({ isActive }) => (
145
<div className={`drag-handle ${isActive ? 'active' : ''}`}>
146
<span>⋮⋮</span>
147
</div>
148
));
149
150
// Usage with state
151
const [activeHandle, setActiveHandle] = useState(null);
152
153
const SortableItem = SortableElement(({ value, index }) => (
154
<li
155
onMouseEnter={() => setActiveHandle(index)}
156
onMouseLeave={() => setActiveHandle(null)}
157
>
158
<InteractiveDragHandle isActive={activeHandle === index} />
159
<span>{value}</span>
160
</li>
161
));
162
```
163
164
### Advanced Handle Patterns
165
166
#### Multiple Handles
167
168
You can have multiple handles within the same sortable element:
169
170
```typescript
171
const TopHandle = SortableHandle(() => <div className="top-handle">↕</div>);
172
const SideHandle = SortableHandle(() => <div className="side-handle">↔</div>);
173
174
const MultiHandleItem = SortableElement(({ value }) => (
175
<div className="multi-handle-item">
176
<TopHandle />
177
<div className="content">
178
<SideHandle />
179
<span>{value}</span>
180
</div>
181
</div>
182
));
183
```
184
185
#### Conditional Handles
186
187
Show handles only when appropriate:
188
189
```typescript
190
const ConditionalHandle = SortableHandle(({ show }) =>
191
show ? <span className="drag-handle">≡</span> : null
192
);
193
194
const SortableItem = SortableElement(({ value, editable, userCanSort }) => (
195
<li className="item">
196
<ConditionalHandle show={editable && userCanSort} />
197
<span>{value}</span>
198
</li>
199
));
200
```
201
202
#### Custom Handle Content
203
204
Create handles with rich content:
205
206
```typescript
207
const CustomDragHandle = SortableHandle(({ label, icon }) => (
208
<div className="custom-drag-handle">
209
{icon && <img src={icon} alt="drag" className="handle-icon" />}
210
<span className="handle-label">{label}</span>
211
<div className="handle-grip">
212
<div className="grip-dot"></div>
213
<div className="grip-dot"></div>
214
<div className="grip-dot"></div>
215
</div>
216
</div>
217
));
218
219
// Usage
220
const SortableTask = SortableElement(({ task }) => (
221
<div className="task-item">
222
<CustomDragHandle
223
label="Drag to reorder"
224
icon="/icons/drag.svg"
225
/>
226
<div className="task-content">
227
<h4>{task.title}</h4>
228
<p>{task.description}</p>
229
</div>
230
</div>
231
));
232
```
233
234
### Handle Events
235
236
#### Event Handling
237
238
Handles can include their own event handlers:
239
240
```typescript
241
const InteractiveHandle = SortableHandle(({ onHandleClick, onHandleHover }) => (
242
<div
243
className="interactive-handle"
244
onClick={onHandleClick}
245
onMouseEnter={onHandleHover}
246
>
247
<span>≡</span>
248
</div>
249
));
250
251
const SortableItem = SortableElement(({ value, onItemAction }) => (
252
<li>
253
<InteractiveHandle
254
onHandleClick={() => onItemAction('handle-clicked')}
255
onHandleHover={() => onItemAction('handle-hovered')}
256
/>
257
<span>{value}</span>
258
</li>
259
));
260
```
261
262
#### Preventing Event Bubbling
263
264
Sometimes you need to prevent handle events from affecting the parent:
265
266
```typescript
267
const SafeHandle = SortableHandle(() => (
268
<div
269
className="safe-handle"
270
onDoubleClick={(e) => {
271
e.stopPropagation(); // Prevent double-click from bubbling
272
console.log('Handle double-clicked');
273
}}
274
>
275
≡
276
</div>
277
));
278
```
279
280
### Handle Accessibility
281
282
#### Keyboard Support
283
284
Drag handles work with keyboard navigation when the sortable element is focusable:
285
286
```typescript
287
const AccessibleSortableItem = SortableElement(({ value, index }) => (
288
<li
289
tabIndex={0} // Make focusable for keyboard navigation
290
role="listitem"
291
aria-label={`Item ${index + 1}: ${value}`}
292
>
293
<DragHandle />
294
<span>{value}</span>
295
</li>
296
));
297
```
298
299
#### Screen Reader Support
300
301
Add appropriate ARIA labels for screen readers:
302
303
```typescript
304
const AccessibleDragHandle = SortableHandle(() => (
305
<div
306
className="drag-handle"
307
role="button"
308
aria-label="Drag to reorder"
309
tabIndex={-1} // Handle shouldn't be directly focusable
310
>
311
≡
312
</div>
313
));
314
```
315
316
### withRef Configuration
317
318
```typescript { .api }
319
interface Config {
320
/** Enable access to wrapped component instance */
321
withRef: boolean;
322
}
323
```
324
325
**withRef Example:**
326
327
```typescript
328
const DragHandle = SortableHandle(HandleComponent, { withRef: true });
329
330
// Access wrapped instance
331
const handleRef = useRef();
332
const wrappedInstance = handleRef.current?.getWrappedInstance();
333
334
<DragHandle ref={handleRef} />
335
```
336
337
### isSortableHandle Utility
338
339
Utility function to check if a DOM element is a sortable handle.
340
341
```typescript { .api }
342
/**
343
* Check if a DOM element is a sortable handle
344
* @param node - The DOM element to check
345
* @returns True if the element is a sortable handle
346
*/
347
function isSortableHandle(node: Element): boolean;
348
```
349
350
**Usage Examples:**
351
352
```typescript
353
import { isSortableHandle } from 'react-sortable-hoc';
354
355
// Check if an element is a sortable handle
356
const handleClick = (event) => {
357
if (isSortableHandle(event.target)) {
358
console.log('Clicked on a sortable handle');
359
} else {
360
console.log('Clicked outside handle area');
361
}
362
};
363
364
// Use in event handlers
365
const SortableItem = SortableElement(({ value }) => (
366
<div onClick={handleClick}>
367
<DragHandle />
368
<span>{value}</span>
369
</div>
370
));
371
372
// Use for conditional logic
373
const isHandleElement = isSortableHandle(document.querySelector('.drag-handle'));
374
```
375
376
### Integration Examples
377
378
#### Complete Example
379
380
```typescript
381
import React, { useState } from 'react';
382
import {
383
SortableContainer,
384
SortableElement,
385
SortableHandle
386
} from 'react-sortable-hoc';
387
import { arrayMove } from 'array-move';
388
389
const DragHandle = SortableHandle(() => (
390
<div className="drag-handle" title="Drag to reorder">
391
⋮⋮
392
</div>
393
));
394
395
const SortableItem = SortableElement(({ value, onEdit, onDelete }) => (
396
<div className="sortable-item">
397
<DragHandle />
398
<div className="item-content">
399
<span>{value}</span>
400
</div>
401
<div className="item-actions">
402
<button onClick={onEdit}>Edit</button>
403
<button onClick={onDelete}>Delete</button>
404
</div>
405
</div>
406
));
407
408
const SortableList = SortableContainer(({ items, onEdit, onDelete }) => (
409
<div className="sortable-list">
410
{items.map((value, index) => (
411
<SortableItem
412
key={`item-${index}`}
413
index={index}
414
value={value}
415
onEdit={() => onEdit(index)}
416
onDelete={() => onDelete(index)}
417
/>
418
))}
419
</div>
420
));
421
422
const App = () => {
423
const [items, setItems] = useState(['Item 1', 'Item 2', 'Item 3']);
424
425
const onSortEnd = ({ oldIndex, newIndex }) => {
426
setItems(arrayMove(items, oldIndex, newIndex));
427
};
428
429
return (
430
<SortableList
431
items={items}
432
onSortEnd={onSortEnd}
433
useDragHandle={true} // Enable drag handles
434
onEdit={(index) => console.log('Edit item', index)}
435
onDelete={(index) => console.log('Delete item', index)}
436
/>
437
);
438
};
439
```