Skip to content

Commit 18df3bd

Browse files
[Pattern] Nested drag and drop (elastic#9270)
Co-authored-by: Arturo Castillo Delgado <arturo@arturu.com>
1 parent f275192 commit 18df3bd

File tree

7 files changed

+929
-20
lines changed

7 files changed

+929
-20
lines changed

packages/website/docs/components/display/drag-and-drop.mdx

Lines changed: 46 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,25 @@ keywords: [EuiDragDropContext, EuiDroppable, EuiDraggable]
44

55
# Drag and drop
66

7-
An extension of [@hello-pangea/dnd](https://github.com/hello-pangea/dnd) (which is an actively maintained fork of [react-beautiful-dnd](https://github.com/atlassian/react-beautiful-dnd)) with a compatible API and built-in style opinions. Functionality results from 3 components working together:
7+
```mdx-code-block
8+
import { EuiLink } from '@elastic/eui';
9+
```
10+
11+
An extension of <EuiLink href="https://github.com/hello-pangea/dnd" target="_blank">@hello-pangea/dnd</EuiLink> (which is an actively maintained fork of <EuiLink href="https://github.com/atlassian/react-beautiful-dnd" target="_blank">react-beautiful-dnd</EuiLink>) with a compatible API and built-in style opinions. Functionality results from 3 components working together:
12+
13+
- `<EuiDragDropContext />`: Section of your application containing the draggable elements and the drop targets.
14+
- `<EuiDroppable />`: Area into which items can be dropped. Contains one or more `<EuiDraggable />`.
15+
- `<EuiDraggable />`: Items that can be dragged. Must be part of an `<EuiDroppable />`.
816

9-
* `<EuiDragDropContext />`: Section of your application containing the draggable elements and the drop targets.
10-
* `<EuiDroppable />`: Area into which items can be dropped. Contains one or more `<EuiDraggable />`.
11-
* `<EuiDraggable />`: Items that can be dragged. Must be part of an `<EuiDroppable />`
17+
:::accessibility Consider your users and use case
1218

13-
:::warning Consider your users and use case
19+
Drag and drop is often less suitable than standard form inputs. It relies on spatial orientation, which can be difficult for screen reader users. Keyboard navigation typically does not afford the same nuanced manipulation as a mouse. The `@hello-pangea/dnd` package ensures a good amount of accessibility but carefully consider your users' context when choosing this pattern.
20+
21+
:::
1422

15-
Drag and drop interfaces are not well-adapted to many cases, and may be less suitable than other form types for data operations. For instance, drag and drop interaction relies heavily on spatial orientation that may not be entirely valid to all users (e.g., screen readers as the sole source of information). Similarly, users navigating by keyboard may not be afforded nuanced, dual-axis drag item manipulation.
23+
:::warning Limitations
1624

17-
EUI (largely due to the great work already in @hello-pangea/dnd) has and will continue to ensure accessibility where possible. With that in mind, keep your users' working context in mind.
25+
One of the limitations of <EuiLink href="https://github.com/hello-pangea/dnd" target="_blank">@hello-pangea/dnd</EuiLink> is **nested drag and drop** (dragging elements between nesting levels). For this use case, we recommend using <EuiLink href="https://atlassian.design/components/pragmatic-drag-and-drop/about" target="_blank">Pragmatic drag and drop</EuiLink>. Check out our [Nested drag and drop pattern](../../patterns/nested-drag-and-drop/index.mdx) for a simplified example of how to implement it.
1826

1927
:::
2028

@@ -28,16 +36,16 @@ All **EuiDragDropContext** elements are discrete and isolated; **EuiDroppables**
2836

2937
**EuiDragDropContext** handles all events but makes no assumptions about the result of a drop event. As such, the following event handlers are available:
3038

31-
* `onBeforeDragStart`
32-
* `onDragStart`
33-
* `onDragUpdate`
34-
* `onDragEnd` (required)
39+
- `onBeforeDragStart`
40+
- `onDragStart`
41+
- `onDragUpdate`
42+
- `onDragEnd` (required)
3543

3644
EUI also provides methods for helping to deal to common action types:
3745

38-
* `reorder`: change an item's location in a droppable area
39-
* `copy`: create a duplicate of an item in a different droppable area
40-
* `move`: move an item to a different droppable area
46+
- `reorder`: change an item's location in a droppable area
47+
- `copy`: create a duplicate of an item in a different droppable area
48+
- `move`: move an item to a different droppable area
4149

4250
```tsx interactive
4351
import React, { useState } from 'react';
@@ -77,7 +85,6 @@ export default () => {
7785
</EuiDragDropContext>
7886
);
7987
};
80-
8188
```
8289

8390
## Simple item reorder
@@ -109,13 +116,15 @@ const makeList = (number, start = 1) =>
109116

110117
export default () => {
111118
const [list, setList] = useState(makeList(3));
119+
112120
const onDragEnd = ({ source, destination }) => {
113121
if (source && destination) {
114122
const items = euiDragDropReorder(list, source.index, destination.index);
115123

116124
setList(items);
117125
}
118126
};
127+
119128
return (
120129
<EuiDragDropContext onDragEnd={onDragEnd}>
121130
<EuiDroppable droppableId="DROPPABLE_AREA" spacing="m" withPanel>
@@ -133,7 +142,6 @@ export default () => {
133142
</EuiDragDropContext>
134143
);
135144
};
136-
137145
```
138146

139147
## Custom drag handle
@@ -143,7 +151,9 @@ By default the entire element surface can initiate a drag. To specify an element
143151
The `provided` parameter on the **EuiDraggable** `children` render prop has all data required for functionality. Along with the `customDragHandle` flag,`provided.dragHandleProps` needs to be added to the intended handle element.
144152

145153
:::accessibility Accessibility requirement
154+
146155
**Icon-only** custom drag handles require an accessible label. Add an `aria-label="Drag handle"` attribute to your React component or HTML element that receives`provided.dragHandleProps`.
156+
147157
:::
148158

149159
```tsx interactive
@@ -172,13 +182,15 @@ const makeList = (number, start = 1) =>
172182

173183
export default () => {
174184
const [list, setList] = useState(makeList(3));
185+
175186
const onDragEnd = ({ source, destination }) => {
176187
if (source && destination) {
177188
const items = euiDragDropReorder(list, source.index, destination.index);
178189

179190
setList(items);
180191
}
181192
};
193+
182194
return (
183195
<EuiDragDropContext onDragEnd={onDragEnd}>
184196
<EuiDroppable
@@ -218,7 +230,6 @@ export default () => {
218230
</EuiDragDropContext>
219231
);
220232
};
221-
222233
```
223234

224235
## Interactive elements
@@ -252,13 +263,15 @@ const makeList = (number, start = 1) =>
252263

253264
export default () => {
254265
const [list, setList] = useState(makeList(3));
266+
255267
const onDragEnd = ({ source, destination }) => {
256268
if (source && destination) {
257269
const items = euiDragDropReorder(list, source.index, destination.index);
258270

259271
setList(items);
260272
}
261273
};
274+
262275
return (
263276
<EuiDragDropContext onDragEnd={onDragEnd}>
264277
<EuiDroppable
@@ -302,7 +315,6 @@ export default () => {
302315
</EuiDragDropContext>
303316
);
304317
};
305-
306318
```
307319

308320
## Move between lists
@@ -366,6 +378,7 @@ export default () => {
366378
}
367379
}
368380
};
381+
369382
return (
370383
<EuiDragDropContext onDragEnd={onDragEnd}>
371384
<EuiFlexGroup>
@@ -437,7 +450,6 @@ export default () => {
437450
</EuiDragDropContext>
438451
);
439452
};
440-
441453
```
442454

443455
## Distinguish droppable areas by type
@@ -474,6 +486,7 @@ export default () => {
474486
const [list1, setList1] = useState(makeList(3));
475487
const [list2, setList2] = useState(makeList(3, 4));
476488
const [list3, setList3] = useState(makeList(3, 7));
489+
477490
const onDragEnd = ({ source, destination }) => {
478491
const lists = {
479492
DROPPABLE_AREA_TYPE_1: list1,
@@ -509,6 +522,7 @@ export default () => {
509522
}
510523
}
511524
};
525+
512526
return (
513527
<EuiDragDropContext onDragEnd={onDragEnd}>
514528
<EuiFlexGroup>
@@ -576,7 +590,6 @@ export default () => {
576590
</EuiDragDropContext>
577591
);
578592
};
579-
580593
```
581594

582595
## Copyable items
@@ -617,22 +630,27 @@ export default () => {
617630
const [isItemRemovable, setIsItemRemovable] = useState(false);
618631
const [list1, setList1] = useState(makeList(3));
619632
const [list2, setList2] = useState([]);
633+
620634
const lists = { DROPPABLE_AREA_COPY_1: list1, DROPPABLE_AREA_COPY_2: list2 };
635+
621636
const actions = {
622637
DROPPABLE_AREA_COPY_1: setList1,
623638
DROPPABLE_AREA_COPY_2: setList2,
624639
};
640+
625641
const remove = (droppableId, index) => {
626642
const list = Array.from(lists[droppableId]);
627643
list.splice(index, 1);
628644

629645
actions[droppableId](list);
630646
};
647+
631648
const onDragUpdate = ({ source, destination }) => {
632649
const shouldRemove =
633650
!destination && source.droppableId === 'DROPPABLE_AREA_COPY_2';
634651
setIsItemRemovable(shouldRemove);
635652
};
653+
636654
const onDragEnd = ({ source, destination }) => {
637655
if (source && destination) {
638656
if (source.droppableId === destination.droppableId) {
@@ -664,6 +682,7 @@ export default () => {
664682
remove(source.droppableId, source.index);
665683
}
666684
};
685+
667686
return (
668687
<EuiDragDropContext onDragEnd={onDragEnd} onDragUpdate={onDragUpdate}>
669688
<EuiFlexGroup>
@@ -787,6 +806,7 @@ export default () => {
787806
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
788807

789808
const [list, setList] = useState(makeList(3));
809+
790810
const onDragEnd: OnDragEndResponder = ({ source, destination }) => {
791811
if (source && destination) {
792812
const items = euiDragDropReorder(list, source.index, destination.index);
@@ -945,16 +965,19 @@ export default () => {
945965
const [list, setList] = useState([1, 2]);
946966
const [list1, setList1] = useState(makeList(3));
947967
const [list2, setList2] = useState(makeList(3, 4));
968+
948969
const lists = {
949970
COMPLEX_DROPPABLE_PARENT: list,
950971
COMPLEX_DROPPABLE_AREA_1: list1,
951972
COMPLEX_DROPPABLE_AREA_2: list2,
952973
};
974+
953975
const actions = {
954976
COMPLEX_DROPPABLE_PARENT: setList,
955977
COMPLEX_DROPPABLE_AREA_1: setList1,
956978
COMPLEX_DROPPABLE_AREA_2: setList2,
957979
};
980+
958981
const onDragEnd = ({ source, destination }) => {
959982
if (source && destination) {
960983
if (source.droppableId === destination.droppableId) {
@@ -980,6 +1003,7 @@ export default () => {
9801003
}
9811004
}
9821005
};
1006+
9831007
return (
9841008
<EuiDragDropContext onDragEnd={onDragEnd}>
9851009
<EuiDroppable
@@ -1039,7 +1063,9 @@ export default () => {
10391063

10401064
## Props
10411065

1066+
```mdx-code-block
10421067
import docgen from '@elastic/eui-docgen/dist/components/drag_and_drop';
1068+
```
10431069

10441070
<PropTable definition={docgen.EuiDragDropContext} />
10451071
<PropTable definition={docgen.EuiDraggable} />

0 commit comments

Comments
 (0)