-
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
Merged
weronikaolejniczak
merged 46 commits into
elastic:main
from
weronikaolejniczak:feat/drag-and-drop-pattern
Jan 13, 2026
Merged
Changes from all commits
Commits
Show all changes
46 commits
Select commit
Hold shift + click to select a range
9128fca
wip: revision 1
weronikaolejniczak 8a3716f
fix: update instruction when dragging
weronikaolejniczak e092e98
fix: change indicator placement
weronikaolejniczak 9693c1e
fix: auto-open newly nested accordions
weronikaolejniczak 5e2f9ce
fix: only show inner-most indicator
weronikaolejniczak 4e8a719
fix: remove unused import
weronikaolejniczak ba53905
feat: add consolidated file demo
weronikaolejniczak 043b5c3
feat: add a simple documentation
weronikaolejniczak bf22470
feat: remove code-block
weronikaolejniczak 2d2558d
fix: custom_typings type augmentation for css prop
weronikaolejniczak 0f70cb7
feat: update current dnd docs
weronikaolejniczak ef6fcda
feat: add grab icon
weronikaolejniczak c2d97e1
refactor: add LineIndicator component
weronikaolejniczak a4b9381
feat: implement blocked panels
weronikaolejniczak b5956f2
fix: drag and drop page title
weronikaolejniczak 91e9e14
fix: change panel padding size
weronikaolejniczak b4a7f15
fix: make overflow visible in accordion content
weronikaolejniczak 4a16614
chore: add comments
weronikaolejniczak 292a30e
fix: remove collapsing on drag, z-index and add comment
weronikaolejniczak 95a795c
fix: remove redundant onDragEnter
weronikaolejniczak 7c6d66a
chore: add a helpful comment about key prop
weronikaolejniczak 9346fa6
chore: add another helpful comment
weronikaolejniczak 5996ca7
chore: add a comment regarding relative position
weronikaolejniczak 0009d73
refactor: dry out the children... wth
weronikaolejniczak 7dd2647
fix: incorrect hover effects
weronikaolejniczak 65ad350
fix: make reordering possible for blocked panels
weronikaolejniczak f8a7bca
chore: set module to nodenext
weronikaolejniczak 1ec2ebe
chore: remove redundant pragma
weronikaolejniczak b45b40f
fix: block descendant drop
weronikaolejniczak dc40d14
refactor: create meaningful variables
weronikaolejniczak 139d32d
feat: add a11y roles
weronikaolejniczak 58b111f
feat: add aria attributes to tree
weronikaolejniczak 26047bc
feat: add a11y admonition
weronikaolejniczak 2f74b15
feat: update styles
weronikaolejniczak bd9a95d
feat: remove grab transition
weronikaolejniczak 4d1b4c7
refactor: rename style
weronikaolejniczak f49811a
feat: handle keyboard navigation
weronikaolejniczak 5457455
feat: add more actions
weronikaolejniczak 027345a
feat: update a11y admonition
weronikaolejniczak 3f9b133
fix: rephrase a11y admonition
weronikaolejniczak 666bf11
refactor: utility functions
weronikaolejniczak df2370b
fix: remove misleading comment
weronikaolejniczak f6516d7
fix: small issue
weronikaolejniczak 34aee0f
Update packages/website/docs/patterns/nested-drag-and-drop/index.mdx
weronikaolejniczak c397e9f
Update packages/website/docs/components/display/drag-and-drop.mdx
weronikaolejniczak 298ed25
Update packages/website/docs/patterns/nested-drag-and-drop/example.tsx
weronikaolejniczak File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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. The `@hello-pangea/dnd` package ensures a good amount of accessibility but carefully consider your users' context when choosing this pattern. | ||
|
|
||
| ::: | ||
|
|
||
| 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} /> | ||
|
|
||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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 👇🏻