react-conventions
Version:
An open source set of React components that implement Ambassador's Design and UX patterns.
154 lines (136 loc) • 4.01 kB
JavaScript
import React from 'react'
import update from 'react/lib/update'
import SortableItem from './SortableItem'
import { DragDropContext } from 'react-dnd'
import HTML5Backend from 'react-dnd-html5-backend'
import CustomDragLayer from './CustomDragLayer'
import style from './style.scss'
export class SortableList extends React.Component {
constructor(props) {
super(props)
}
static propTypes = {
/**
* Items to display in the list.
*/
items: React.PropTypes.array.isRequired,
/**
* Name of the SortableList.
*/
name: React.PropTypes.string,
/**
* A callback function to be called when the order of the items changes or when an item is toggled.
*/
changeCallback: React.PropTypes.func,
/**
* A callback function to be called when dragging starts.
*/
onDragStart: React.PropTypes.func,
/**
* A callback function to be called when dragging stops.
*/
onDragStop: React.PropTypes.func,
/**
* Optional styles to add to the SortableList component.
*/
optClass: React.PropTypes.string
}
state = {
items: this.props.items,
dragging: false,
width: 0,
left: 0
}
handleResize = () => {
this.setState({ width: this._sortableList.getBoundingClientRect().width, top: this._sortableList.getBoundingClientRect().top })
}
componentDidMount = () => {
this.handleResize()
// Add event listener
window.addEventListener('resize', this.handleResize)
}
componentWillUnmount = () => {
// Remove event listener
window.removeEventListener('resize', this.handleResize)
}
componentWillReceiveProps = (nextProps) => {
this.setState({ items: nextProps.items })
}
moveSortableItem = (dragIndex, hoverIndex) => {
const { items } = this.state
const dragSortableItem = items[dragIndex]
this.setState(update(this.state, {
items: {
$splice: [
[dragIndex, 1],
[hoverIndex, 0, dragSortableItem]
]
}
}), function() {
if (this.props.changeCallback) {
this.props.changeCallback({
target: {
name: this.props.name,
value: this.state.items
}
})
}
})
}
toggleSortableItem = (index) => {
let items = this.state.items
items[index].active = !items[index].active
this.setState(update(this.state, {
items: { $set: items }
}), function() {
if (this.props.changeCallback) {
this.props.changeCallback({
target: {
name: this.props.name,
value: this.state.items
}
})
}
})
}
onDragStart = () => {
this.setState({ dragging: true }, function() {
if (this.props.onDragStart) {
this.props.onDragStart()
}
})
}
onDragStop = () => {
this.setState({ dragging: false }, function() {
if (this.props.onDragStop) {
this.props.onDragStop()
}
})
}
render = () => {
const { items } = this.state
return (
<div className={style['sortable-list-container']} ref={(c) => this._sortableList = c}>
<div className={style['sortable-list']}>
{items.map((item, i) => {
return (
<SortableItem key={item.value}
index={i}
value={item.value}
text={item.text}
active={item.active}
moveSortableItem={this.moveSortableItem}
toggleSortableItem={this.toggleSortableItem}
getDimensions={this.handleResize}
onDragStart={this.onDragStart}
onDragStop={this.onDragStop}
count={items.length} />
)
})}
</div>
{ this.state.dragging ? <CustomDragLayer dimensions={{ width: this.state.width, top: this.state.top }} count={this.state.items.length} /> : null }
</div>
)
}
}
export default DragDropContext(HTML5Backend)(SortableList)