diff --git a/docs/demos/index.js b/docs/demos/index.js
index 2403dd8a1..741a1a801 100644
--- a/docs/demos/index.js
+++ b/docs/demos/index.js
@@ -59,6 +59,7 @@ export default {
'radio': asyncWrapper(r => require.ensure([], () => r(componentWrapper(require('./views/radio'), 'radio')), 'radio')),
'search': asyncWrapper(r => require.ensure([], () => r(componentWrapper(require('./views/search'), 'search')), 'search')),
'sku': asyncWrapper(r => require.ensure([], () => r(componentWrapper(require('./views/sku'), 'sku')), 'sku')),
+ 'slider': asyncWrapper(r => require.ensure([], () => r(componentWrapper(require('./views/slider'), 'slider')), 'slider')),
'stepper': asyncWrapper(r => require.ensure([], () => r(componentWrapper(require('./views/stepper'), 'stepper')), 'stepper')),
'steps': asyncWrapper(r => require.ensure([], () => r(componentWrapper(require('./views/steps'), 'steps')), 'steps')),
'submit-bar': asyncWrapper(r => require.ensure([], () => r(componentWrapper(require('./views/submit-bar'), 'submit-bar')), 'submit-bar')),
diff --git a/docs/demos/views/slider.vue b/docs/demos/views/slider.vue
new file mode 100644
index 000000000..0a0e07b4a
--- /dev/null
+++ b/docs/demos/views/slider.vue
@@ -0,0 +1,90 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/markdown/en-US/slider.md b/docs/markdown/en-US/slider.md
new file mode 100644
index 000000000..9c6af4378
--- /dev/null
+++ b/docs/markdown/en-US/slider.md
@@ -0,0 +1,109 @@
+## Slider
+
+### Install
+``` javascript
+import { Slider } from 'vant';
+
+Vue.use(Slider);
+```
+
+### Usage
+#### Basic Usage
+
+```html
+
+
+
+
+
+
+```
+
+```js
+data() {
+ return {
+ value1: 50
+ }
+}
+```
+
+#### Max && Min
+
+```html
+
+```
+```js
+data() {
+ return {
+ value2: 50,
+ min: 10,
+ max: 90
+ }
+}
+```
+
+#### Disabed
+
+```html
+
+```
+
+#### Customized style
+
+```html
+
+```
+
+```js
+data() {
+ return {
+ value4: 50
+ }
+},
+methods: {
+ handleChange(value) {
+ console.log('handleChange:', value)
+ },
+ handleAfterChange(value) {
+ console.log('handleAfterChange:', value)
+ }
+}
+```
+
+### Customized style
+
+```html
+
+```
+
+### API
+
+| Attribute | Description | Type | Default | Accepted Values |
+|-----------|-----------|-----------|-------------|-------------|
+| value | current value | Number | 0 | - |
+| disabled | disabled | Boolean | false | - |
+| bar-color | bar-color | string | `#cacaca` | - |
+| loaded-bar-color | loaded-bar-color | string | `#4b0` | - |
+| pivot-color | pivot-color | string | `#4b0` | - |
+| max | max | Number | 100 | - |
+| min | min | Number | 0 | - |
+
+### Event
+
+| Event | Description | Arguments |
+|-----------|-----------|-----------|
+| change | touchmove emit | value |
+| after-change | touchend emit | value |
diff --git a/docs/markdown/index.js b/docs/markdown/index.js
index 76f0fb952..e5ba2075c 100644
--- a/docs/markdown/index.js
+++ b/docs/markdown/index.js
@@ -56,6 +56,7 @@ export default {
'zh-CN/radio': wrapper(r => require.ensure([], () => r(require('./zh-CN/radio.md')), 'zh-CN/radio')),
'zh-CN/search': wrapper(r => require.ensure([], () => r(require('./zh-CN/search.md')), 'zh-CN/search')),
'zh-CN/sku': wrapper(r => require.ensure([], () => r(require('./zh-CN/sku.md')), 'zh-CN/sku')),
+ 'zh-CN/slider': wrapper(r => require.ensure([], () => r(require('./zh-CN/slider.md')), 'zh-CN/slider')),
'zh-CN/stepper': wrapper(r => require.ensure([], () => r(require('./zh-CN/stepper.md')), 'zh-CN/stepper')),
'zh-CN/steps': wrapper(r => require.ensure([], () => r(require('./zh-CN/steps.md')), 'zh-CN/steps')),
'zh-CN/submit-bar': wrapper(r => require.ensure([], () => r(require('./zh-CN/submit-bar.md')), 'zh-CN/submit-bar')),
@@ -112,6 +113,7 @@ export default {
'en-US/radio': wrapper(r => require.ensure([], () => r(require('./en-US/radio.md')), 'en-US/radio')),
'en-US/search': wrapper(r => require.ensure([], () => r(require('./en-US/search.md')), 'en-US/search')),
'en-US/sku': wrapper(r => require.ensure([], () => r(require('./en-US/sku.md')), 'en-US/sku')),
+ 'en-US/slider': wrapper(r => require.ensure([], () => r(require('./en-US/slider.md')), 'en-US/slider')),
'en-US/stepper': wrapper(r => require.ensure([], () => r(require('./en-US/stepper.md')), 'en-US/stepper')),
'en-US/steps': wrapper(r => require.ensure([], () => r(require('./en-US/steps.md')), 'en-US/steps')),
'en-US/submit-bar': wrapper(r => require.ensure([], () => r(require('./en-US/submit-bar.md')), 'en-US/submit-bar')),
diff --git a/docs/markdown/zh-CN/slider.md b/docs/markdown/zh-CN/slider.md
new file mode 100644
index 000000000..5cffc243c
--- /dev/null
+++ b/docs/markdown/zh-CN/slider.md
@@ -0,0 +1,108 @@
+## Slider 滑块
+
+### 使用指南
+``` javascript
+import { Slider } from 'vant';
+
+Vue.use(Slider);
+```
+
+#### 基本用法
+
+```html
+
+
+
+
+
+
+```
+
+```js
+data() {
+ return {
+ value1: 50
+ }
+}
+```
+
+#### 最大最小值
+
+```html
+
+```
+```js
+data() {
+ return {
+ value2: 50,
+ min: 10,
+ max: 90
+ }
+}
+```
+
+#### 禁用
+
+```html
+
+```
+
+#### 事件
+
+```html
+
+```
+
+```js
+data() {
+ return {
+ value4: 50
+ }
+},
+methods: {
+ handleChange(value) {
+ console.log('handleChange:', value)
+ },
+ handleAfterChange(value) {
+ console.log('handleAfterChange:', value)
+ }
+}
+```
+
+### 自定义样式
+
+```html
+
+```
+
+### API
+
+| 参数 | 说明 | 类型 | 默认值 | 必须 |
+|-----------|-----------|-----------|-------------|-------------|
+| value | 当前进度百分比 | Number | 0 | 否 |
+| disabled | 滑块是否禁用 | Boolean | false | 否 |
+| bar-color | 进度条颜色 | string | `#cacaca` | 否 |
+| loaded-bar-color | 已加载条颜色 | string | `#4b0` | 否 |
+| pivot-color | 滑块颜色 | string | `#4b0` | 否 |
+| max | 最大值 | Number | 100 | - |
+| min | 最小值 | Number | 0 | - |
+
+### Event
+
+| 事件名 | 说明 | 参数 |
+|-----------|-----------|-----------|
+| change | 拖动时触发 | value |
+| after-change | 拖动停止后触发 | value |
diff --git a/docs/src/doc.config.js b/docs/src/doc.config.js
index 3cfdb6560..8117af688 100644
--- a/docs/src/doc.config.js
+++ b/docs/src/doc.config.js
@@ -148,6 +148,10 @@ module.exports = {
path: '/swipe',
title: 'Swipe - 轮播'
},
+ {
+ path: '/slider',
+ title: 'Slider - 滑块'
+ },
{
path: '/tab',
title: 'Tab - 标签页'
@@ -442,6 +446,10 @@ module.exports = {
path: '/swipe',
title: 'Swipe'
},
+ {
+ path: '/slider',
+ title: 'Slider'
+ },
{
path: '/tab',
title: 'Tab'
diff --git a/packages/index.js b/packages/index.js
index e34632cd7..131c6d6b2 100644
--- a/packages/index.js
+++ b/packages/index.js
@@ -48,6 +48,7 @@ import RadioGroup from './radio-group';
import Row from './row';
import Search from './search';
import Sku from './sku';
+import Slider from './slider';
import Step from './step';
import Stepper from './stepper';
import Steps from './steps';
@@ -116,6 +117,7 @@ const components = [
Row,
Search,
Sku,
+ Slider,
Step,
Stepper,
Steps,
@@ -196,6 +198,7 @@ export {
Row,
Search,
Sku,
+ Slider,
Step,
Stepper,
Steps,
diff --git a/packages/slider/index.vue b/packages/slider/index.vue
new file mode 100644
index 000000000..2d042cea1
--- /dev/null
+++ b/packages/slider/index.vue
@@ -0,0 +1,170 @@
+
+
+
+
diff --git a/packages/vant-css/src/index.css b/packages/vant-css/src/index.css
index 15c427c70..269044633 100644
--- a/packages/vant-css/src/index.css
+++ b/packages/vant-css/src/index.css
@@ -26,6 +26,7 @@
@import './stepper.css';
@import './progress.css';
@import './swipe.css';
+@import './slider.css';
/* form components */
@import './checkbox.css';
diff --git a/packages/vant-css/src/slider.css b/packages/vant-css/src/slider.css
new file mode 100644
index 000000000..888afcffd
--- /dev/null
+++ b/packages/vant-css/src/slider.css
@@ -0,0 +1,41 @@
+@import './common/var.css';
+
+$c-slider-background: #cacaca;
+$c-pivot-border: #fff;
+
+.van-slider {
+ &--disabled {
+ opacity: .3;
+ .van-slider__pivot {
+ cursor: not-allowed;
+ }
+ }
+
+ &__bar {
+ position: relative;
+ margin: 0 15px;
+ height: 4px;
+ border-radius: 5px;
+ background: $c-slider-background;
+ }
+
+ &__loaded-portion,
+ &__finished-portion {
+ border-radius: 5px;
+ height: 100%;
+ position: absolute;
+ left: 0;
+ }
+
+ &__pivot {
+ position: absolute;
+ top: 50%;
+ width: 16px;
+ height: 16px;
+ border: 3px solid $c-pivot-border;
+ box-shadow: 0 1px 4px;
+ border-radius: 50%;
+ background-color: $c-slider-background;
+ transform: translate3d(0, -50%, 0);
+ }
+}
diff --git a/test/specs/slider.spec.js b/test/specs/slider.spec.js
new file mode 100644
index 000000000..8287bca39
--- /dev/null
+++ b/test/specs/slider.spec.js
@@ -0,0 +1,98 @@
+import Slider from 'packages/slider';
+import { mount } from 'avoriaz';
+import { triggerTouch } from '../utils';
+
+describe('Slider', () => {
+ let wrapper;
+
+ afterEach(() => {
+ wrapper && wrapper.destroy();
+ });
+
+ it('create a simple slider', () => {
+ wrapper = mount(Slider, {
+ propsData: {
+ value: 50,
+ disabled: true
+ }
+ });
+
+ expect(wrapper.hasClass('van-slider')).to.be.true;
+ expect(wrapper.find('.van-slider__bar').length).to.equal(1);
+ expect(wrapper.find('.van-slider__finished-portion').length).to.equal(1);
+ expect(wrapper.vm.value).to.equal(50);
+ expect(wrapper.hasClass('van-slider--disabled')).to.equal(true);
+
+ wrapper.setProps({
+ value: 100
+ });
+ wrapper.update();
+
+ expect(wrapper.vm.value).to.equal(100);
+ });
+
+ it('test click bar', () => {
+ wrapper = mount(Slider, {
+ propsData: {
+ disabled: true,
+ value: 50
+ }
+ });
+
+ const eventStub = sinon.stub(wrapper.vm, '$emit');
+ const $bar = wrapper.find('.van-slider__bar')[0];
+ $bar.trigger('click');
+
+ expect(eventStub.called).to.equal(false);
+
+ wrapper.setData({
+ disabled: false
+ });
+ wrapper.update();
+ $bar.trigger('click');
+
+ expect(wrapper.vm.disabled).to.equal(false);
+ expect(eventStub.called).to.equal(true);
+ });
+
+ it('Customized style', () => {
+ const COLOR = '#123';
+ wrapper = mount(Slider, {
+ propsData: {
+ pivotColor: COLOR,
+ barColor: COLOR,
+ loadedBarColor: COLOR,
+ value: 50
+ }
+ });
+
+ expect(wrapper.vm.barStyle.backgroundColor).to.equal(COLOR);
+ expect(wrapper.vm.pivotStyle.backgroundColor).to.equal(COLOR);
+ expect(wrapper.vm.finishedStyle.backgroundColor).to.equal(COLOR);
+ });
+
+ it('drag pivot', () => {
+ wrapper = mount(Slider, {
+ propsData: {
+ value: 50
+ }
+ });
+
+ const pivot = wrapper.find('.van-slider__pivot')[0];
+ triggerTouch(pivot, 'touchstart', 0, 0);
+ expect(wrapper.vm.startX).to.equal(0);
+
+ triggerTouch(pivot, 'touchmove', 50, 0);
+ expect(wrapper.vm.offsetX).to.equal(50);
+
+ triggerTouch(pivot, 'touchend', 50, 0);
+ expect(wrapper.vm.offsetX).to.equal(50);
+
+ wrapper.setData({
+ disabled: true
+ });
+
+ triggerTouch(pivot, 'touchstart', 0, 0);
+ expect(wrapper.vm.startX).to.equal(0);
+ });
+});