From 0c48f3d1149fb9954f9234c7cda61e52865f084e Mon Sep 17 00:00:00 2001 From: Javi Velasco Date: Wed, 9 Sep 2015 02:16:05 +0200 Subject: [PATCH] Port slider to ES6 --- components/slider/__tests__/index.spec.cjsx | 149 ---------- components/slider/__tests__/index.spec.jsx | 187 +++++++++++++ components/slider/index.cjsx | 245 ---------------- components/slider/index.jsx | 295 ++++++++++++++++++++ components/utils/index.js | 8 + 5 files changed, 490 insertions(+), 394 deletions(-) delete mode 100644 components/slider/__tests__/index.spec.cjsx create mode 100644 components/slider/__tests__/index.spec.jsx delete mode 100644 components/slider/index.cjsx create mode 100644 components/slider/index.jsx diff --git a/components/slider/__tests__/index.spec.cjsx b/components/slider/__tests__/index.spec.cjsx deleted file mode 100644 index 3b96ece30..000000000 --- a/components/slider/__tests__/index.spec.cjsx +++ /dev/null @@ -1,149 +0,0 @@ -TestUtils = React.addons.TestUtils -expect = require('expect') -sinon = require('sinon') -utils = require('../../utils/testing') -ProgressBar = require('../../progress_bar') -Input = require('../../input') -Slider = require('../index') - -describe 'Slider', -> - describe '#positionToValue', -> - before -> - props = { min: -500, max: 500 } - state = { sliderStart: 500, sliderLength: 100 } - @slider = utils.renderComponent(Slider, props, state) - - it 'returns min when position is less than origin', -> - expect(@slider.positionToValue({x: 400})).toEqual(-500) - - it 'returns max when position is more and origin plus length', -> - expect(@slider.positionToValue({x: 900})).toEqual(500) - - it 'returns the proper position when the position is inside slider', -> - expect(@slider.positionToValue({x: 520})).toEqual(-300) - - describe '#endPositionToValue', -> - before -> - props = { min: -500, max: 500 } - state = { sliderStart: 500, sliderLength: 100, startPosition: 520, startValue: -300 } - @slider = utils.renderComponent(Slider, props, state) - - it 'returns the proper value when is moved left', -> - expect(@slider.endPositionToValue({x: 510})).toEqual(-400) - - it 'returns the proper value when is moved right', -> - expect(@slider.endPositionToValue({x: 570})).toEqual(200) - - it 'returns the proper value when is not moved', -> - expect(@slider.endPositionToValue({x: 520})).toEqual(-300) - - describe '#trimValue', -> - before -> - props = { min: 0, max: 100, step: 0.1 } - @slider = utils.renderComponent(Slider, props) - - it 'rounds to the proper number', -> - expect(@slider.trimValue(57.16)).toEqual(57.2) - expect(@slider.trimValue(57.12)).toEqual(57.10) - - it 'returns min if number is less than min', -> - expect(@slider.trimValue(-57.16)).toEqual(0) - - it 'returns max if number is more than max', -> - expect(@slider.trimValue(257.16)).toEqual(100) - - describe '#valueForInput', -> - before -> - props = { min: 0, max: 100, step: 0.01 } - @slider = utils.renderComponent(Slider, props) - - it 'returns a fixed number when an integer is given', -> - expect(@slider.valueForInput(4)).toEqual('4.00') - - it 'returns a fixed number when a float is given', -> - expect(@slider.valueForInput(4.06)).toEqual('4.06') - - describe '#calculateKnobOffset', -> - it 'returns the corresponding offset for a given value and slider length/start', -> - props = { min: -500, max: 500, value: -250 } - state = { sliderStart: 500, sliderLength: 100 } - slider = utils.renderComponent(Slider, props, state) - expect(slider.calculateKnobOffset()).toEqual(25) - - describe '#getValue', -> - it 'retrieves the current value', -> - slider = utils.renderComponent(Slider, {value: 10}) - expect(slider.getValue()).toEqual(slider.state.value) - - describe '#setValue', -> - it 'set the current value', -> - slider = utils.renderComponent(Slider, {value: 10}) - slider.setValue(50) - expect(slider.state.value).toEqual(50) - - describe '#render', -> - it "contains a linear progress bar with proper properties", -> - slider = utils.renderComponent(Slider, {min: 100, max: 1000, value: 140}) - progress = TestUtils.findRenderedComponentWithType(slider, ProgressBar) - expect(progress.props.mode).toEqual('determinate') - expect(progress.props.type).toEqual('linear') - expect(progress.props.value).toEqual(140) - expect(progress.props.min).toEqual(100) - expect(progress.props.max).toEqual(1000) - - it "contains an input component if its editable", -> - slider = utils.renderComponent(Slider, {editable: true, value: 130}) - input = TestUtils.findRenderedComponentWithType(slider, Input) - expect(input.props.value).toEqual(slider.props.value) - - it "contains the proper number of snaps when snapped", -> - slider = utils.renderComponent(Slider, {snaps: true, step: 10}) - snaps = slider.refs.snaps - expect(snaps.props.children.length).toEqual(10) - - it "has the proper classes for pinned, editable and ring", -> - slider = utils.shallowRenderComponent(Slider, {editable: true, pinned: true}) - expect(slider.props.className).toContain("ring") - expect(slider.props.className).toContain("pinned") - slider = utils.shallowRenderComponent(Slider, {editable: true, value: 50}) - expect(slider.props.className).toNotContain("ring") - - describe 'events', -> - before -> - props = { min: -500, max: 500 } - state = { sliderStart: 0, sliderLength: 1000 } - @slider = utils.renderComponent(Slider, props, state) - - it "sets pressed state when knob is clicked", -> - TestUtils.Simulate.mouseDown(@slider.refs.knob) - expect(@slider.state.pressed).toEqual(true) - - it "sets pressed state when knob is touched", -> - TestUtils.Simulate.touchStart(@slider.refs.knob, {touches: [{pageX: 200}]}) - expect(@slider.state.pressed).toEqual(true) - - it "sets a proper value when the slider is clicked", -> - TestUtils.Simulate.mouseDown(@slider.refs.slider, { pageX: 200 }) - expect(@slider.state.value).toEqual(-300) - - it "sets a proper value when the slider is touched", -> - TestUtils.Simulate.touchStart(@slider.refs.slider, {touches: [{pageX: 200, pageY: 0}]}) - expect(@slider.state.value).toEqual(-300) - - it "changes its value when input changes", -> - slider = utils.renderComponent(Slider, {editable: true, value: 50}) - input = TestUtils.findRenderedComponentWithType(slider, Input) - TestUtils.Simulate.change(input.refs.input, {target: {value: '80'}}) - expect(slider.state.value).toEqual(80) - - it "changes input value when slider changes", -> - slider = utils.renderComponent(Slider, {editable: true}, {sliderStart: 0, sliderLength: 1000}) - input = TestUtils.findRenderedComponentWithType(slider, Input) - TestUtils.Simulate.mouseDown(slider.refs.slider, { pageX: 900 }) - expect(input.state.value).toEqual(90) - - it "calls onChange callback when the value is changed", -> - onChangeSpy = sinon.spy() - slider = utils.renderComponent(Slider, {onChange: onChangeSpy}, {sliderStart: 0, sliderLength: 1000}) - TestUtils.Simulate.mouseDown(slider.refs.slider, { pageX: 900 }) - expect(onChangeSpy.called).toEqual(true) diff --git a/components/slider/__tests__/index.spec.jsx b/components/slider/__tests__/index.spec.jsx new file mode 100644 index 000000000..c4491d2e7 --- /dev/null +++ b/components/slider/__tests__/index.spec.jsx @@ -0,0 +1,187 @@ +const React = window.React; +const TestUtils = React.addons.TestUtils; +const expect = require('expect'); +const sinon = require('sinon'); +const utils = require('../../utils/testing'); + +const ProgressBar = require('../../progress_bar'); +const Input = require('../../input'); +const Slider = require('../index'); + +describe('Slider', function () { + let props, state, slider, progress, input; + + describe('#positionToValue', function () { + before(function () { + props = { min: -500, max: 500 }; + state = { sliderStart: 500, sliderLength: 100 }; + slider = utils.renderComponent(Slider, props, state); + }); + + it('returns min when position is less than origin', function () { + expect(slider.positionToValue({x: 400})).toEqual(-500); + }); + + it('returns max when position is more and origin plus length', function () { + expect(slider.positionToValue({x: 900})).toEqual(500); + }); + + it('returns the proper position when the position is inside slider', function () { + expect(slider.positionToValue({x: 520})).toEqual(-300); + }); + }); + + describe('#endPositionToValue', function () { + before(function () { + props = { min: -500, max: 500 }; + state = { sliderStart: 500, sliderLength: 100, startPosition: 520, startValue: -300 }; + slider = utils.renderComponent(Slider, props, state); + }); + + it('returns the proper value when is moved left', function () { + expect(slider.endPositionToValue({x: 510})).toEqual(-400); + }); + + it('returns the proper value when is moved right', function () { + expect(slider.endPositionToValue({x: 570})).toEqual(200); + }); + + it('returns the proper value when is not moved', function () { + expect(slider.endPositionToValue({x: 520})).toEqual(-300); + }); + }); + + describe('#trimValue', function () { + before(function () { + props = { min: 0, max: 100, step: 0.1 }; + slider = utils.renderComponent(Slider, props); + }); + + it('rounds to the proper number', function () { + expect(slider.trimValue(57.16)).toEqual(57.2); + expect(slider.trimValue(57.12)).toEqual(57.10); + }); + + it('returns min if number is less than min', function () { + expect(slider.trimValue(-57.16)).toEqual(0); + }); + + it('returns max if number is more than max', function () { + expect(slider.trimValue(257.16)).toEqual(100); + }); + }); + + describe('#valueForInput', function () { + before(function () { + props = { min: 0, max: 100, step: 0.01 }; + slider = utils.renderComponent(Slider, props); + }); + + it('returns a fixed number when an integer is given', function () { + expect(slider.valueForInput(4)).toEqual('4.00'); + }); + + it('returns a fixed number when a float is given', function () { + expect(slider.valueForInput(4.06)).toEqual('4.06'); + }); + }); + + describe('#calculateKnobOffset', function () { + it('returns the corresponding offset for a given value and slider length/start', function () { + props = { min: -500, max: 500, value: -250 }; + state = { sliderStart: 500, sliderLength: 100 }; + slider = utils.renderComponent(Slider, props, state); + expect(slider.calculateKnobOffset()).toEqual(25); + }); + }); + + describe('#getValue', function () { + it('retrieves the current value', function () { + slider = utils.renderComponent(Slider, {value: 10}); + expect(slider.getValue()).toEqual(slider.state.value); + }); + }); + + describe('#setValue', function () { + it('set the current value', function () { + slider = utils.renderComponent(Slider, {value: 10}); + slider.setValue(50); + expect(slider.state.value).toEqual(50); + }); + }); + + describe('#render', function () { + it('contains a linear progress bar with proper properties', function () { + slider = utils.renderComponent(Slider, {min: 100, max: 1000, value: 140}); + progress = TestUtils.findRenderedComponentWithType(slider, ProgressBar); + expect(progress.props.mode).toEqual('determinate'); + expect(progress.props.type).toEqual('linear'); + expect(progress.props.value).toEqual(140); + expect(progress.props.min).toEqual(100); + expect(progress.props.max).toEqual(1000); + }); + + it('contains an input component if its editable', function () { + slider = utils.renderComponent(Slider, {editable: true, value: 130}); + input = TestUtils.findRenderedComponentWithType(slider, Input); + expect(input.props.value).toEqual(slider.props.value); + }); + + it('contains the proper number of snaps when snapped', function () { + slider = utils.shallowRenderComponent(Slider, {editable: true, pinned: true}); + expect(slider.props.className).toContain('ring'); + expect(slider.props.className).toContain('pinned'); + slider = utils.shallowRenderComponent(Slider, {editable: true, value: 50}); + expect(slider.props.className).toNotContain('ring'); + }); + }); + + describe('#events', function () { + before(function () { + props = { min: -500, max: 500 }; + state = { sliderStart: 0, sliderLength: 1000 }; + slider = utils.renderComponent(Slider, props, state); + }); + + it('sets pressed state when knob is clicked', function () { + TestUtils.Simulate.mouseDown(slider.refs.knob); + expect(slider.state.pressed).toEqual(true); + }); + + it('sets pressed state when knob is touched', function () { + TestUtils.Simulate.touchStart(slider.refs.knob, {touches: [{pageX: 200}]}); + expect(slider.state.pressed).toEqual(true); + }); + + it('sets a proper value when the slider is clicked', function () { + TestUtils.Simulate.mouseDown(slider.refs.slider, { pageX: 200 }); + expect(slider.state.value).toEqual(-300); + }); + + it('sets a proper value when the slider is touched', function () { + TestUtils.Simulate.touchStart(slider.refs.slider, {touches: [{pageX: 200, pageY: 0}]}); + expect(slider.state.value).toEqual(-300); + }); + + it('changes its value when input changes', function () { + slider = utils.renderComponent(Slider, {editable: true, value: 50}); + input = TestUtils.findRenderedComponentWithType(slider, Input); + TestUtils.Simulate.change(input.refs.input, {target: {value: '80'}}); + expect(slider.state.value).toEqual(80); + }); + + it('changes input value when slider changes', function () { + slider = utils.renderComponent(Slider, {editable: true}, {sliderStart: 0, sliderLength: 1000}); + input = TestUtils.findRenderedComponentWithType(slider, Input); + TestUtils.Simulate.mouseDown(slider.refs.slider, { pageX: 900 }); + expect(input.state.value).toEqual(90); + }); + + it('calls onChange callback when the value is changed', function () { + let onChangeSpy = sinon.spy(); + slider = utils.renderComponent(Slider, {onChange: onChangeSpy}, {sliderStart: 0, sliderLength: 1000}); + TestUtils.Simulate.mouseDown(slider.refs.slider, { pageX: 900 }); + expect(onChangeSpy.called).toEqual(true); + }); + }); +}); diff --git a/components/slider/index.cjsx b/components/slider/index.cjsx deleted file mode 100644 index 4ac962412..000000000 --- a/components/slider/index.cjsx +++ /dev/null @@ -1,245 +0,0 @@ -localCSS = require './style' -prefixer = require "../utils/prefixer" -ProgressBar = require "../progress_bar" -Input = require "../input" - -module.exports = React.createClass - - # -- States & Properties - propTypes: - className : React.PropTypes.string - editable : React.PropTypes.bool - max : React.PropTypes.number - min : React.PropTypes.number - onChange : React.PropTypes.func - pinned : React.PropTypes.bool - snaps : React.PropTypes.bool - step : React.PropTypes.number - value : React.PropTypes.number - - getDefaultProps: -> - className : "" - editable : false - max : 100 - min : 0 - pinned : false - snaps : false - step : 0.01 - value : 0 - - getInitialState: -> - sliderStart : 0 - sliderLength : 0 - value : @props.value - - # -- Lifecycle - componentDidMount: -> - @onResize() - window.addEventListener('resize', @onResize) - - componentWillUnmount: -> - window.removeEventListener('resize', @onResize) - - componentDidUpdate: (prevProps, prevState) -> - if prevState.value != @state.value - @props.onChange? @ - if @state.value != parseFloat(@refs.input?.getValue()) - @refs.input?.setValue(@valueForInput(@state.value)) - - # -- Events - onResize: (event) -> - sliderBounds = @refs.progressbar.getDOMNode().getBoundingClientRect() - @setState - sliderStart: sliderBounds['left'], - sliderLength: (sliderBounds['right'] - sliderBounds['left']) - - onSliderMouseDown: (event) -> - position = _getMousePosition(event) - value = @positionToValue(position) - @setState value: value, => - @start(position) - _addEventsToDocument(@getMouseEventMap()) - _pauseEvent(event) - - onSliderTouchStart: (event) -> - position = _getTouchPosition(event) - value = @positionToValue(position) - @setState value: value, => - @start(position) - _addEventsToDocument(@getTouchEventMap()) - _pauseEvent(event) - - onSliderFocus: (event) -> - _addEventsToDocument(@getKeyboardEvents()) - - onSliderBlur: (event) -> - _removeEventsFromDocument(@getKeyboardEvents()) - - onInputChange: (event) -> - @setState value: @trimValue(event.target.value) - - onKeyDown: (event) -> - event.stopPropagation() - @getDOMNode().blur() if event.keyCode in [13, 27] - @addToValue(@props.step) if event.keyCode == 38 - @addToValue(-@props.step) if event.keyCode == 40 - - onMouseDown: (event) -> - @start(_getMousePosition(event)) - _addEventsToDocument(@getMouseEventMap()) - - onTouchStart: (event) -> - event.stopPropagation() - @start(_getTouchPosition(event)) - _addEventsToDocument(@getTouchEventMap()) - - onMouseMove: (event) -> - _pauseEvent(event) - @move(_getMousePosition(event)) - - onTouchMove: (event) -> - @move(_getTouchPosition(event)) - - onMouseUp: -> - @end(@getMouseEventMap()) - - onTouchEnd: -> - @end(@getTouchEventMap()) - - # -- Internal methods - getMouseEventMap: -> - mousemove: @onMouseMove - mouseup: @onMouseUp - - getTouchEventMap: -> - touchmove: @onTouchMove - touchend: @onTouchEnd - - getKeyboardEvents: -> - keydown: @onKeyDown - - positionToValue: (position) -> - offset = position.x - @state.sliderStart - @trimValue(offset / @state.sliderLength * (@props.max - @props.min) + @props.min) - - start: (position) -> - @setState - pressed: true - startPosition: position.x - startValue: @state.value - - move: (position) -> - value = @endPositionToValue(position) - @setState value: value - - end: (events) -> - _removeEventsFromDocument(events) - @setState pressed: false - - endPositionToValue: (position) -> - offset = position.x - @state.startPosition - diffValue = offset / @state.sliderLength * (@props.max - @props.min) - @trimValue(diffValue + @state.startValue) - - trimValue: (value) -> - value = @props.min if (value < @props.min) - value = @props.max if (value > @props.max) - _round(value, @stepDecimals()) - - stepDecimals: -> - (@props.step.toString().split('.')[1] || []).length - - addToValue: (value) -> - @setState value: @trimValue(@state.value + value) - - valueForInput: (value) -> - decimals = @stepDecimals() - if decimals > 0 then value.toFixed(decimals) else value.toString() - - calculateKnobOffset: -> - @state.sliderLength * (@state.value - @props.min) / (@props.max - @props.min) - - render: -> - className = @props.className - className += " editable" if @props.editable - className += " pinned" if @props.pinned - className += " pressed" if @state.pressed - className += " ring" if @state.value == @props.min - knobStyles = prefixer(transform: "translateX(#{@calculateKnobOffset()}px)") - -
- -
- -
-
-
- -
- - { - if @props.snaps -
- { - for i in [1..((@props.max - @props.min) / @props.step)] -
- } -
- } -
-
- - { - if @props.editable - - } -
- - # -- Extends - getValue: -> - @state.value - - setValue: (value) -> - @setState value: value - -# -- Private methods -_pauseEvent = (event) -> - event.stopPropagation() - event.preventDefault() - event.returnValue = false - event.cancelBubble = true - return null - -_getMousePosition = (event) -> - x: event.pageX - y: event.pageY - -_getTouchPosition = (event) -> - x: event.touches[0]['pageX'] - y: event.touches[0]['pageY'] - -_addEventsToDocument = (events) -> - document.addEventListener(key, events[key], false) for key of events - -_removeEventsFromDocument = (events) -> - document.removeEventListener(key, events[key], false) for key of events - -_round = (n, decimals) -> - if (!isNaN(parseFloat(n)) && isFinite(n)) - decimalPower = Math.pow(10, decimals) - return Math.round(parseFloat(n) * decimalPower) / decimalPower - return NaN diff --git a/components/slider/index.jsx b/components/slider/index.jsx new file mode 100644 index 000000000..92d3fb9ad --- /dev/null +++ b/components/slider/index.jsx @@ -0,0 +1,295 @@ +const React = window.React; +const css = require('./style'); +const utils = require('../utils'); + +const ProgressBar = require('../progress_bar'); +const Input = require('../input'); + +module.exports = React.createClass({ + displayName: 'Slider', + + propTypes: { + className: React.PropTypes.string, + editable: React.PropTypes.bool, + max: React.PropTypes.number, + min: React.PropTypes.number, + onChange: React.PropTypes.func, + pinned: React.PropTypes.bool, + snaps: React.PropTypes.bool, + step: React.PropTypes.number, + value: React.PropTypes.number + }, + + getDefaultProps () { + return { + className: '', + editable: false, + max: 100, + min: 0, + pinned: false, + snaps: false, + step: 0.01, + value: 0 + }; + }, + + getInitialState () { + return { + sliderStart: 0, + sliderLength: 0, + value: this.props.value + }; + }, + + componentDidMount () { + window.addEventListener('resize', this.onResize); + this.onResize(); + }, + + componentWillUnmount () { + window.removeEventListener('resize', this.onResize); + }, + + componentDidUpdate (prevProps, prevState) { + if (prevState.value !== this.state.value && this.props.onChange) { + this.props.onChange(this); + } + + if (this.refs.input) { + const inputValue = parseFloat(this.refs.input.getValue()); + if (this.state.value !== inputValue) { + this.refs.input.setValue(this.valueForInput(this.state.value)); + } + } + }, + + onResize () { + const bounds = this.refs.progressbar.getDOMNode().getBoundingClientRect(); + this.setState({ + sliderStart: bounds.left, + sliderLength: bounds.right - bounds.left + }); + }, + + onSliderMouseDown (event) { + const position = utils.events.getMousePosition(event); + const value = this.positionToValue(position); + this.setState({value: value}, (function () { + this.start(position); + utils.events.addEventsToDocument(this.getTouchEventMap()); + }).bind(this)); + utils.events.pauseEvent(event); + }, + + onSliderTouchStart (event) { + const position = utils.events.getTouchPosition(event); + const value = this.positionToValue(position); + this.setState({value: value}, (function () { + this.start(position); + utils.events.addEventsToDocument(this.getTouchEventMap()); + }).bind(this)); + utils.events.pauseEvent(event); + }, + + onSliderFocus () { + utils.events.addEventsToDocument(this.getKeyboardEvents()); + }, + + onSliderBlur () { + utils.events.removeEventsFromDocument(this.getKeyboardEvents()); + }, + + onInputChange (event) { + this.setState({value: this.trimValue(event.target.value) }); + }, + + onKeyDown (event) { + event.stopPropagation(); + if (event.keyCode in [13, 27]) this.getDOMNode().blur(); + if (event.keyCode === 38) this.addToValue(this.props.step); + if (event.keyCode === 40) this.addToValue(-this.props.step); + }, + + onMouseDown (event) { + this.start(utils.events.getMousePosition(event)); + utils.events.addEventsToDocument(this.getMouseEventMap()); + }, + + onTouchStart (event) { + event.stopPropagation(); + this.start(utils.events.getTouchPosition(event)); + utils.events.addEventsToDocument(this.getTouchEventMap()); + }, + + onMouseMove (event) { + utils.events.pauseEvent(event); + this.move(utils.events.getMousePosition(event)); + }, + + onTouchMove (event) { + this.move(utils.events.getTouchPosition(event)); + }, + + onMouseUp () { + this.end(this.getMouseEventMap()); + }, + + onTouchEnd () { + this.end(this.getTouchEventMap()); + }, + + getMouseEventMap () { + return { + mousemove: this.onMouseMove, + mouseup: this.onMouseUp + }; + }, + + getTouchEventMap () { + return { + touchmove: this.onTouchMove, + touchend: this.onTouchEnd + }; + }, + + getKeyboardEvents () { + return { + keydown: this.onKeyDown + }; + }, + + positionToValue (position) { + const offset = position.x - this.state.sliderStart; + return this.trimValue(offset / this.state.sliderLength * (this.props.max - this.props.min) + this.props.min); + }, + + start (position) { + this.setState({ + pressed: true, + startPosition: position.x, + startValue: this.state.value + }); + }, + + move (position) { + const value = this.endPositionToValue(position); + this.setState({value: value}); + }, + + end (revents) { + utils.events.removeEventsFromDocument(revents); + this.setState({pressed: false}); + }, + + endPositionToValue (position) { + const offset = position.x - this.state.startPosition; + const diffValue = offset / this.state.sliderLength * (this.props.max - this.props.min); + return this.trimValue(diffValue + this.state.startValue); + }, + + trimValue (value) { + if (value < this.props.min) return this.props.min; + if (value > this.props.max) return this.props.max; + return utils.round(value, this.stepDecimals()); + }, + + stepDecimals () { + return (this.props.step.toString().split('.')[1] || []).length; + }, + + addToValue (value) { + this.setState({ + value: this.trimValue(this.state.value + value) + }); + }, + + valueForInput (value) { + const decimals = this.stepDecimals(); + return decimals > 0 ? value.toFixed(decimals) : value.toString(); + }, + + calculateKnobOffset () { + return this.state.sliderLength * (this.state.value - this.props.min) / (this.props.max - this.props.min); + }, + + renderSnaps () { + if (this.props.snaps) { + return ( +
+ { + utils.range(0, (this.props.max - this.props.min) / this.props.step).map(i => { + return (
); + }) + } +
+ ); + } + }, + + renderInput () { + if (this.props.editable) { + return ( + + ); + } + }, + + render () { + let knobStyles = utils.prefixer({transform: `translateX(${this.calculateKnobOffset()}px)`}); + let className = this.props.className; + if (this.props.editable) className += ' editable'; + if (this.props.pinned) className += ' pinned'; + if (this.state.pressed) className += ' pressed'; + if (this.state.value === this.props.min) className += ' ring'; + + return ( +
+ +
+ +
+
+
+ +
+ + { this.renderSnaps() } +
+
+ + { this.renderInput() } +
+ ); + }, + + getValue () { + return this.state.value; + }, + + setValue (value) { + this.setState({value: value}); + } +}); diff --git a/components/utils/index.js b/components/utils/index.js index db3c3379d..ae274004d 100644 --- a/components/utils/index.js +++ b/components/utils/index.js @@ -22,6 +22,14 @@ module.exports = { return range; }, + round (number, decimals) { + if (!isNaN(parseFloat(number)) && isFinite(number)) { + let decimalPower = Math.pow(10, decimals); + return Math.round(parseFloat(number) * decimalPower) / decimalPower; + } + return NaN; + }, + events: require('./events'), prefixer: require('./prefixer'), time: require('./time'),