npx create-react-app myapp --template @thomd/cra-template-simple
cd myapp
npm install @hello-pangea/dnd --sav
Then update App.jsx and npm start
| import React, { useState } from 'react' | |
| import { DragDropContext, Droppable, Draggable } from '@hello-pangea/dnd' | |
| // fake data generator | |
| const getItems = (count, offset = 0) => | |
| Array.from({ length: count }, (v, k) => k).map((k) => ({ | |
| id: `item-${k + offset}-${new Date().getTime()}`, | |
| content: `item ${k + offset}`, | |
| })) | |
| const reorder = (list, startIndex, endIndex) => { | |
| const result = Array.from(list) | |
| const [removed] = result.splice(startIndex, 1) | |
| result.splice(endIndex, 0, removed) | |
| return result | |
| } | |
| // move item from one list to another list. | |
| const move = (source, destination, droppableSource, droppableDestination) => { | |
| const sourceClone = Array.from(source) | |
| const destClone = Array.from(destination) | |
| const [removed] = sourceClone.splice(droppableSource.index, 1) | |
| destClone.splice(droppableDestination.index, 0, removed) | |
| const result = {} | |
| result[droppableSource.droppableId] = sourceClone | |
| result[droppableDestination.droppableId] = destClone | |
| return result | |
| } | |
| const getItemStyle = (isDragging, draggableStyle) => ({ | |
| userSelect: 'none', | |
| padding: '1rem', | |
| margin: `0 0 1rem 0`, | |
| background: isDragging ? 'lightgreen' : 'grey', | |
| ...draggableStyle, | |
| }) | |
| const getListStyle = (isDraggingOver) => ({ | |
| background: isDraggingOver ? 'lightblue' : 'lightgrey', | |
| padding: '1rem', | |
| width: '16rem', | |
| }) | |
| export default function App() { | |
| const [state, setState] = useState([getItems(4), getItems(4, 4)]) | |
| function onDragEnd(result) { | |
| const { source, destination } = result | |
| if (!destination) { | |
| return | |
| } | |
| const sInd = +source.droppableId | |
| const dInd = +destination.droppableId | |
| if (sInd === dInd) { | |
| const items = reorder(state[sInd], source.index, destination.index) | |
| const newState = [...state] | |
| newState[sInd] = items | |
| setState(newState) | |
| } else { | |
| const result = move(state[sInd], state[dInd], source, destination) | |
| const newState = [...state] | |
| newState[sInd] = result[sInd] | |
| newState[dInd] = result[dInd] | |
| setState(newState.filter((group) => group.length)) | |
| } | |
| } | |
| return ( | |
| <div> | |
| <div style={{ display: 'flex' }}> | |
| <DragDropContext onDragEnd={onDragEnd}> | |
| {state.map((el, ind) => ( | |
| <Droppable key={ind} droppableId={`${ind}`}> | |
| {(provided, snapshot) => ( | |
| <div ref={provided.innerRef} style={getListStyle(snapshot.isDraggingOver)} {...provided.droppableProps}> | |
| {el.map((item, index) => ( | |
| <Draggable key={item.id} draggableId={item.id} index={index}> | |
| {(provided, snapshot) => ( | |
| <div | |
| ref={provided.innerRef} | |
| {...provided.draggableProps} | |
| {...provided.dragHandleProps} | |
| style={getItemStyle(snapshot.isDragging, provided.draggableProps.style)}> | |
| <div | |
| style={{ | |
| display: 'flex', | |
| justifyContent: 'space-around', | |
| }}> | |
| {item.content} | |
| </div> | |
| </div> | |
| )} | |
| </Draggable> | |
| ))} | |
| {provided.placeholder} | |
| </div> | |
| )} | |
| </Droppable> | |
| ))} | |
| </DragDropContext> | |
| </div> | |
| </div> | |
| ) | |
| } |