/* eslint-disable react/forbid-prop-types */ /* eslint-disable react/require-default-props */ import React, { Fragment } from 'react' import PropTypes from 'prop-types' import { pick } from 'lodash' import { findDOMNode } from 'react-dom' import { compose, toClass } from 'recompose' import { DragSource, DropTarget } from 'react-dnd' const itemSource = { beginDrag(props) { return pick(props, props.beginDragProps) }, } const itemTarget = { hover({ moveItem, index, listId }, monitor, component) { const { index: dragIndex, listId: toListId } = monitor.getItem() const hoverIndex = index if (listId !== toListId) { return } if (dragIndex === hoverIndex) { return } const hoverBoundingRect = findDOMNode(component).getBoundingClientRect() // eslint-disable-line const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2 const clientOffset = monitor.getClientOffset() const hoverClientY = clientOffset.y - hoverBoundingRect.top if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) { return } if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) { return } if (typeof moveItem === 'function') { moveItem(dragIndex, hoverIndex, monitor.getItem()) } monitor.getItem().index = hoverIndex }, drop({ dropItem, ...restProps }, monitor) { if (dropItem && typeof dropItem === 'function') dropItem(monitor.getItem(), restProps) }, } const Item = ({ listItem, dragHandle, connectDragSource, connectDropTarget, connectDragPreview, ...rest }) => dragHandle ? connectDragPreview( connectDropTarget( <div style={{ flex: 1 }}> {React.createElement(listItem, { ...rest, dragHandle: connectDragSource( <div style={{ display: 'flex', alignSelf: 'stretch' }}> {React.createElement(dragHandle)} </div>, ), })} </div>, ), ) : connectDropTarget( connectDragSource( <div style={{ flex: 1 }}>{React.createElement(listItem, rest)}</div>, ), ) const DecoratedItem = compose( DropTarget('item', itemTarget, (connect, monitor) => ({ connectDropTarget: connect.dropTarget(), isOver: monitor.isOver(), })), DragSource('item', itemSource, (connect, monitor) => ({ connectDragSource: connect.dragSource(), connectDragPreview: connect.dragPreview(), isDragging: monitor.isDragging(), })), toClass, )(Item) const SortableList = ({ listItem, dragHandle, items = [], itemKey = 'id', ...rest }) => ( <Fragment> {items.map((item, i) => ( <DecoratedItem dragHandle={dragHandle} index={i} item={item} key={item[itemKey]} listItem={listItem} {...item} {...rest} /> ))} </Fragment> ) SortableList.propTypes = { /** List items. */ items: PropTypes.array, /** Render prop for list's item. */ listItem: PropTypes.oneOfType([PropTypes.element, PropTypes.func]).isRequired, /** Key used to map through items. */ itemKey: PropTypes.string, /** Function invoked to change the order of the list's items. */ moveItem: PropTypes.func, /** Function invoked when the currently dragged item is dropped. */ dropItem: PropTypes.func, /** * What props to pick from the dragged item. E.g.: if a specific property is needed * in the move function. * */ beginDragProps: PropTypes.array, } SortableList.moveItem = (items, dragIndex, hoverIndex) => { if (!dragIndex) return items if (dragIndex <= hoverIndex) { return [ ...items.slice(0, dragIndex), items[hoverIndex], items[dragIndex], ...items.slice(hoverIndex + 1), ] } return [ ...items.slice(0, hoverIndex), items[dragIndex], items[hoverIndex], ...items.slice(dragIndex + 1), ] } export default SortableList