Skip to content

Instantly share code, notes, and snippets.

@jlroettger
Created August 30, 2016 00:53

Revisions

  1. jlroettger created this gist Aug 30, 2016.
    33 changes: 33 additions & 0 deletions ReorderableList.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,33 @@
    import React, { Component } from 'react'
    import { Link } from 'react-router'
    // Drag and Drop
    import { DragDropContext } from 'react-dnd'
    import HTML5Backend from 'react-dnd-html5-backend'
    // Material UI
    import { List } from 'material-ui/List'
    import Subheader from 'material-ui/Subheader'

    class ReorderableList extends Component {

    constructor(props) {
    super(props)
    this.state = {
    dndConstraint: this.props.dndConstraint || (Math.random() + 1).toString()
    }
    }

    render() {
    const { children, ...props } = this.props
    const { dndConstraint } = this.state
    return (
    <List { ...props }>
    {React.Children.map(children, (child) => {
    if (['div', Subheader, Link].includes(child.type)) return child
    return React.cloneElement(child, { listIdentifier: dndConstraint })
    })}
    </List>
    );
    }
    }

    export default DragDropContext(HTML5Backend)(ReorderableList)
    112 changes: 112 additions & 0 deletions ReorderableListItem.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,112 @@
    import React, { Component, PropTypes } from 'react'
    // Drag and Drop
    import { findDOMNode } from 'react-dom'
    import { DragSource, DropTarget } from 'react-dnd'
    // Material UI
    import { ListItem } from 'material-ui/List'
    import { fade } from 'material-ui/utils/colorManipulator'

    const itemSource = {
    beginDrag(props) {
    let { id, index } = props
    return {
    id,
    index
    }
    }
    }

    const itemTarget = {
    hover(props, monitor, component) {

    let { id: dragId, index: dragIndex } = monitor.getItem()
    let { id: hoverId, index: hoverIndex } = props

    if (dragIndex === hoverIndex) return; // Don't replace items with themselves

    let hoveringOffsets = findDOMNode(component).getBoundingClientRect()
    let penPercent = 0.50 // Percentage distance into next item before swap
    let penMin = (hoveringOffsets.bottom - hoveringOffsets.top) * penPercent
    let clientOffset = monitor.getClientOffset()
    let penY

    // Dragging downwards
    if (dragIndex < hoverIndex) penY = clientOffset.y - hoveringOffsets.top
    // Dragging upwards
    if (dragIndex > hoverIndex) penY = hoveringOffsets.bottom - clientOffset.y

    if ( !(penY > penMin) ) return // Exit it haven't penetrated enough

    // Time to actually perform the action
    props.moveItem({ dragId, dragIndex, hoverId, hoverIndex })

    // Note: we're mutating the monitor item here!
    // Generally it's better to avoid mutations,
    // but it's good here for the sake of performance
    // to avoid expensive index searches.
    monitor.getItem().index = hoverIndex
    }
    }

    const dndType = props => props.listIdentifier || 'reorderableListItem'

    class ReorderableListItem extends Component {

    static contextTypes = {
    muiTheme: PropTypes.object.isRequired
    }

    componentDidMount() {
    // Does not work in IE
    const img = new Image();
    img.onload = () => this.props.connectDragPreview(img);
    // Single transparent pixel
    img.src = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'
    }

    render() {
    const textColor = this.context.muiTheme.baseTheme.palette.textColor;
    const {
    children,
    connectDragSource,
    connectDropTarget,
    isDragging,
    style,
    // eslint-disable-next-line no-unused-vars
    index, moveReport, renameReport, moveItem, connectDragPreview, // omit pass hackAutoComplete
    ...props
    } = this.props
    return connectDragSource(connectDropTarget(
    <div>
    <ListItem
    { ...props }
    style={{
    ...style,
    backgroundColor: isDragging && fade(textColor, 0.4),
    cursor: isDragging && 'move'
    }}
    >
    {children}
    </ListItem>
    </div>
    ))
    }
    }

    const connectTarget = connect => ({
    connectDropTarget: connect.dropTarget()
    })

    const connectSource = (connect, monitor) => ({
    connectDragSource: connect.dragSource(),
    connectDragPreview: connect.dragPreview(),
    isDragging: monitor.isDragging()
    })

    export default (
    DropTarget(dndType, itemTarget, connectTarget)(
    DragSource(dndType, itemSource, connectSource)(
    ReorderableListItem
    )
    )
    )
    2 changes: 2 additions & 0 deletions index.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,2 @@
    export ReorderableList from './ReorderableList'
    export ReorderableListItem from './ReorderableListItem'