DnD for Transfer/Relink Holdings and/or Items

DnD for Transfer/Relink Holdings and/or Items

Overview

https://github.com/atlassian/react-beautiful-dnd library was reviewed to support items transfer between tables with DnD

Library provides mostly all functionality we need:

  • multi select

  • multiple drop zones

  • drop zone disable

  • accessibility (with multi drag support it should be implemented), but there are some limitations like DnD can be done only for sibling zones - would be nice to discuss expectations

Library doesn't provide a way to replace content of item is currently drugging, but additional components can be added to display how many items are dragging.

Code example

Row.js

import React from 'react'; import { Draggable } from 'react-beautiful-dnd'; const getItemStyle = (draggableStyle, isSelected) => { return { userSelect: 'none', fontWeight: isSelected ? 600 : 300, ...draggableStyle }; }; const primaryButton = 0; const keyCodes = { enter: 13, escape: 27, arrowDown: 40, arrowUp: 38, tab: 9, }; const Row = ({ rowClass, cells, rowIndex, rowData, rowProps, }) => { const { getIsSelected, toggleSelection, } = rowProps; const onKeyDown = (event, provided, snapshot) => { if (event.defaultPrevented) { return; } if (snapshot.isDragging) { return; } if (event.keyCode !== keyCodes.enter) { return; } // we are using the event for selection event.preventDefault(); toggleSelection(rowData.id); }; const onClick = (event) => { if (event.defaultPrevented) { return; } if (event.button !== primaryButton) { return; } // marking the event as used event.preventDefault(); toggleSelection(rowData.id); }; const onTouchEnd = (event: TouchEvent) => { if (event.defaultPrevented) { return; } // marking the event as used // we would also need to add some extra logic to prevent the click // if this element was an anchor event.preventDefault(); toggleSelection(rowData.id); }; return ( <Draggable key={`${rowData.id}`} draggableId={`${rowData.id}`} index={rowIndex} > {(provided, snapshot) => ( <div ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps} onTouchEnd={onTouchEnd} onKeyDown={(event) => onKeyDown(event, provided, snapshot)} onClick={onClick} className={rowClass} role="row" tabIndex="0" style={getItemStyle( provided.draggableProps.style, getIsSelected(rowData.id), )} > {cells} </div> )} </Draggable> ); }; export default Row;


 List.js

import React from 'react'; import { Droppable } from 'react-beautiful-dnd'; import { MultiColumnList, } from '@folio/stripes/components'; import Row from './Row'; const List = ({ droppableId, contentData, visibleColumns, isDroppable, getIsSelected, toggleSelection, }) => { return ( <Droppable droppableId={droppableId} isDropDisabled={!isDroppable} > {(provided) => ( <div {...provided.droppableProps} ref={provided.innerRef} > <MultiColumnList contentData={contentData} visibleColumns={visibleColumns} rowFormatter={Row} height={300} rowProps={{ getIsSelected, toggleSelection, }} /> {provided.placeholder} </div> )} </Droppable> ); }; export default List;



Pane.js

import React, { useState, } from 'react'; import { DragDropContext } from 'react-beautiful-dnd'; import List from './List'; const visibleColumns = ['id', 'name']; const contentData = { 'uid1': [ { id: 1, name: 'test 1', }, { id: 2, name: 'test 2', }, { id: 3, name: 'test 3', }, ], 'uid2': [ { id: 4, name: 'test 4', }, { id: 5, name: 'test 5', }, { id: 6, name: 'test 6', }, ], }; const Pane = () => { const [lists, setLists] = useState(contentData); const [selectedIds, setSelectedIds] = useState({}); const [activeDroppable, setActiveDroppable] = useState(); const onDragStart = (result) => { setActiveDroppable(result.source.droppableId); }; const onDragEnd = (result) => { if (!result.destination) return; const selectedItemIds = Object.keys(selectedIds).filter(itemId => selectedIds[itemId]); if (!selectedItemIds.length) { selectedItemIds.push(result.draggableId); } setLists({ ...lists, [result.source.droppableId]: lists[result.source.droppableId].filter(item => !selectedItemIds.includes(`${item.id}`)), [result.destination.droppableId]: [ ...lists[result.destination.droppableId], ...lists[result.source.droppableId].filter(item => selectedItemIds.includes(`${item.id}`)) ], }); setActiveDroppable(undefined); }; const toggleSelection = (id) => { setSelectedIds({ ...selectedIds, [id]: !selectedIds[id], }); }; const getIsSelected = (id) => { return selectedIds[id]; }; return ( <DragDropContext onDragStart={onDragStart} onDragEnd={onDragEnd} > <div> { Object.keys(lists).map((listId) => ( <List key={listId} droppableId={listId} contentData={lists[listId]} visibleColumns={visibleColumns} isDroppable={activeDroppable !== listId} getIsSelected={getIsSelected} toggleSelection={toggleSelection} /> )) } </div> </DragDropContext> ); }; export default Pane;

Result recording