Skip to content

Instantly share code, notes, and snippets.

@aschmoe
Last active June 27, 2017 03:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save aschmoe/5cf05ebec29d140f9bf0ad2c31d6a3cb to your computer and use it in GitHub Desktop.
Save aschmoe/5cf05ebec29d140f9bf0ad2c31d6a3cb to your computer and use it in GitHub Desktop.
Integrating react-select with react-sortable-hoc (ripped out mobx code, so possible its not entirely functional)
import React, { Component } from 'react';
import { PropTypes as PT } from 'prop-types';
import {
SortableHandle,
SortableElement,
SortableContainer
} from 'react-sortable-hoc';
/**
* Draggable handle wraps the label
*/
const DragHandle = SortableHandle(({ children }) => <span>{children}</span>);
DragHandle.propTypes = {
children: PT.node,
};
/**
* Sortable wrapper for item, mirrors the Value component
* https://github.com/JedWatson/react-select/blob/master/src/Value.js
*/
const DraggableItem = SortableElement(({ id, value, children, onRemove }) => {
let dragging = false;
const removeIt = (event) => {
event.preventDefault();
event.stopPropagation();
onRemove(value);
};
const handleTouchEndRemove = (event) => {
// Check if the view is being dragged, In this case
// we don't want to fire the click event (because the user only wants to scroll)
if (dragging) return;
// Fire the mouse events
removeIt(event);
};
const handleTouchMove = () => {
// Set a flag that the view is being dragged
dragging = true;
};
const handleTouchStart = () => {
// Set a flag that the view is not being dragged
dragging = false;
};
return (
<div className='Select-value'>
<span className="Select-value-icon"
aria-hidden="true"
onMouseDown={removeIt}
onTouchEnd={handleTouchEndRemove}
onTouchStart={handleTouchStart}
onTouchMove={handleTouchMove}>
&times;
</span>
<span className="Select-value-label" role="option" aria-selected="true" id={id}>
{children}
</span>
</div>
);
});
DraggableItem.propTypes = {
id: PT.string,
value: PT.object,
onRemove: PT.func,
children: PT.node,
};
/**
* Hacky wrapping element necessary to pull index to pass to SortableElement
*/
const DraggableItemWrap = ({ id, ...props }) => {
let index = 0;
id.replace(/.*?-value-(.*)?$/igm, (m, p1) => {
index = parseInt(p1, 10);
});
return <DraggableItem id={id} index={index} {...props} />;
};
DraggableItemWrap.propTypes = {
children: PT.node,
disabled: PT.bool, // disabled prop passed to ReactSelect
id: PT.string, // Unique id for the value - used for aria
onClick: PT.func, // method to handle click on value label
onRemove: PT.func, // method to handle removal of the value
value: PT.object.isRequired, // the option object for this value
};
/**
* Sortable wrapper for list
*/
const DraggableList = SortableContainer(({ children }) => children);
DraggableList.propTypes = {
children: PT.node,
};
export {
DragHandle,
DraggableItemWrap,
DraggableList,
};
import React, { Component } from 'react';
import { PropTypes as PT } from 'prop-types';
import ReactSelect from 'react-select';
import 'react-select/dist/react-select.css';
import { arrayMove } from 'react-sortable-hoc';
import {
DragHandle,
DraggableItemWrap,
DraggableList
} from './DraggableWrappers';
import './style.scss'
/**
* Draggable react select
*/
class InputDraggableSelect extends Component {
valueRenderer = option => (
<DragHandle>{option.label}</DragHandle>
)
// Function for setting array on drag
onSortEnd = ({ oldIndex, newIndex }) => {
this.props.updateOrder(arrayMove(this.props.value, oldIndex, newIndex));
}
render() {
const { creatable, value } = this.props;
return (
<DraggableList axis="xy"
shouldCancelStart={() => value && value.length < 2}
onSortEnd={props => this.onSortEnd(props)}
useDragHandle={true}
helperClass="draggable-dragging">
{creatable ? (
<ReactSelect.Creatable {...this.props}
value={value}
multi={true}
valueRenderer={this.valueRenderer}
valueComponent={DraggableItemWrap} />
) : (
<ReactSelect {...this.props}
value={value}
multi={true}
valueRenderer={this.valueRenderer}
valueComponent={DraggableItemWrap} />
)}
</DraggableList>
);
}
}
InputDraggableSelect.propTypes = {
updateOrder: PT.func.isRequired, // Changes order of sort
onChange: PT.func.isRequired, // Change value
value: PT.array,
creatable: PT.bool,
};
export default InputDraggableSelect;
{
"dependencies": {
"prop-types": "^15.5.4",
"react": "^15.5.4",
"react-dom": "^15.5.4",
"react-input-autosize": "1.1.0",
"react-select": "^1.0.0-rc.3",
"react-sortable-hoc": "^0.6.3"
}
}
// Item while dragging
.draggable-dragging {
box-shadow: 0 5px 5px -5px rgba(0, 0, 0, 0.2), 0 -5px 5px -5px rgba(0, 0, 0, 0.2);
background-color: rgba(255, 255, 255, 0.8);
cursor: row-resize;
z-index: 10;
* {
-webkit-touch-callout: none;
user-select: none;
}
label, p {
text-align: left;
}
}
// Replicate styles for the dragging state
$select-item-border-radius: 2px !default;
$select-item-gutter: 5px !default;
$select-item-padding-vertical: 2px !default;
$select-item-padding-horizontal: 5px !default;
$select-item-font-size: .9em !default;
$select-item-color: #08c !default; // pale blue
$select-item-bg: #f2f9fc !default;
$select-item-border-color: darken($select-item-bg, 10%) !default;
$select-item-hover-color: darken($select-item-color, 5%) !default; // pale blue
$select-item-hover-bg: darken($select-item-bg, 5%) !default;
$select-item-disabled-color: #333 !default;
$select-item-disabled-bg: #fcfcfc !default;
$select-item-disabled-border-color: darken($select-item-disabled-bg, 10%) !default;
.Select-value {
&.draggable-dragging {
background-color: $select-item-bg;
border-radius: $select-item-border-radius;
border: 1px solid $select-item-border-color;
color: $select-item-color;
display: inline-block;
font-size: $select-item-font-size;
line-height: 1.4;
margin-left: $select-item-gutter;
margin-top: $select-item-gutter;
vertical-align: top;
// common
.Select-value-icon,
.Select-value-label {
display: inline-block;
vertical-align: middle;
box-sizing: border-box;
float: left;
}
// label
.Select-value-label {
border-bottom-right-radius: $select-item-border-radius;
border-top-right-radius: $select-item-border-radius;
cursor: default;
padding: $select-item-padding-vertical 0 $select-item-padding-vertical $select-item-padding-horizontal;
}
// icon
.Select-value-icon {
cursor: pointer;
border-bottom-left-radius: $select-item-border-radius;
border-top-left-radius: $select-item-border-radius;
border-right: 1px solid $select-item-border-color;
// move the baseline up by 1px
padding: ($select-item-padding-vertical - 1) $select-item-padding-horizontal ($select-item-padding-vertical + 1);
&:hover,
&:focus {
background-color: $select-item-hover-bg;
color: $select-item-hover-color;
}
&:active {
background-color: $select-item-border-color;
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment