From 7e9ca167fad84afc9ba595763a41a21a9f643a7a Mon Sep 17 00:00:00 2001
From: neverland <chenjiahan@buaa.edu.cn>
Date: Tue, 2 Apr 2019 09:18:42 +0800
Subject: [PATCH] [new feature] Slider: add vertical prop (#3078)

---
 packages/slider/demo/index.vue                | 19 +++++--
 packages/slider/en-US.md                      |  9 ++++
 packages/slider/index.js                      | 30 ++++++++---
 packages/slider/index.less                    | 11 ++++
 .../test/__snapshots__/demo.spec.js.snap      | 11 ++++
 .../test/__snapshots__/index.spec.js.snap     | 20 ++++++++
 packages/slider/test/index.spec.js            | 51 ++++++++++++++++++-
 packages/slider/zh-CN.md                      | 11 ++++
 8 files changed, 151 insertions(+), 11 deletions(-)

diff --git a/packages/slider/demo/index.vue b/packages/slider/demo/index.vue
index 2dd14769a..9354bb6e8 100644
--- a/packages/slider/demo/index.vue
+++ b/packages/slider/demo/index.vue
@@ -53,6 +53,16 @@
         </div>
       </van-slider>
     </demo-block>
+
+    <demo-block :title="$t('vertical')">
+      <div :style="{ height: '120px', paddingLeft: '30px' }">
+        <van-slider
+          v-model="value7"
+          vertical
+          @change="onChange"
+        />
+      </div>
+    </demo-block>
   </demo-section>
 </template>
 
@@ -66,7 +76,8 @@ export default {
       title4: '指定步长',
       customStyle: '自定义样式',
       customButton: '自定义按钮',
-      text: '当前值:'
+      text: '当前值:',
+      vertical: '垂直方向'
     },
     'en-US': {
       title1: 'Basic Usage',
@@ -75,7 +86,8 @@ export default {
       title4: 'Step size',
       customStyle: 'Custom Style',
       customButton: 'Custom Button',
-      text: 'Current value: '
+      text: 'Current value: ',
+      vertical: 'Vertical'
     }
   },
 
@@ -86,7 +98,8 @@ export default {
       value3: 50,
       value4: 50,
       value5: 50,
-      value6: 50
+      value6: 50,
+      value7: 50
     };
   },
 
diff --git a/packages/slider/en-US.md b/packages/slider/en-US.md
index f89337284..bdc71cb6f 100644
--- a/packages/slider/en-US.md
+++ b/packages/slider/en-US.md
@@ -74,6 +74,14 @@ export default {
 </van-slider>
 ```
 
+#### Vertical
+
+```html
+<div :style="{ height: '100px' }">
+  <van-slider v-model="value" vertical />
+</div>
+```
+
 ### API
 
 | Attribute | Description | Type | Default |
@@ -86,6 +94,7 @@ export default {
 | bar-height | Height of bar | `String` | `2px` |
 | active-color | Active color of bar | `String` | `#1989fa` |
 | inactive-color | Inactive color of bar | `String` | `#e5e5e5` |
+| vertical | Whether to display vertical | `Boolean` | `false` |
 
 ### Event
 
diff --git a/packages/slider/index.js b/packages/slider/index.js
index 12ec5c19b..b2e83ecf3 100644
--- a/packages/slider/index.js
+++ b/packages/slider/index.js
@@ -10,6 +10,7 @@ export default sfc({
     min: Number,
     value: Number,
     disabled: Boolean,
+    vertical: Boolean,
     activeColor: String,
     inactiveColor: String,
     max: {
@@ -41,8 +42,12 @@ export default sfc({
       if (this.disabled) return;
 
       this.touchMove(event);
+
       const rect = this.$el.getBoundingClientRect();
-      const diff = (this.deltaX / rect.width) * 100;
+      const delta = this.vertical ? this.deltaY : this.deltaX;
+      const total = this.vertical ? rect.height : rect.width;
+      const diff = (delta / total) * 100;
+
       this.updateValue(this.startValue + diff);
     },
 
@@ -57,7 +62,10 @@ export default sfc({
       if (this.disabled) return;
 
       const rect = this.$el.getBoundingClientRect();
-      const value = ((event.clientX - rect.left) / rect.width) * 100;
+      const delta = this.vertical ? event.clientY - rect.top : event.clientX - rect.left;
+      const total = this.vertical ? rect.height : rect.width;
+      const value = (delta / total) * 100;
+
       this.updateValue(value, true);
     },
 
@@ -71,23 +79,33 @@ export default sfc({
     },
 
     format(value) {
-      return Math.round(Math.max(this.min, Math.min(value, this.max)) / this.step) * this.step;
+      return (
+        Math.round(Math.max(this.min, Math.min(value, this.max)) / this.step) * this.step
+      );
     }
   },
 
   render(h) {
+    const { vertical } = this;
     const style = {
       background: this.inactiveColor
     };
 
+    const mainAxis = vertical ? 'height' : 'width';
+    const crossAxis = vertical ? 'width' : 'height';
+
     const barStyle = {
-      width: `${this.format(this.value)}%`,
-      height: this.barHeight,
+      [mainAxis]: `${this.format(this.value)}%`,
+      [crossAxis]: this.barHeight,
       background: this.activeColor
     };
 
     return (
-      <div style={style} class={bem({ disabled: this.disabled })} onClick={this.onClick}>
+      <div
+        style={style}
+        class={bem({ disabled: this.disabled, vertical })}
+        onClick={this.onClick}
+      >
         <div class={bem('bar')} style={barStyle}>
           <div
             class={bem('button-wrapper')}
diff --git a/packages/slider/index.less b/packages/slider/index.less
index c113d2510..c9ae76478 100644
--- a/packages/slider/index.less
+++ b/packages/slider/index.less
@@ -39,4 +39,15 @@
   &--disabled {
     opacity: .3;
   }
+
+  &--vertical {
+    height: 100%;
+    display: inline-block;
+
+    .van-slider__button-wrapper {
+      top: auto;
+      bottom: 0;
+      transform: translate3d(50%, 50%, 0);
+    }
+  }
 }
diff --git a/packages/slider/test/__snapshots__/demo.spec.js.snap b/packages/slider/test/__snapshots__/demo.spec.js.snap
index 509249fc9..b59eebc3a 100644
--- a/packages/slider/test/__snapshots__/demo.spec.js.snap
+++ b/packages/slider/test/__snapshots__/demo.spec.js.snap
@@ -58,5 +58,16 @@ exports[`renders demo correctly 1`] = `
       </div>
     </div>
   </div>
+  <div>
+    <div style="height:120px;padding-left:30px;">
+      <div class="van-slider van-slider--vertical">
+        <div class="van-slider__bar" style="height:50%;width:2px;">
+          <div class="van-slider__button-wrapper">
+            <div class="van-slider__button"></div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
 </div>
 `;
diff --git a/packages/slider/test/__snapshots__/index.spec.js.snap b/packages/slider/test/__snapshots__/index.spec.js.snap
index 02d26fc8d..9b1b41e6d 100644
--- a/packages/slider/test/__snapshots__/index.spec.js.snap
+++ b/packages/slider/test/__snapshots__/index.spec.js.snap
@@ -20,6 +20,16 @@ exports[`click bar 2`] = `
 </div>
 `;
 
+exports[`click vertical 1`] = `
+<div class="van-slider van-slider--vertical">
+  <div class="van-slider__bar" style="height: 100%; width: 2px;">
+    <div class="van-slider__button-wrapper">
+      <div class="van-slider__button"></div>
+    </div>
+  </div>
+</div>
+`;
+
 exports[`drag button 1`] = `
 <div class="van-slider van-slider--disabled">
   <div class="van-slider__bar" style="width: 50%; height: 2px;">
@@ -39,3 +49,13 @@ exports[`drag button 2`] = `
   </div>
 </div>
 `;
+
+exports[`drag button vertical 1`] = `
+<div class="van-slider van-slider--vertical">
+  <div class="van-slider__bar" style="height: 100%; width: 2px;">
+    <div class="van-slider__button-wrapper">
+      <div class="van-slider__button"></div>
+    </div>
+  </div>
+</div>
+`;
diff --git a/packages/slider/test/index.spec.js b/packages/slider/test/index.spec.js
index 682754841..85642d7ea 100644
--- a/packages/slider/test/index.spec.js
+++ b/packages/slider/test/index.spec.js
@@ -1,11 +1,19 @@
 import Slider from '..';
 import { mount, triggerDrag, trigger } from '../../../test/utils';
 
-Element.prototype.getBoundingClientRect = jest.fn(() => ({ width: 100, left: 0 }));
+function mockGetBoundingClientRect(vertical) {
+  Element.prototype.getBoundingClientRect = jest.fn(() => ({
+    width: vertical ? 0 : 100,
+    height: vertical ? 100 : 0,
+    top: vertical ? 0 : 100,
+    left: vertical ? 100 : 0
+  }));
+}
 
 test('drag button', () => {
+  mockGetBoundingClientRect();
+
   const wrapper = mount(Slider, {
-    attachToDocument: true,
     propsData: {
       value: 50,
       disabled: true
@@ -26,6 +34,8 @@ test('drag button', () => {
 });
 
 it('click bar', () => {
+  mockGetBoundingClientRect();
+
   const wrapper = mount(Slider, {
     propsData: {
       value: 50,
@@ -44,3 +54,40 @@ it('click bar', () => {
   trigger(wrapper, 'click', 100, 0);
   expect(wrapper).toMatchSnapshot();
 });
+
+test('drag button vertical', () => {
+  mockGetBoundingClientRect(true);
+
+  const wrapper = mount(Slider, {
+    propsData: {
+      value: 50,
+      vertical: true
+    }
+  });
+
+  wrapper.vm.$on('input', value => {
+    wrapper.setProps({ value });
+  });
+
+  const button = wrapper.find('.van-slider__button');
+  triggerDrag(button, 0, 50);
+  expect(wrapper).toMatchSnapshot();
+});
+
+it('click vertical', () => {
+  mockGetBoundingClientRect(true);
+
+  const wrapper = mount(Slider, {
+    propsData: {
+      value: 50,
+      vertical: true
+    }
+  });
+
+  wrapper.vm.$on('input', value => {
+    wrapper.setProps({ value });
+  });
+
+  trigger(wrapper, 'click', 0, 100);
+  expect(wrapper).toMatchSnapshot();
+});
diff --git a/packages/slider/zh-CN.md b/packages/slider/zh-CN.md
index d430913ca..9d43b9262 100644
--- a/packages/slider/zh-CN.md
+++ b/packages/slider/zh-CN.md
@@ -73,6 +73,16 @@ export default {
 </van-slider>
 ```
 
+#### 垂直方向
+
+Slider 垂直展示时,高度为 100% 父元素高度
+
+```html
+<div :style="{ height: '100px' }">
+  <van-slider v-model="value" vertical />
+</div>
+```
+
 ### API
 
 | 参数 | 说明 | 类型 | 默认值 | 版本 |
@@ -85,6 +95,7 @@ export default {
 | bar-height | 进度条高度 | `String` | `2px` | 1.1.0 |
 | active-color | 进度条激活态颜色 | `String` | `#1989fa` | 1.5.1 |
 | inactive-color | 进度条默认颜色 | `String` | `#e5e5e5` | 1.5.1 |
+| vertical | 是否垂直展示 | `Boolean` | `false` | 1.6.13 |
 
 ### Event