Skip to content

Commit

Permalink
feat(react-sortable): Add sortable list component
Browse files Browse the repository at this point in the history
[Finishes #85641322]

Signed-off-by: Nicole Sullivan <nsullivan@pivotal.io>
  • Loading branch information
rdy authored and Geoff Pleiss committed Jan 22, 2015
1 parent f7ee708 commit 3242698
Show file tree
Hide file tree
Showing 12 changed files with 504 additions and 3 deletions.
2 changes: 1 addition & 1 deletion gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ gulp.task('_buildPuiJs', ['_cleanBuiltPuiJs'], function() {

gulp.task('_buildPuiReactJs', ['_cleanBuiltPuiJs'], function() {
var b = browserify('./src/pivotal-ui/javascripts/pivotal-ui-react.js');
b.transform(reactify);
b.transform(reactify, {es6: true});

return b.bundle()
.pipe(source('./pivotal-ui-react.js'))
Expand Down
4 changes: 3 additions & 1 deletion karma.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ module.exports = function(config) {
basePath: './',
browserNoActivityTimeout: 60000,
browserify: {
transform: ['reactify']
transform: [
['reactify', {es6: true}]
]
},
browsers: ['Chrome', 'PhantomJS'],
colors: true,
Expand Down
70 changes: 70 additions & 0 deletions src/pivotal-ui/components/lists.scss
Original file line number Diff line number Diff line change
Expand Up @@ -977,3 +977,73 @@ or you want to vertically align it.
Use [Card lists][list_cards] if you'd like to make a grid of vertically and horizontally aligned cards.
*/


/*doc
---
title: React Draggable List
name: react_draggable_list
categories:
- Beta
---
Creates a draggable list.
The property `onDrop` is a callback when a drop event has completed. Use this
if you need to make an API call to update the order of some elements.
```jsx_example
var draggableListDropCallback = function(data) {
alert('New item indices order: ' + data);
};
```
```react_example
<UI.DraggableList onDrop={draggableListDropCallback}>
<UI.DraggableListItem>
Get me out of here!
</UI.DraggableListItem>
<UI.DraggableListItem>
LOL
</UI.DraggableListItem>
<UI.DraggableListItem>
Can't stop
</UI.DraggableListItem>
</UI.DraggableList>
```
*/

.list-draggable {
@include user-select(none);

.draggable-grip {
display: inline-block;
visibility: hidden;
color: $gray-6;
}

> li {
width: 100%;

&.hover {
cursor: pointer;

.draggable-grip {
visibility: visible;
}
}

&.grabbed {
background-color: $list-draggable-bg;
* {
visibility: hidden;
}
}

&.grabbed .draggable-grip {
visibility: hidden;
}
}
}
2 changes: 2 additions & 0 deletions src/pivotal-ui/components/pui-variables.scss
Original file line number Diff line number Diff line change
Expand Up @@ -960,6 +960,8 @@ $list-group-link-color: #555 !default;
$list-group-link-hover-color: $list-group-link-color !default;
$list-group-link-heading-color: #333 !default;

$list-draggable-bg: $teal-3;


// Panels
// -------------------------
Expand Down
3 changes: 3 additions & 0 deletions src/pivotal-ui/javascripts/components.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ module.exports = {
SimpleTabs: require('./tabs.jsx').SimpleTabs,
SimpleAltTabs: require('./tabs.jsx').SimpleAltTabs,

DraggableList: require('./draggable-list.js').DraggableList,
DraggableListItem: require('./draggable-list.js').DraggableListItem,

Dropdown: require('./dropdowns.jsx').Dropdown,
DropdownItem: require('./dropdowns.jsx').DropdownItem,
LinkDropdown: require('./dropdowns.jsx').LinkDropdown,
Expand Down
125 changes: 125 additions & 0 deletions src/pivotal-ui/javascripts/draggable-list.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
'use strict';
var React = require('react/addons');
var _ = require('lodash');
var cx = React.addons.classSet;
var {move} = require('./utils');
var HoverMixin = require('./mixins/hover-mixin');

function preventDefault(e) {
e.preventDefault();
}

var DraggableList = React.createClass({
propTypes: {
onDrop: React.PropTypes.func
},

getInitialProps: function() {
return {
onDrop: _.noop
};
},

getInitialState: function() {
return {
itemIndices: _.times(this.props.children.length),
draggingId: null
};
},

componentWillReceiveProps: function(nextProps) {
if (nextProps.children) {
this.setState({
itemIndices: _.times(nextProps.children.length),
draggingId: null
});
}
},

dragStart: function(draggingId, e) {
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.dropEffect = 'move';
e.dataTransfer.setData('text/plain', '');
setTimeout(function() { this.setState({draggingId}); }.bind(this), 0);
},

dragEnd: function() {
this.setState({draggingId: null});
},

dragEnter: function(e) {
var {draggingId, itemIndices} = this.state;
var endDraggingId = parseInt(e.currentTarget.dataset.draggingId, 10);
if (draggingId === null || _.isNaN(endDraggingId)) {
return;
}

var startIndex = itemIndices.indexOf(draggingId);
var endIndex = itemIndices.indexOf(endDraggingId);

move(itemIndices, startIndex, endIndex);
this.setState({itemIndices});
},

drop: function() {
this.props.onDrop(this.state.itemIndices);
},

render: function() {
var grabbed, items = [];
React.Children.forEach(this.props.children, function(child, draggingId) {
grabbed = this.state.draggingId === draggingId;
items.push(React.addons.cloneWithProps(child, {grabbed, onDragStart: _.bind(this.dragStart, this, draggingId), onDragEnd: this.dragEnd, onDragEnter: this.dragEnter, onDrop: this.drop, draggingId, key: draggingId}));
}, this);
var sortedItems = _.map(this.state.itemIndices, i => items[i]);
return (
<ul className={cx({'list-group list-draggable': true, dragging: this.state.draggingId !== null})}>
{sortedItems}
</ul>
);
}
});

var DraggableListItem = React.createClass({
mixins: [HoverMixin],

propTypes: {
draggingId: React.PropTypes.number,
onMouseEnter: React.PropTypes.func,
onMouseLeave: React.PropTypes.func,
onDragStart: React.PropTypes.func,
onDragEnter: React.PropTypes.func,
onDragEnd: React.PropTypes.func,
onDrop: React.PropTypes.func
},

render: function() {
var {hover} = this.state;
var {grabbed, onDragStart, onDragEnd, onDragEnter, onDrop, draggingId} = this.props;
var {onMouseEnter, onMouseLeave} = this;
var className = cx({'list-group-item pln': true, grabbed, hover});
var props = {
className, onMouseEnter, onMouseLeave, onDragStart, onDragEnd, onDragEnter, onDrop,
onDragOver: preventDefault,
draggable: !grabbed,
'data-dragging-id': draggingId
};
return (
<li {...props} aria-dropeffect="move">
<div className='draggable-grip mhl' aria-grabbed={grabbed} role='button'>
<i className='fa fa-ellipsis-v mrs'/>
<i className='fa fa-ellipsis-v'/>
<span className='sr-only'>Drag to reorder</span>
</div>
<span>
{this.props.children}
</span>
</li>
);
}
});

module.exports = {
DraggableList,
DraggableListItem
};
19 changes: 19 additions & 0 deletions src/pivotal-ui/javascripts/mixins/hover-mixin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
'use strict';

var HoverMixin = {
getInitialState: function() {
return {
hover: false
};
},

onMouseEnter: function() {
this.setState({hover: true});
},

onMouseLeave: function() {
this.setState({hover: false});
}
};

module.exports = HoverMixin;
21 changes: 20 additions & 1 deletion src/pivotal-ui/javascripts/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ var breakpoints = {
xlMin: 1800
};

module.exports = window.utils = {
var utils = {
isMinWidthXs: function minWidthXs() {
return isMinWidth(breakpoints.xsMin);
},
Expand All @@ -29,9 +29,28 @@ module.exports = window.utils = {

isMinWidthXl: function minWidthXs() {
return isMinWidth(breakpoints.xlMin);
},

move: function(collection, startIndex, endIndex) {
while (startIndex < 0) {
startIndex += collection.length;
}
while (endIndex < 0) {
endIndex += collection.length;
}
if (endIndex >= collection.length) {
var k = endIndex - collection.length;
while ((k--) + 1) {
collection.push(undefined);
}
}
collection.splice(endIndex, 0, collection.splice(startIndex, 1)[0]);
return collection;
}
};

module.exports = global.utils = utils;

function isMinWidth(width) {
return Modernizr.mq('(min-width: ' + width + 'px)');
}
Loading

0 comments on commit 3242698

Please sign in to comment.