feat(slider): add new slider component
This commit is contained in:
parent
3bfe6684d6
commit
3ad8a6a329
|
|
@ -218,6 +218,11 @@ input-item-placeholder = color-text-placeholder
|
|||
input-item-placeholder-highlight = color-primary
|
||||
input-item-icon = color-text-placeholder // delete icon
|
||||
|
||||
// slider
|
||||
slider-bg-base = color-bg-base
|
||||
slider-bg-tap = color-primary
|
||||
slider-handle-bg = color-bg-inverse
|
||||
|
||||
// landscape
|
||||
landscape-width = 540px
|
||||
landscape-radius = radius-normal
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ import WaterMark from './water-mark'
|
|||
import Transition from './transition'
|
||||
import DetailItem from './detail-item'
|
||||
import Overlay from './overlay'
|
||||
import Slider from './slider'
|
||||
/* @init<%import ${componentNameUpper} from './${componentName}'%> */
|
||||
|
||||
// 全量引入提醒
|
||||
|
|
@ -124,6 +125,7 @@ export const components = {
|
|||
Transition,
|
||||
DetailItem,
|
||||
Overlay,
|
||||
Slider,
|
||||
/* @init<%${componentNameUpper},%> */
|
||||
}
|
||||
|
||||
|
|
@ -208,6 +210,7 @@ export {
|
|||
Transition,
|
||||
DetailItem,
|
||||
Overlay,
|
||||
Slider,
|
||||
/* @init<%${componentNameUpper},%> */
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
---
|
||||
title: Slider
|
||||
preview: https://didi.github.io/mand-mobile/examples/#/slider
|
||||
---
|
||||
|
||||
### Import
|
||||
|
||||
```javascript
|
||||
import { Slider } from 'mand-mobile'
|
||||
|
||||
Vue.component(Slider.name, Slider)
|
||||
```
|
||||
|
||||
### Code Examples
|
||||
<!-- DEMO -->
|
||||
|
||||
### API
|
||||
|
||||
#### Slider Props
|
||||
|Props | Description | Type | Default | Note|
|
||||
|----|-----|------|------|------|
|
||||
|v-model|the value of slider, when <code>range</code> is false, use <code>number</code>, otherwise, use <code>[number, number]</code>|number | number[]|`0`|-|
|
||||
|disabled|whether to disable slider|Boolean|`false`|-|
|
||||
|min|the minimum value the slider can slide from|number|`0`|-|
|
||||
|max|the maximum value the slider can slide to|number|`100`|-|
|
||||
|step|the granularity the slider can step through|number|`1`|-|
|
||||
|range|dual thumb mode|Boolean|`false`|-|
|
||||
|format|slider will pass its value to <code>format</code>, and display its value in tooltip|Function|`(val) => {return val}`|-|
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
---
|
||||
title: Slider 滑块
|
||||
preview: https://didi.github.io/mand-mobile/examples/#/slider
|
||||
---
|
||||
|
||||
### 引入
|
||||
|
||||
```javascript
|
||||
import { Slider } from '@didi/mand-mobile'
|
||||
|
||||
Vue.component(Slider.name, Slider)
|
||||
```
|
||||
|
||||
### 代码演示
|
||||
<!-- DEMO -->
|
||||
|
||||
### API
|
||||
|
||||
#### Slider Props
|
||||
|属性 | 说明 | 类型 | 默认值 | 备注|
|
||||
|----|-----|------|------|------|
|
||||
|v-model|双向绑定的值, 当开启<code>range</code>时, 其值为数组形式</code>|number | number[]|`0`|-|
|
||||
|disabled|是否禁用滑块|Boolean|`false`|-|
|
||||
|min|可拖动的最小值|number|`0`|-|
|
||||
|max|可拖动的最大值|number|`100`|-|
|
||||
|step|步长|number|`1`|-|
|
||||
|range|是否启动双向拖动|Boolean|`false`|-|
|
||||
|format|显示文本的格式化函数|Function|`(val) => {return val}`|-|
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
export default {
|
||||
'name': 'slider',
|
||||
'text': '滑块',
|
||||
'category': 'form',
|
||||
'description': '',
|
||||
'author': 'moyu <moyuboy@gmail.com>'
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
<template>
|
||||
<div class="md-example-child md-example-child-slider">
|
||||
<md-slider v-model="quantity"></md-slider>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
import {Slider} from 'mand-mobile'
|
||||
|
||||
export default {
|
||||
name: 'slider-demo',
|
||||
/* DELETE */
|
||||
title: '基本',
|
||||
titleEnUS: 'Basic',
|
||||
/* DELETE */
|
||||
components: {
|
||||
[Slider.name]: Slider,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
quantity: 25,
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
</style>
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
<template>
|
||||
<div class="md-example-child md-example-child-slider">
|
||||
<md-slider v-model="quantity" disabled></md-slider>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
import {Slider} from 'mand-mobile'
|
||||
|
||||
export default {
|
||||
name: 'slider-demo',
|
||||
/* DELETE */
|
||||
title: '禁用',
|
||||
titleEnUS: 'Disabled',
|
||||
/* DELETE */
|
||||
components: {
|
||||
[Slider.name]: Slider,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
quantity: 50,
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
</style>
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
<template>
|
||||
<div class="md-example-child md-example-child-slider">
|
||||
<md-slider v-model="quantity" :format="format"></md-slider>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
import {Slider} from 'mand-mobile'
|
||||
|
||||
export default {
|
||||
name: 'slider-demo',
|
||||
/* DELETE */
|
||||
title: '格式化',
|
||||
titleEnUS: 'Format',
|
||||
/* DELETE */
|
||||
components: {
|
||||
[Slider.name]: Slider,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
quantity: 50,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
format(val) {
|
||||
return '¥' + val
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
</style>
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
<template>
|
||||
<div class="md-example-child md-example-child-slider">
|
||||
<md-slider v-model="range" range></md-slider>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
import {Slider} from 'mand-mobile'
|
||||
|
||||
export default {
|
||||
name: 'slider-demo',
|
||||
/* DELETE */
|
||||
title: '范围',
|
||||
titleEnUS: 'Range',
|
||||
/* DELETE */
|
||||
components: {
|
||||
[Slider.name]: Slider,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
range: [25, 50],
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
</style>
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
<template>
|
||||
<div class="md-example-child md-example-child-slider">
|
||||
<md-slider v-model="bucket" :step="10"></md-slider>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
import {Slider} from 'mand-mobile'
|
||||
|
||||
export default {
|
||||
name: 'slider-demo',
|
||||
/* DELETE */
|
||||
title: '步长',
|
||||
titleEnUS: 'Steps',
|
||||
/* DELETE */
|
||||
components: {
|
||||
[Slider.name]: Slider,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
bucket: 10,
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
</style>
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
<template>
|
||||
<div class="md-example-child md-example-child-slider">
|
||||
<md-slider v-model="range" :min="15" :max="80" range></md-slider>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
import {Slider} from 'mand-mobile'
|
||||
|
||||
export default {
|
||||
name: 'slider-demo',
|
||||
/* DELETE */
|
||||
title: '边界值',
|
||||
titleEnUS: 'Min & Max',
|
||||
/* DELETE */
|
||||
components: {
|
||||
[Slider.name]: Slider,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
range: [25, 50],
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
</style>
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
<template>
|
||||
<div class="md-example slider">
|
||||
<section class="md-example-section" v-for="(demo, index) in demos" :key="index">
|
||||
<div class="md-example-title" v-html="demo.title"></div>
|
||||
<div class="md-example-describe" v-html="demo.describe"></div>
|
||||
<div class="md-example-content">
|
||||
<component :is="demo"></component>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
import createDemoModule from '../../../examples/create-demo-module'
|
||||
import Demo0 from './cases/demo0'
|
||||
import Demo1 from './cases/demo1'
|
||||
import Demo2 from './cases/demo2'
|
||||
import Demo3 from './cases/demo3'
|
||||
import Demo4 from './cases/demo4'
|
||||
import Demo5 from './cases/demo5'
|
||||
|
||||
export default createDemoModule('slider', [Demo0, Demo1, Demo2, Demo3, Demo4, Demo5])
|
||||
</script>
|
||||
|
||||
<style lang="stylus">
|
||||
.md-example.slider
|
||||
position relative
|
||||
</style>
|
||||
|
|
@ -0,0 +1,324 @@
|
|||
<template>
|
||||
<div class="md-slider" :class="{'is-disabled': disabled}">
|
||||
<template v-if="range">
|
||||
<div class="md-slider-bar" :style="barStyle"></div>
|
||||
<div class="md-slider-handle is-lower"
|
||||
:data-hint="format(values[0])"
|
||||
:class="{
|
||||
'is-active': isDragging && !isDragingUpper
|
||||
}"
|
||||
:style="{'left': lowerHandlePosition + '%'}">
|
||||
<span
|
||||
@mousedown="$_startLowerDrag"
|
||||
@touchstart="$_startLowerDrag"
|
||||
></span>
|
||||
</div>
|
||||
<div class="md-slider-handle is-higher"
|
||||
:data-hint="format(values[1])"
|
||||
:class="{
|
||||
'is-active': isDragging && isDragingUpper
|
||||
}"
|
||||
:style="{'left': upperHandlePosition + '%'}">
|
||||
<span
|
||||
@mousedown="$_startUpperDrag"
|
||||
@touchstart="$_startUpperDrag"
|
||||
></span>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="md-slider-bar" :style="barStyle"></div>
|
||||
<div class="md-slider-handle"
|
||||
:data-hint="format(values[0])"
|
||||
:class="{
|
||||
'is-active': isDragging
|
||||
}"
|
||||
:style="{'left': lowerHandlePosition + '%'}">
|
||||
<span
|
||||
@mousedown="$_startLowerDrag"
|
||||
@touchstart="$_startLowerDrag"
|
||||
></span>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
export default {
|
||||
name: 'md-slider',
|
||||
|
||||
props: {
|
||||
value: {
|
||||
type: [Array, Number],
|
||||
default: 0,
|
||||
},
|
||||
min: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
max: {
|
||||
type: Number,
|
||||
default: 100,
|
||||
},
|
||||
step: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
},
|
||||
range: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
format: {
|
||||
type: Function,
|
||||
default(val) {
|
||||
return val
|
||||
},
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
isDragging: false,
|
||||
isDragingUpper: false,
|
||||
values: [this.min, this.max],
|
||||
startDragMousePos: 0,
|
||||
startVal: 0,
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
value: {
|
||||
immediate: true,
|
||||
handler(val) {
|
||||
if (
|
||||
(Array.isArray(val) && (val[0] !== this.values[0] || val[1] !== this.values[1])) ||
|
||||
val !== this.values[0]
|
||||
) {
|
||||
this.$_updateValue(val)
|
||||
}
|
||||
},
|
||||
},
|
||||
disabled(newVal) {
|
||||
if (!newVal) {
|
||||
this.$_stopDrag()
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
lowerHandlePosition() {
|
||||
return (this.values[0] - this.min) / (this.max - this.min) * 100
|
||||
},
|
||||
upperHandlePosition() {
|
||||
return (this.values[1] - this.min) / (this.max - this.min) * 100
|
||||
},
|
||||
barStyle() {
|
||||
if (this.range) {
|
||||
return {
|
||||
width: (this.values[1] - this.values[0]) / (this.max - this.min) * 100 + '%',
|
||||
left: this.lowerHandlePosition + '%',
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
width: this.values[0] / (this.max - this.min) * 100 + '%',
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
$_updateValue(newVal) {
|
||||
let newValues = []
|
||||
|
||||
if (Array.isArray(newVal)) {
|
||||
newValues = [newVal[0], newVal[1]]
|
||||
} else {
|
||||
newValues[0] = newVal
|
||||
}
|
||||
|
||||
if (typeof newValues[0] !== 'number') {
|
||||
newValues[0] = this.values[0]
|
||||
} else {
|
||||
newValues[0] = Math.round((newValues[0] - this.min) / this.step) * this.step + this.min
|
||||
}
|
||||
|
||||
if (typeof newValues[1] !== 'number') {
|
||||
newValues[1] = this.values[1]
|
||||
} else {
|
||||
newValues[1] = Math.round((newValues[1] - this.min) / this.step) * this.step + this.min
|
||||
}
|
||||
|
||||
// value boundary adjust
|
||||
if (newValues[0] < this.min) {
|
||||
newValues[0] = this.min
|
||||
}
|
||||
if (newValues[1] > this.max) {
|
||||
newValues[1] = this.max
|
||||
}
|
||||
if (newValues[0] > newValues[1]) {
|
||||
if (newValues[0] === this.values[0]) {
|
||||
newValues[1] = newValues[0]
|
||||
} else {
|
||||
newValues[0] = newValues[1]
|
||||
}
|
||||
}
|
||||
|
||||
if (this.values[0] === newValues[0] && this.values[1] === newValues[1]) {
|
||||
return
|
||||
}
|
||||
|
||||
this.values = newValues
|
||||
|
||||
if (this.range) {
|
||||
this.$emit('input', this.values)
|
||||
} else {
|
||||
this.$emit('input', this.values[0])
|
||||
}
|
||||
},
|
||||
$_startLowerDrag(e) {
|
||||
if (this.disabled) {
|
||||
return
|
||||
}
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
e = e.changedTouches ? e.changedTouches[0] : e
|
||||
this.startDragMousePos = e.pageX
|
||||
this.startVal = this.values[0]
|
||||
this.isDragingUpper = false
|
||||
this.isDragging = true
|
||||
window.addEventListener('mousemove', this.$_onDrag)
|
||||
window.addEventListener('touchmove', this.$_onDrag)
|
||||
window.addEventListener('mouseup', this.$_onUp)
|
||||
window.addEventListener('touchend', this.$_onUp)
|
||||
},
|
||||
$_startUpperDrag(e) {
|
||||
if (this.disabled) {
|
||||
return
|
||||
}
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
e = e.changedTouches ? e.changedTouches[0] : e
|
||||
this.startDragMousePos = e.pageX
|
||||
this.startVal = this.values[1]
|
||||
this.isDragingUpper = true
|
||||
this.isDragging = true
|
||||
window.addEventListener('mousemove', this.$_onDrag)
|
||||
window.addEventListener('touchmove', this.$_onDrag)
|
||||
window.addEventListener('mouseup', this.$_onUp)
|
||||
window.addEventListener('touchend', this.$_onUp)
|
||||
},
|
||||
$_onDrag(e) {
|
||||
if (this.disabled) {
|
||||
return
|
||||
}
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
if (!this.isDragging) {
|
||||
return
|
||||
}
|
||||
e = e.changedTouches ? e.changedTouches[0] : e
|
||||
window.requestAnimationFrame(() => {
|
||||
let diff = (e.pageX - this.startDragMousePos) / this.$el.offsetWidth * (this.max - this.min)
|
||||
let nextVal = this.startVal + diff
|
||||
if (this.isDragging) {
|
||||
if (this.isDragingUpper) {
|
||||
this.$_updateValue([null, nextVal])
|
||||
} else {
|
||||
this.$_updateValue([nextVal, null])
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
$_onUp(e) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
this.$_stopDrag()
|
||||
},
|
||||
$_stopDrag() {
|
||||
this.isDragging = false
|
||||
this.isDragingUpper = false
|
||||
window.removeEventListener('mousemove', this.$_onDrag)
|
||||
window.removeEventListener('touchmove', this.$_onDrag)
|
||||
window.removeEventListener('mouseup', this.$_onUp)
|
||||
window.removeEventListener('touchend', this.$_onUp)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus">
|
||||
.md-slider
|
||||
position relative
|
||||
width 100%
|
||||
height 60px
|
||||
&::before
|
||||
content ''
|
||||
position absolute
|
||||
top 28px
|
||||
left 0
|
||||
right 0
|
||||
height 4px
|
||||
border-radius 2px
|
||||
background-color slider-bg-base
|
||||
&.is-disabled
|
||||
.md-slider-bar
|
||||
opacity 0.35
|
||||
.md-slider-handle span
|
||||
cursor: not-allowed
|
||||
|
||||
.md-slider-bar
|
||||
position absolute
|
||||
left 0
|
||||
top 28px
|
||||
height 4px
|
||||
background-color slider-bg-tap
|
||||
border-radius 2px
|
||||
z-index 5
|
||||
|
||||
.md-slider-handle
|
||||
position absolute
|
||||
top 10px
|
||||
left 0
|
||||
margin-left -20px
|
||||
z-index 15
|
||||
overflow visible
|
||||
&::after
|
||||
content attr(data-hint)
|
||||
color tip-color
|
||||
position absolute
|
||||
pointer-events none
|
||||
opacity 0
|
||||
visibility hidden
|
||||
z-index 15
|
||||
font-size font-minor-normal
|
||||
line-height 1.25
|
||||
padding 8px 16px
|
||||
border-radius radius-normal
|
||||
background-color tip-fill
|
||||
white-space nowrap
|
||||
left 50%
|
||||
bottom 100%
|
||||
margin-bottom 20px
|
||||
transform translateX(-50%)
|
||||
|
||||
&:hover::after,
|
||||
&:active::after
|
||||
opacity 1
|
||||
visibility visible
|
||||
|
||||
&.is-higher
|
||||
z-index 20
|
||||
&.is-active span
|
||||
transform scale(1.3)
|
||||
span
|
||||
display block
|
||||
cursor pointer
|
||||
width 40px
|
||||
height 40px
|
||||
background-color slider-handle-bg
|
||||
border-radius 50%
|
||||
box-shadow 0 1px 2px rgba(0, 0, 0, 0.2)
|
||||
transition transform 200ms
|
||||
</style>
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
import Slider from '../index'
|
||||
import {mount} from 'avoriaz'
|
||||
|
||||
describe('Slider', () => {
|
||||
let wrapper
|
||||
|
||||
afterEach(() => {
|
||||
wrapper && wrapper.destroy()
|
||||
})
|
||||
|
||||
it('create a simple slider', () => {
|
||||
wrapper = mount(Slider)
|
||||
|
||||
expect(wrapper.hasClass('md-slider')).to.be.true
|
||||
})
|
||||
})
|
||||
|
|
@ -227,6 +227,11 @@
|
|||
"path": "/radio",
|
||||
"icon": "radio",
|
||||
"text": "单选框"
|
||||
}, {
|
||||
"name": "Slider",
|
||||
"path": "/slider",
|
||||
"icon": "slider",
|
||||
"text": "滑块"
|
||||
}, {
|
||||
"name": "Switch",
|
||||
"path": "/switch",
|
||||
|
|
|
|||
|
|
@ -45,4 +45,4 @@ export {default as WaterMark} from '../components/water-mark/demo'
|
|||
export {default as Transition} from '../components/transition/demo'
|
||||
export {default as DetailItem} from '../components/detail-item/demo'
|
||||
export {default as Overlay} from '../components/overlay/demo'
|
||||
/* @init<%export {default as ${componentNameUpper}} from '../components/${componentName}/demo'%> */
|
||||
export {default as Slider} from '../components/slider/demo'
/* @init<%export {default as ${componentNameUpper}} from '../components/${componentName}/demo'%> */
|
||||
|
|
|
|||
|
|
@ -45,4 +45,4 @@ export const Bill = r => require.ensure([], () => r(require('../components/bill
|
|||
export const WaterMark = r => require.ensure([], () => r(require('../components/water-mark/demo')), 'water-mark')
|
||||
export const Transition = r => require.ensure([], () => r(require('../components/transition/demo')), 'transition')
|
||||
export const DetailItem = r => require.ensure([], () => r(require('../components/detail-item/demo')), 'detail-item')
|
||||
export const Overlay = r => require.ensure([], () => r(require('../components/overlay/demo')), 'overlay')
/* @init<%export const ${componentNameUpper} = r => require.ensure([], () => r(require('../components/${componentName}/demo')), '${componentName}')%> */
|
||||
export const Overlay = r => require.ensure([], () => r(require('../components/overlay/demo')), 'overlay')
export const Slider = r => require.ensure([], () => r(require('../components/slider/demo')), 'slider')
/* @init<%export const ${componentNameUpper} = r => require.ensure([], () => r(require('../components/${componentName}/demo')), '${componentName}')%> */
|
||||
|
|
|
|||
Loading…
Reference in New Issue