From 938530b67b09223d610de23d79bc50ff89e4e8c7 Mon Sep 17 00:00:00 2001 From: Artemis330 Date: Fri, 1 Apr 2016 16:40:36 +0800 Subject: [PATCH] feat: vertical slider - add `vertical` props - add vertical slider demo: v-marks.html v-range.html v-slider.html refs:https://github.com/react-component/slider/issues/18 --- HISTORY.md | 5 ++ README.md | 6 +++ assets/index.less | 41 ++++++++++++++- examples/v-marks.html | 1 + examples/v-marks.js | 53 +++++++++++++++++++ examples/v-range.html | 0 examples/v-range.js | 117 +++++++++++++++++++++++++++++++++++++++++ examples/v-slider.html | 1 + examples/v-slider.js | 106 +++++++++++++++++++++++++++++++++++++ package.json | 2 +- src/Handle.jsx | 5 +- src/Marks.jsx | 14 +++-- src/Slider.jsx | 45 +++++++++------- src/Steps.jsx | 10 ++-- src/Track.jsx | 11 ++-- tests/index.spec.js | 7 ++- 16 files changed, 388 insertions(+), 36 deletions(-) create mode 100644 examples/v-marks.html create mode 100644 examples/v-marks.js create mode 100644 examples/v-range.html create mode 100644 examples/v-range.js create mode 100644 examples/v-slider.html create mode 100644 examples/v-slider.js diff --git a/HISTORY.md b/HISTORY.md index d14150d..f47f001 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,6 +1,11 @@ # History ---- +## 3.5.2 / 2016-04-01 + +[#18](https://github.com/react-component/slider/issues/18) add `vertical` props ([@wnlee](https://github.com/WNLee)) + +... ## 1.2.5 / 2015-07-13 diff --git a/README.md b/README.md index d1bc197..0b7791c 100644 --- a/README.md +++ b/README.md @@ -112,6 +112,12 @@ ReactDOM.render(, container); true When `range` is `true`, `allowCross` could be set as `true` to allow those two handles cross. + + vertical + boolean + false + If vertical is `true`, the slider will be vertical. + defaultValue number or [number, number] diff --git a/assets/index.less b/assets/index.less index 66717e3..6da4b28 100644 --- a/assets/index.less +++ b/assets/index.less @@ -94,7 +94,7 @@ &-dot { position: absolute; - top: -2px; + bottom: -2px; margin-left: -4px; width: 8px; height: 8px; @@ -133,6 +133,45 @@ } } +.rc-slider-vertical { + width: 4px; + height: 100%; + .@{prefixClass} { + &-track { + bottom: 0; + width: 4px; + } + + &-handle { + position: absolute; + margin-left: -5px; + margin-bottom: -7px; + } + + &-mark { + top: 0; + left: 10px; + height: 100%; + } + + &-step { + height: 100%; + width: 4px; + } + + &-dot { + left: 2px; + margin-bottom: -4px; + &:first-child { + margin-bottom: -4px; + } + &:last-child { + margin-bottom: -4px; + } + } + } +} + .motion-common() { animation-duration: .3s; animation-fill-mode: both; diff --git a/examples/v-marks.html b/examples/v-marks.html new file mode 100644 index 0000000..48cdce8 --- /dev/null +++ b/examples/v-marks.html @@ -0,0 +1 @@ +placeholder diff --git a/examples/v-marks.js b/examples/v-marks.js new file mode 100644 index 0000000..05358c6 --- /dev/null +++ b/examples/v-marks.js @@ -0,0 +1,53 @@ +require('rc-slider/assets/index.less'); + +const React = require('react'); +const ReactDOM = require('react-dom'); +const Slider = require('rc-slider'); + +const style = {height: 400, marginBottom: 50, marginLeft: 50}; +const marks = { + '-10': '-10°C', + 0: 0°C, + 26: '26°C', + 37: '37°C', + 50: '50°C', + 100: { + style: { + color: 'red', + }, + label: 100°C, + }, +}; + +function log(value) { + console.log(value); +} + +ReactDOM.render( +
+

Slider with marks, `step=null`

+
+ +
+

Slider with marks and steps

+
+ +
+

Slider with marks, `included=false`

+
+ +
+

Slider with marks and steps, `included=false`

+
+ +
+

Range with marks

+
+ +
+

Range with marks and steps

+
+ +
+
+ , document.getElementById('__react-content')); diff --git a/examples/v-range.html b/examples/v-range.html new file mode 100644 index 0000000..e69de29 diff --git a/examples/v-range.js b/examples/v-range.js new file mode 100644 index 0000000..5ecf9be --- /dev/null +++ b/examples/v-range.js @@ -0,0 +1,117 @@ +/* eslint react/no-multi-comp: 0 */ +require('rc-slider/assets/index.less'); + +const React = require('react'); +const ReactDOM = require('react-dom'); +const Slider = require('rc-slider'); + +const style = {height: 400, marginBottom: 50, marginLeft: 50}; + +function log(value) { + console.log(value); +} + +const CustomizedRange = React.createClass({ + getInitialState: function() { + return { + lowerBound: 20, + upperBound: 40, + value: [20, 40], + }; + }, + onLowerBoundChange: function(e) { + this.setState({ lowerBound: +e.target.value }); + }, + onUpperBoundChange: function(e) { + this.setState({ upperBound: +e.target.value }); + }, + onSliderChange: function(value) { + log(value); + this.setState({ + value: value, + }); + }, + handleApply: function() { + const { lowerBound, upperBound } = this.state; + this.setState({ value: [lowerBound, upperBound]}); + }, + render: function() { + return ( +
+ + +
+ + +
+ +

+ +
+ ); + }, +}); + +const DynamicBounds = React.createClass({ + getInitialState() { + return { + min: 0, + max: 100, + }; + }, + onSliderChange: function(value) { + log(value); + }, + onMinChange: function(e) { + this.setState({ + min: +e.target.value || 0, + }); + }, + onMaxChange: function(e) { + this.setState({ + max: +e.target.value || 100, + }); + }, + render: function() { + return ( +
+ + +
+ + +

+ +
+ ); + }, +}); + +ReactDOM.render( +
+

Basic Range,`allowCross=false`

+
+ +
+

Basic Range,`step=20`

+
+ +
+

Basic Range,`step=20, dots`

+
+ +
+

Controlled Range

+
+ +
+

Customized Range

+
+ +
+

Range with dynamic `max` `min`

+
+ +
+
+ , document.getElementById('__react-content')); diff --git a/examples/v-slider.html b/examples/v-slider.html new file mode 100644 index 0000000..b3a4252 --- /dev/null +++ b/examples/v-slider.html @@ -0,0 +1 @@ +placeholder \ No newline at end of file diff --git a/examples/v-slider.js b/examples/v-slider.js new file mode 100644 index 0000000..6db3f11 --- /dev/null +++ b/examples/v-slider.js @@ -0,0 +1,106 @@ +/* eslint react/no-multi-comp: 0 */ +require('rc-slider/assets/index.less'); + +const React = require('react'); +const ReactDOM = require('react-dom'); +const Slider = require('rc-slider'); + +const style = {height: 400, marginBottom: 50, marginLeft: 50}; + +function log(value) { + console.log(value); +} + + +function percentFormatter(v) { + return v + ' %'; +} + +const CustomizedSlider = React.createClass({ + getInitialState: function() { + return { + value: 50, + }; + }, + onSliderChange: function(value) { + log(value); + this.setState({ + value: value, + }); + }, + render: function() { + return ; + }, +}); + +const DynamicBounds = React.createClass({ + getInitialState: function() { + return { + min: 0, + max: 100, + }; + }, + onSliderChange: function(value) { + log(value); + }, + onMinChange: function(e) { + this.setState({ + min: +e.target.value || 0, + }); + }, + onMaxChange: function(e) { + this.setState({ + max: +e.target.value || 100, + }); + }, + render: function() { + return ( +
+ + +
+ + +

+ +
+ ); + }, +}); + +ReactDOM.render( +
+

Basic Slider

+
+ +
+

Basic Slider,`step=20`

+
+ +
+

Basic Slider,`step=20, dots`

+
+ +
+

Basic Slider with `tipFormatter`

+
+ +
+

Basic Slider without tooltip

+
+ +
+

Controlled Slider

+
+ +
+

Customized Slider

+
+ +
+

Slider with dynamic `min` `max`

+
+ +
+
+ , document.getElementById('__react-content')); diff --git a/package.json b/package.json index 7ea2174..c848d02 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "rc-slider", - "version": "3.5.1", + "version": "3.5.2", "description": "slider ui component for react", "keywords": [ "react", diff --git a/src/Handle.jsx b/src/Handle.jsx index a1ef234..fb54e42 100644 --- a/src/Handle.jsx +++ b/src/Handle.jsx @@ -24,10 +24,10 @@ export default class Handle extends React.Component { render() { const props = this.props; - const {className, tipTransitionName, tipFormatter, offset, value} = props; + const {className, tipTransitionName, tipFormatter, vertical, offset, value} = props; const {dragging, noTip} = props; - const style = { left: offset + '%' }; + const style = vertical ? { bottom: offset + '%' } : { left: offset + '%' }; const handle = (
{ +const Marks = ({className, vertical, marks, included, upperBound, lowerBound, max, min}) => { const marksKeys = Object.keys(marks); const marksCount = marksKeys.length; const unit = 100 / (marksCount - 1); @@ -16,11 +16,19 @@ const Marks = ({className, marks, included, upperBound, lowerBound, max, min}) = [className + '-text-active']: isActived, }); - const style = { + const bottomStyle = { + // height: markWidth + '%', + marginBottom: '-200' + '%', + bottom: (point - min) / range * 100 + '%', + }; + + const leftStyle = { width: markWidth + '%', marginLeft: -markWidth / 2 + '%', + left: (point - min) / range * 100 + '%', }; - style.left = (point - min) / range * 100 + '%'; + + const style = vertical ? bottomStyle : leftStyle; const markPoint = marks[point]; const markPointIsObject = typeof markPoint === 'object' && diff --git a/src/Slider.jsx b/src/Slider.jsx index e2aa6bb..991fc7b 100644 --- a/src/Slider.jsx +++ b/src/Slider.jsx @@ -13,12 +13,12 @@ function isNotTouchEvent(e) { return e.touches.length > 1 || (e.type.toLowerCase() === 'touchend' && e.touches.length > 0); } -function getTouchPosition(e) { - return e.touches[0].pageX; +function getTouchPosition(vertical, e) { + return vertical ? e.touches[0].clientY : e.touches[0].pageX; } -function getMousePosition(e) { - return e.pageX; +function getMousePosition(vertical, e) { + return vertical ? e.clientY : e.pageX; } function pauseEvent(e) { @@ -113,7 +113,7 @@ class Slider extends React.Component { } onMouseMove(e) { - const position = getMousePosition(e); + const position = getMousePosition(this.props.vertical, e); this.onMove(e, position); } @@ -123,7 +123,7 @@ class Slider extends React.Component { return; } - const position = getTouchPosition(e); + const position = getTouchPosition(this.props.vertical, e); this.onMove(e, position); } @@ -132,7 +132,8 @@ class Slider extends React.Component { const props = this.props; const state = this.state; - const diffPosition = position - this.startPosition; + let diffPosition = position - this.startPosition; + diffPosition = this.props.vertical ? -diffPosition : diffPosition; const diffValue = diffPosition / this.getSliderLength() * (props.max - props.min); const value = this.trimAlignValue(this.startValue + diffValue); @@ -164,14 +165,14 @@ class Slider extends React.Component { onTouchStart(e) { if (isNotTouchEvent(e)) return; - const position = getTouchPosition(e); + const position = getTouchPosition(this.props.vertical, e); this.onStart(position); this.addDocumentEvents('touch'); pauseEvent(e); } onMouseDown(e) { - const position = getMousePosition(e); + const position = getMousePosition(this.props.vertical, e); this.onStart(position); this.addDocumentEvents('mouse'); pauseEvent(e); @@ -229,14 +230,14 @@ class Slider extends React.Component { return 0; } - return slider.clientWidth; + return this.props.vertical ? slider.clientHeight : slider.clientWidth; } getSliderStart() { const slider = this.refs.slider; const rect = slider.getBoundingClientRect(); - return rect.left; + return this.props.vertical ? rect.top : rect.left; } getPrecision() { @@ -291,9 +292,10 @@ class Slider extends React.Component { } calcValue(offset) { - const {min, max} = this.props; - const ratio = offset / this.getSliderLength(); - return ratio * (max - min) + min; + const {vertical, min, max} = this.props; + const ratio = Math.abs(offset / this.getSliderLength()); + const value = vertical ? (1 - ratio) * (max - min) + min : ratio * (max - min) + min; + return value; } calcValueByPos(position) { @@ -331,7 +333,7 @@ class Slider extends React.Component { render() { const {handle, upperBound, lowerBound} = this.state; - const {className, prefixCls, disabled, dots, included, range, step, + const {className, prefixCls, disabled, vertical, dots, included, range, step, marks, max, min, tipTransitionName, tipFormatter, children} = this.props; const upperOffset = this.calcOffset(upperBound); @@ -342,19 +344,20 @@ class Slider extends React.Component { const upper = (); + vertical = {vertical} offset={upperOffset} value={upperBound} dragging={handle === 'upperBound'}/>); let lower = null; if (range) { lower = (); + vertical = {vertical} offset={lowerOffset} value={lowerBound} dragging={handle === 'lowerBound'}/>); } const sliderClassName = classNames({ [prefixCls]: true, [prefixCls + '-disabled']: disabled, [className]: !!className, + ['rc-slider-vertical']: this.props.vertical, }); const isIncluded = included || range; return ( @@ -363,12 +366,12 @@ class Slider extends React.Component { onMouseDown={disabled ? noop : this.onMouseDown.bind(this)}> {upper} {lower} - - - {children} @@ -402,6 +405,7 @@ Slider.propTypes = { tipFormatter: React.PropTypes.func, dots: React.PropTypes.bool, range: React.PropTypes.bool, + vertical: React.PropTypes.bool, allowCross: React.PropTypes.bool, }; @@ -421,6 +425,7 @@ Slider.defaultProps = { disabled: false, dots: false, range: false, + vertical: false, allowCross: true, }; diff --git a/src/Steps.jsx b/src/Steps.jsx index e3e969e..5f33e7c 100644 --- a/src/Steps.jsx +++ b/src/Steps.jsx @@ -1,7 +1,7 @@ import React from 'react'; import classNames from 'classnames'; -function calcPoints(marks, dots, step, min, max) { +function calcPoints(vertical, marks, dots, step, min, max) { const points = Object.keys(marks).map(parseFloat); if (dots) { for (let i = min; i <= max; i = i + step) { @@ -12,12 +12,12 @@ function calcPoints(marks, dots, step, min, max) { return points; } -const Steps = ({prefixCls, marks, dots, step, included, +const Steps = ({prefixCls, vertical, marks, dots, step, included, lowerBound, upperBound, max, min}) => { const range = max - min; - const elements = calcPoints(marks, dots, step, min, max).map((point) => { - const offset = (point - min) / range * 100 + '%'; - const style = { left: offset }; + const elements = calcPoints(vertical, marks, dots, step, min, max).map((point) => { + const offset = Math.abs(point - min) / range * 100 + '%'; + const style = vertical ? { bottom: offset } : { left: offset }; const isActived = (!included && point === upperBound) || (included && point <= upperBound && point >= lowerBound); diff --git a/src/Track.jsx b/src/Track.jsx index 70df9ed..bcecba2 100644 --- a/src/Track.jsx +++ b/src/Track.jsx @@ -1,11 +1,16 @@ import React from 'react'; -const Track = ({className, included, offset, length}) => { +const Track = ({className, included, vertical, offset, length}) => { const style = { - left: offset + '%', - width: length + '%', visibility: included ? 'visible' : 'hidden', }; + if (vertical) { + style.bottom = offset + '%'; + style.height = length + '%'; + } else { + style.left = offset + '%'; + style.width = length + '%'; + } return
; }; diff --git a/tests/index.spec.js b/tests/index.spec.js index ca2bd0d..e654238 100644 --- a/tests/index.spec.js +++ b/tests/index.spec.js @@ -6,7 +6,7 @@ const Slider = require('..'); require('../assets/index.less'); -describe('rc-slider', function() { +describe('rc-slider', function test() { this.timeout(5000); const div = document.createElement('div'); document.body.appendChild(div); @@ -140,4 +140,9 @@ describe('rc-slider', function() { expect(range.state.upperBound).to.be(90); ReactDOM.unmountComponentAtNode(div); }); + + it('should render a vertical slider, when `vertical` is true', () => { + const slider = ReactDOM.render(, div); + expect(ReactTestUtils.scryRenderedDOMComponentsWithClass(slider, 'rc-slider-vertical').length).to.be(1); + }); });