-
Notifications
You must be signed in to change notification settings - Fork 860
[Pattern] Nested drag and drop #9270
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
9128fca
8a3716f
e092e98
9693c1e
5e2f9ce
4e8a719
ba53905
043b5c3
bf22470
2d2558d
0f70cb7
ef6fcda
c2d97e1
a4b9381
b5956f2
91e9e14
b4a7f15
4a16614
292a30e
95a795c
7c6d66a
9346fa6
5996ca7
0009d73
7dd2647
65ad350
f8a7bca
1ec2ebe
b45b40f
dc40d14
139d32d
58b111f
26047bc
2f74b15
bd9a95d
4d1b4c7
f49811a
5457455
027345a
3f9b133
666bf11
df2370b
f6516d7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,17 +4,25 @@ keywords: [EuiDragDropContext, EuiDroppable, EuiDraggable] | |
|
|
||
| # Drag and drop | ||
|
|
||
| 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: | ||
| ```mdx-code-block | ||
| import { EuiLink } from '@elastic/eui'; | ||
| ``` | ||
|
|
||
| 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: | ||
|
|
||
| - `<EuiDragDropContext />`: Section of your application containing the draggable elements and the drop targets. | ||
| - `<EuiDroppable />`: Area into which items can be dropped. Contains one or more `<EuiDraggable />`. | ||
| - `<EuiDraggable />`: Items that can be dragged. Must be part of an `<EuiDroppable />`. | ||
|
|
||
| * `<EuiDragDropContext />`: Section of your application containing the draggable elements and the drop targets. | ||
| * `<EuiDroppable />`: Area into which items can be dropped. Contains one or more `<EuiDraggable />`. | ||
| * `<EuiDraggable />`: Items that can be dragged. Must be part of an `<EuiDroppable />` | ||
| :::accessibility Consider your users and use case | ||
|
|
||
| :::warning Consider your users and use case | ||
| 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. EUI maintains accessibility support but carefully consider your users' context when choosing this pattern. | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Opinionated rewrite of the previous admonition that was long, used some hefty words and, in my opinion, didn't get the point across effectively. I can drop if someone disagrees. |
||
|
|
||
| ::: | ||
|
|
||
| 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. | ||
| :::warning Limitations | ||
|
|
||
| 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. | ||
| 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. | ||
|
Comment on lines
+23
to
+25
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added an admonition to warn users about the limitations of our drag and drop components and redirect them to the "Nested drag and drop" pattern. |
||
|
|
||
| ::: | ||
|
|
||
|
|
@@ -28,16 +36,16 @@ All **EuiDragDropContext** elements are discrete and isolated; **EuiDroppables** | |
|
|
||
| **EuiDragDropContext** handles all events but makes no assumptions about the result of a drop event. As such, the following event handlers are available: | ||
|
|
||
| * `onBeforeDragStart` | ||
| * `onDragStart` | ||
| * `onDragUpdate` | ||
| * `onDragEnd` (required) | ||
| - `onBeforeDragStart` | ||
| - `onDragStart` | ||
| - `onDragUpdate` | ||
| - `onDragEnd` (required) | ||
|
|
||
| EUI also provides methods for helping to deal to common action types: | ||
|
|
||
| * `reorder`: change an item's location in a droppable area | ||
| * `copy`: create a duplicate of an item in a different droppable area | ||
| * `move`: move an item to a different droppable area | ||
| - `reorder`: change an item's location in a droppable area | ||
| - `copy`: create a duplicate of an item in a different droppable area | ||
| - `move`: move an item to a different droppable area | ||
|
|
||
| ```tsx interactive | ||
| import React, { useState } from 'react'; | ||
|
|
@@ -77,7 +85,6 @@ export default () => { | |
| </EuiDragDropContext> | ||
| ); | ||
| }; | ||
|
|
||
| ``` | ||
|
|
||
| ## Simple item reorder | ||
|
|
@@ -109,13 +116,15 @@ const makeList = (number, start = 1) => | |
|
|
||
| export default () => { | ||
| const [list, setList] = useState(makeList(3)); | ||
|
|
||
| const onDragEnd = ({ source, destination }) => { | ||
| if (source && destination) { | ||
| const items = euiDragDropReorder(list, source.index, destination.index); | ||
|
|
||
| setList(items); | ||
| } | ||
| }; | ||
|
|
||
| return ( | ||
| <EuiDragDropContext onDragEnd={onDragEnd}> | ||
| <EuiDroppable droppableId="DROPPABLE_AREA" spacing="m" withPanel> | ||
|
|
@@ -133,7 +142,6 @@ export default () => { | |
| </EuiDragDropContext> | ||
| ); | ||
| }; | ||
|
|
||
| ``` | ||
|
|
||
| ## Custom drag handle | ||
|
|
@@ -143,7 +151,9 @@ By default the entire element surface can initiate a drag. To specify an element | |
| 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. | ||
|
|
||
| :::accessibility Accessibility requirement | ||
|
|
||
| **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`. | ||
|
|
||
| ::: | ||
|
|
||
| ```tsx interactive | ||
|
|
@@ -172,13 +182,15 @@ const makeList = (number, start = 1) => | |
|
|
||
| export default () => { | ||
| const [list, setList] = useState(makeList(3)); | ||
|
|
||
| const onDragEnd = ({ source, destination }) => { | ||
| if (source && destination) { | ||
| const items = euiDragDropReorder(list, source.index, destination.index); | ||
|
|
||
| setList(items); | ||
| } | ||
| }; | ||
|
|
||
| return ( | ||
| <EuiDragDropContext onDragEnd={onDragEnd}> | ||
| <EuiDroppable | ||
|
|
@@ -218,7 +230,6 @@ export default () => { | |
| </EuiDragDropContext> | ||
| ); | ||
| }; | ||
|
|
||
| ``` | ||
|
|
||
| ## Interactive elements | ||
|
|
@@ -252,13 +263,15 @@ const makeList = (number, start = 1) => | |
|
|
||
| export default () => { | ||
| const [list, setList] = useState(makeList(3)); | ||
|
|
||
| const onDragEnd = ({ source, destination }) => { | ||
| if (source && destination) { | ||
| const items = euiDragDropReorder(list, source.index, destination.index); | ||
|
|
||
| setList(items); | ||
| } | ||
| }; | ||
|
|
||
| return ( | ||
| <EuiDragDropContext onDragEnd={onDragEnd}> | ||
| <EuiDroppable | ||
|
|
@@ -302,7 +315,6 @@ export default () => { | |
| </EuiDragDropContext> | ||
| ); | ||
| }; | ||
|
|
||
| ``` | ||
|
|
||
| ## Move between lists | ||
|
|
@@ -366,6 +378,7 @@ export default () => { | |
| } | ||
| } | ||
| }; | ||
|
|
||
| return ( | ||
| <EuiDragDropContext onDragEnd={onDragEnd}> | ||
| <EuiFlexGroup> | ||
|
|
@@ -437,7 +450,6 @@ export default () => { | |
| </EuiDragDropContext> | ||
| ); | ||
| }; | ||
|
|
||
| ``` | ||
|
|
||
| ## Distinguish droppable areas by type | ||
|
|
@@ -474,6 +486,7 @@ export default () => { | |
| const [list1, setList1] = useState(makeList(3)); | ||
| const [list2, setList2] = useState(makeList(3, 4)); | ||
| const [list3, setList3] = useState(makeList(3, 7)); | ||
|
|
||
| const onDragEnd = ({ source, destination }) => { | ||
| const lists = { | ||
| DROPPABLE_AREA_TYPE_1: list1, | ||
|
|
@@ -509,6 +522,7 @@ export default () => { | |
| } | ||
| } | ||
| }; | ||
|
|
||
| return ( | ||
| <EuiDragDropContext onDragEnd={onDragEnd}> | ||
| <EuiFlexGroup> | ||
|
|
@@ -576,7 +590,6 @@ export default () => { | |
| </EuiDragDropContext> | ||
| ); | ||
| }; | ||
|
|
||
| ``` | ||
|
|
||
| ## Copyable items | ||
|
|
@@ -617,22 +630,27 @@ export default () => { | |
| const [isItemRemovable, setIsItemRemovable] = useState(false); | ||
| const [list1, setList1] = useState(makeList(3)); | ||
| const [list2, setList2] = useState([]); | ||
|
|
||
| const lists = { DROPPABLE_AREA_COPY_1: list1, DROPPABLE_AREA_COPY_2: list2 }; | ||
|
|
||
| const actions = { | ||
| DROPPABLE_AREA_COPY_1: setList1, | ||
| DROPPABLE_AREA_COPY_2: setList2, | ||
| }; | ||
|
|
||
| const remove = (droppableId, index) => { | ||
| const list = Array.from(lists[droppableId]); | ||
| list.splice(index, 1); | ||
|
|
||
| actions[droppableId](list); | ||
| }; | ||
|
|
||
| const onDragUpdate = ({ source, destination }) => { | ||
| const shouldRemove = | ||
| !destination && source.droppableId === 'DROPPABLE_AREA_COPY_2'; | ||
| setIsItemRemovable(shouldRemove); | ||
| }; | ||
|
|
||
| const onDragEnd = ({ source, destination }) => { | ||
| if (source && destination) { | ||
| if (source.droppableId === destination.droppableId) { | ||
|
|
@@ -664,6 +682,7 @@ export default () => { | |
| remove(source.droppableId, source.index); | ||
| } | ||
| }; | ||
|
|
||
| return ( | ||
| <EuiDragDropContext onDragEnd={onDragEnd} onDragUpdate={onDragUpdate}> | ||
| <EuiFlexGroup> | ||
|
|
@@ -787,6 +806,7 @@ export default () => { | |
| const [isPopoverOpen, setIsPopoverOpen] = useState(false); | ||
|
|
||
| const [list, setList] = useState(makeList(3)); | ||
|
|
||
| const onDragEnd: OnDragEndResponder = ({ source, destination }) => { | ||
| if (source && destination) { | ||
| const items = euiDragDropReorder(list, source.index, destination.index); | ||
|
|
@@ -945,16 +965,19 @@ export default () => { | |
| const [list, setList] = useState([1, 2]); | ||
| const [list1, setList1] = useState(makeList(3)); | ||
| const [list2, setList2] = useState(makeList(3, 4)); | ||
|
|
||
| const lists = { | ||
| COMPLEX_DROPPABLE_PARENT: list, | ||
| COMPLEX_DROPPABLE_AREA_1: list1, | ||
| COMPLEX_DROPPABLE_AREA_2: list2, | ||
| }; | ||
|
|
||
| const actions = { | ||
| COMPLEX_DROPPABLE_PARENT: setList, | ||
| COMPLEX_DROPPABLE_AREA_1: setList1, | ||
| COMPLEX_DROPPABLE_AREA_2: setList2, | ||
| }; | ||
|
|
||
| const onDragEnd = ({ source, destination }) => { | ||
| if (source && destination) { | ||
| if (source.droppableId === destination.droppableId) { | ||
|
|
@@ -980,6 +1003,7 @@ export default () => { | |
| } | ||
| } | ||
| }; | ||
|
|
||
| return ( | ||
| <EuiDragDropContext onDragEnd={onDragEnd}> | ||
| <EuiDroppable | ||
|
|
@@ -1039,7 +1063,9 @@ export default () => { | |
|
|
||
| ## Props | ||
|
|
||
| ```mdx-code-block | ||
| import docgen from '@elastic/eui-docgen/dist/components/drag_and_drop'; | ||
| ``` | ||
|
|
||
| <PropTable definition={docgen.EuiDragDropContext} /> | ||
| <PropTable definition={docgen.EuiDraggable} /> | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is some opinionated formatting in this diff so I'll highlight the most important changes with comments 👇🏻