Skip to content

Instantly share code, notes, and snippets.

@SPY
Last active March 4, 2016 10:01
Show Gist options
  • Save SPY/91dece9f04043abbc6e6 to your computer and use it in GitHub Desktop.
Save SPY/91dece9f04043abbc6e6 to your computer and use it in GitHub Desktop.
import * as React from 'react'
interface Point {
x: number,
y: number
}
interface DraggableState {
dragStartPoint?: Point
}
export interface DraggableCallback {
(props: any, prevPoint: Point, nextPoint: Point): any
}
export interface DraggableProps {
startDrag?: (ev: React.MouseEvent) => any
}
export default function draggable(callback: DraggableCallback) {
return (Component: typeof React.Component): any => {
return class Draggable extends React.Component<any, DraggableState> {
static displayName: string = `Dragable(${(Component as any).displayName})`
onMouseMoveFn: (ev: MouseEvent) => any
onMouseUpFn: (ev: MouseEvent) => any
onMouseDownFn: (ev: React.MouseEvent) => any
constructor(props: any) {
super(props)
this.onMouseMoveFn = this.onMouseMove.bind(this)
this.onMouseUpFn = this.onMouseUp.bind(this)
this.onMouseDownFn = this.onMouseDown.bind(this)
this.state = { dragStartPoint: null }
}
onMouseDown(ev: React.MouseEvent) {
this.setState({ dragStartPoint: { x: ev.screenX, y: ev.screenY }})
document.body.addEventListener('mousemove', this.onMouseMoveFn, false)
document.body.addEventListener('mouseup', this.onMouseUpFn, false)
}
unsubscribe() {
document.body.removeEventListener('mousemove', this.onMouseMoveFn, false)
document.body.removeEventListener('mouseup', this.onMouseUpFn, false)
}
onMouseMove(ev: MouseEvent) {
if (!this.state.dragStartPoint) {
return
}
const nextPoint = { x: ev.screenX, y: ev.screenY }
callback(this.props, this.state.dragStartPoint, nextPoint)
this.setState({ dragStartPoint: nextPoint })
}
onMouseUp(ev: MouseEvent) {
this.unsubscribe()
this.setState({ dragStartPoint: null })
}
componentWillUnmount() {
this.unsubscribe()
}
render() {
const props = Object.assign({}, this.props, {
startDrag: this.onMouseDownFn
})
return <Component {...props} />
}
}
}
}
.paperify() {
box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23);
}
.circle(@radius) {
width: 2*@radius;
height: 2*@radius;
border-radius: @radius;
}
.slider {
display: inline-block;
position: relative;
height: 30px;
-webkit-user-select: none;
&__scale {
margin-top: 15px;
border-bottom: 1px solid #bbb;
}
&__value {
position: absolute;
top: 10px;
background-color: white;
cursor: pointer;
.circle(5px);
.paperify();
}
}
import * as React from 'react'
import draggable, {DraggableProps} from './Draggable'
import './Slider.less'
export interface SliderProps extends React.Props<Slider>, DraggableProps {
from: number,
to: number,
width: number,
value: number,
className?: string,
onChange?: (newValue: number) => void
}
@draggable((props: SliderProps, prevPoint, nextPoint) => {
const {width, from, to, value} = props
const range = to - from
const delta = (nextPoint.x - prevPoint.x) / width * range
const newValue = Math.min(to, Math.max(value + delta, from))
const changed = Math.abs(Math.abs(value) - Math.abs(newValue)) > range / (width * 100)
if (changed && props.onChange) {
props.onChange(newValue)
}
})
export default class Slider extends React.Component<SliderProps, {}> {
render() {
const {from, to, value, width} = this.props
const left = Math.round(width * (value - from) / (to - from))
return (
<div className={['slider', this.props.className || ''].join(' ')} style={{ width }}>
<div className='slider__scale' />
<div className='slider__value' style={{ left }} onMouseDown={this.props.startDrag} />
</div>
)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment