diff --git a/components/index.js b/components/index.js index 5b19a3170..9945987f9 100644 --- a/components/index.js +++ b/components/index.js @@ -133,3 +133,5 @@ const { MonthPicker, RangePicker, WeekPicker } = DatePicker export { DatePicker, MonthPicker, RangePicker, WeekPicker } export { default as version } from './version' + +export { default as Slider } from './slider' diff --git a/components/slider/demo/basic.md b/components/slider/demo/basic.md new file mode 100644 index 000000000..ef36d92d6 --- /dev/null +++ b/components/slider/demo/basic.md @@ -0,0 +1,38 @@ + +#### 基本 +基本滑动条。当 `range` 为 `true` 时,渲染为双滑块。当 `disabled` 为 `true` 时,滑块处于不可用状态。 + + + +#### Basic +Basic slider. When `range` is `true`, display as dual thumb mode. When `disable` is `true`, the slider will not be interactable. + + +```html + + + +``` diff --git a/components/slider/demo/event.md b/components/slider/demo/event.md new file mode 100644 index 000000000..c49e2888b --- /dev/null +++ b/components/slider/demo/event.md @@ -0,0 +1,42 @@ + +#### 事件 +当 Slider 的值发生改变时,会触发 `onChange` 事件,并把改变后的值作为参数传入。在 `onmouseup` 时,会触发 `onAfterChange` 事件,并把当前值作为参数传入。 + + + +#### Event +The `onChange` callback function will fire when the user changes the slider's value. +The `onAfterChange` callback function will fire when `onmouseup` fired. + + +```html + + + +``` + diff --git a/components/slider/demo/icon-slider.md b/components/slider/demo/icon-slider.md new file mode 100644 index 000000000..722bc8db5 --- /dev/null +++ b/components/slider/demo/icon-slider.md @@ -0,0 +1,74 @@ + +#### 带 icon 的滑块 +滑块左右可以设置图标来表达业务含义。 + + + +#### Slider with icon +You can add an icon beside the slider to make it meaningful. + + +```html + + + +``` + diff --git a/components/slider/demo/index.vue b/components/slider/demo/index.vue new file mode 100644 index 000000000..a04488de3 --- /dev/null +++ b/components/slider/demo/index.vue @@ -0,0 +1,59 @@ + diff --git a/components/slider/demo/input-number.md b/components/slider/demo/input-number.md new file mode 100644 index 000000000..3a8e51c45 --- /dev/null +++ b/components/slider/demo/input-number.md @@ -0,0 +1,59 @@ + +#### 带输入框的滑块 +和 [数字输入框](#/cn/components/input-number/) 组件保持同步。 + + + +#### Slider with InputNumber +Synchronize with [InptNumber](#/us/components/input-number/) component. + + +```html + + + +``` + diff --git a/components/slider/demo/mark.md b/components/slider/demo/mark.md new file mode 100644 index 000000000..a4863ac3b --- /dev/null +++ b/components/slider/demo/mark.md @@ -0,0 +1,67 @@ + +#### 带标签的滑块 +使用 `marks` 属性标注分段式滑块,使用 `value` / `defaultValue` 指定滑块位置。当 `included=false` 时,表明不同标记间为并列关系。当 `step=null` 时,Slider 的可选值仅有 `marks` 标出来的部分。 + + + +#### Graduated slider +Using `marks` property to mark a graduated slider, use `value` or `defaultValue` to specify the position of thumb. +When `included` is false, means that different thumbs are coordinative. +when `step` is null, users can only slide the thumbs onto marks. + + +```html + + + +``` + + diff --git a/components/slider/demo/tip-formatter.md b/components/slider/demo/tip-formatter.md new file mode 100644 index 000000000..83f0e428f --- /dev/null +++ b/components/slider/demo/tip-formatter.md @@ -0,0 +1,33 @@ + +#### 自定义提示 +使用 `tipFormatter` 可以格式化 `Tooltip` 的内容,设置 `tipFormatter={null}`,则隐藏 `Tooltip`。 + + + +#### Customerize tooltip +Use `tipFormatter` to format content of `Toolip`. If `tipFormatter` is null, hide it. + + +```html + + +``` + diff --git a/components/slider/demo/vertical.md b/components/slider/demo/vertical.md new file mode 100644 index 000000000..a393e4b6a --- /dev/null +++ b/components/slider/demo/vertical.md @@ -0,0 +1,55 @@ + +#### 垂直 +垂直方向的 Slider。 + + + +#### Vertical +The vertical Slider. + + +```html + + + +``` + diff --git a/components/slider/index.en-US.md b/components/slider/index.en-US.md new file mode 100644 index 000000000..090b3d649 --- /dev/null +++ b/components/slider/index.en-US.md @@ -0,0 +1,31 @@ +## API + +| Property | Description | Type | Default | +| -------- | ----------- | ---- | ------- | +| autoFocus | get focus when component mounted | boolean | false | +| defaultValue | The default value of slider. When `range` is `false`, use `number`, otherwise, use `[number, number]` | number\|number\[] | 0 or [0, 0] | +| disabled | If true, the slider will not be interactable. | boolean | false | +| dots | Whether the thumb can drag over tick only. | boolean | false | +| included | Make effect when `marks` not null,`true` means containment and `false` means coordinative | boolean | true | +| marks | Tick mark of Slider, type of key must be `number`, and must in closed interval [min, max] ,each mark can declare its own style. | object | { number: string\|VNode } or { number: { style: object, label: string\|VNode } } | +| max | The maximum value the slider can slide to | number | 100 | +| min | The minimum value the slider can slide to. | number | 0 | +| range | dual thumb mode | boolean | false | +| step | The granularity the slider can step through values. Must greater than 0, and be divided by (max - min) . When `marks` no null, `step` can be `null`. | number\|null | 1 | +| tipFormatter | Slider will pass its value to `tipFormatter`, and display its value in Tooltip, and hide Tooltip when return value is null. | Function\|null | IDENTITY | +| value(v-model) | The value of slider. When `range` is `false`, use `number`, otherwise, use `[number, number]` | number\|number\[] | | +| vertical | If true, the slider will be vertical. | Boolean | false | + + +### events +| Events Name | Description | Arguments | +| --- | --- | --- | +| afterChange | Fire when `mouseup` is fired. | Function(value) | NOOP | +| change | Callback function that is fired when the user changes the slider's value. | Function(value) | NOOP | + +## Methods + +| Name | Description | +| ---- | ----------- | +| blur() | remove focus | +| focus() | get focus | diff --git a/components/slider/index.jsx b/components/slider/index.jsx new file mode 100644 index 000000000..dc235a75d --- /dev/null +++ b/components/slider/index.jsx @@ -0,0 +1,145 @@ +import PropTypes from '../_util/vue-types' +import BaseMixin from '../_util/BaseMixin' +import { getOptionProps } from '../_util/props-util' +import VcSlider from '../vc-slider/src/Slider' +import VcRange from '../vc-slider/src/Range' +import VcHandle from '../vc-slider/src/Handle' +import Tooltip from '../tooltip' + +// export interface SliderMarks { +// [key]: React.ReactNode | { +// style: React.CSSProperties, +// label: React.ReactNode, +// }; +// } +// const SliderMarks = PropTypes.shape({ +// style: PropTypes.object, +// label: PropTypes.any, +// }).loose + +export const SliderProps = () => ({ + prefixCls: PropTypes.string, + tooltipPrefixCls: PropTypes.string, + range: PropTypes.bool, + min: PropTypes.number, + max: PropTypes.number, + step: PropTypes.oneOfType([PropTypes.number, PropTypes.any]), + marks: PropTypes.object, + dots: PropTypes.bool, + value: PropTypes.oneOfType([ + PropTypes.number, + PropTypes.arrayOf(PropTypes.number), + ]), + defaultValue: PropTypes.oneOfType([ + PropTypes.number, + PropTypes.arrayOf(PropTypes.number), + ]), + included: PropTypes.bool, + disabled: PropTypes.bool, + vertical: PropTypes.bool, + tipFormatter: PropTypes.oneOfType([ + PropTypes.func, + PropTypes.object, + ]), + id: PropTypes.string, +}) + +export default { + name: 'Slider', + model: { + prop: 'value', + event: 'change', + }, + mixins: [BaseMixin], + props: { + ...SliderProps(), + prefixCls: PropTypes.string.def('ant-slider'), + tooltipPrefixCls: PropTypes.string.def('ant-tooltip'), + tipFormatter: PropTypes.oneOfType([ + PropTypes.func, + PropTypes.object, + ]).def(value => value.toString()), + }, + data () { + return { + visibles: {}, + } + }, + methods: { + toggleTooltipVisible (index, visible) { + this.setState(({ visibles }) => ({ + visibles: { + ...visibles, + [index]: visible, + }, + })) + }, + handleWithTooltip (h, { value, dragging, index, refStr, ...restProps }) { + const { tooltipPrefixCls, tipFormatter } = this.$props + const { visibles } = this + const visible = tipFormatter ? (visibles[index] || dragging) : false + + const tooltipProps = { + props: { + prefixCls: tooltipPrefixCls, + title: tipFormatter ? tipFormatter(value) : '', + visible, + placement: 'top', + transitionName: 'zoom-down', + }, + key: index, + } + const handleProps = { + props: { + value, + ...restProps, + }, + attrs: { + refStr, + }, + on: { + mouseenter: () => this.toggleTooltipVisible(index, true), + mouseleave: () => this.toggleTooltipVisible(index, false), + }, + } + return ( + + + + ) + }, + focus () { + this.$refs.sliderRef.focus() + }, + blur () { + this.$refs.sliderRef.focus() + }, + }, + render () { + const { range, ...restProps } = getOptionProps(this) + if (range) { + const vcRangeProps = { + props: { + handle: this.handleWithTooltip, + ...restProps, + }, + ref: 'sliderRef', + on: this.$listeners, + } + return + } + const vcSliderProps = { + props: { + handle: this.handleWithTooltip, + ...restProps, + }, + ref: 'sliderRef', + on: this.$listeners, + } + return + }, +} diff --git a/components/slider/index.zh-CN.md b/components/slider/index.zh-CN.md new file mode 100644 index 000000000..b8360fdce --- /dev/null +++ b/components/slider/index.zh-CN.md @@ -0,0 +1,30 @@ +## API + +| 参数 | 说明 | 类型 | 默认值 | +| --- | --- | --- | --- | +| autoFocus | 自动获取焦点 | boolean | false | +| defaultValue | 设置初始取值。当 `range` 为 `false` 时,使用 `number`,否则用 `[number, number]` | number\|number\[] | 0 or [0, 0] | +| disabled | 值为 `true` 时,滑块为禁用状态 | boolean | false | +| dots | 是否只能拖拽到刻度上 | boolean | false | +| included | `marks` 不为空对象时有效,值为 true 时表示值为包含关系,false 表示并列 | boolean | true | +| marks | 刻度标记,key 的类型必须为 `number` 且取值在闭区间 [min, max] 内,每个标签可以单独设置样式 | object | { number: string\|VNode } or { number: { style: object, label: string\|VNode } } | +| max | 最大值 | number | 100 | +| min | 最小值 | number | 0 | +| range | 双滑块模式 | boolean | false | +| step | 步长,取值必须大于 0,并且可被 (max - min) 整除。当 `marks` 不为空对象时,可以设置 `step` 为 `null`,此时 Slider 的可选值仅有 marks 标出来的部分。 | number\|null | 1 | +| tipFormatter | Slider 会把当前值传给 `tipFormatter`,并在 Tooltip 中显示 `tipFormatter` 的返回值,若为 null,则隐藏 Tooltip。 | Function\|null | IDENTITY | +| value(v-model) | 设置当前取值。当 `range` 为 `false` 时,使用 `number`,否则用 `[number, number]` | number\|number\[] | | +| vertical | 值为 `true` 时,Slider 为垂直方向 | Boolean | false | + +### 事件 +| 事件名称 | 说明 | 回调参数 | +| --- | --- | --- | +| afterChange | 与 `mouseup` 触发时机一致,把当前值作为参数传入。 | Function(value) | NOOP | +| change | 当 Slider 的值发生改变时,会触发 change 事件,并把改变后的值作为参数传入。 | Function(value) | NOOP | + +## 方法 + +| 名称 | 描述 | +| --- | --- | +| blur() | 移除焦点 | +| focus() | 获取焦点 | diff --git a/components/slider/style/index.js b/components/slider/style/index.js new file mode 100644 index 000000000..581a385b2 --- /dev/null +++ b/components/slider/style/index.js @@ -0,0 +1,5 @@ +import '../../style/index.less' +import './index.less' + +// style dependencies +import '../../tooltip/style' diff --git a/components/slider/style/index.less b/components/slider/style/index.less new file mode 100644 index 000000000..e7962cb3d --- /dev/null +++ b/components/slider/style/index.less @@ -0,0 +1,188 @@ +@import "../../style/themes/default"; +@import "../../style/mixins/index"; + +@slider-prefix-cls: ~"@{ant-prefix}-slider"; + +.@{slider-prefix-cls} { + .reset-component; + position: relative; + margin: @slider-margin; + padding: 4px 0; + height: 12px; + cursor: pointer; + + .vertical(); + + &-with-marks { + margin-bottom: 28px; + } + + &-rail { + position: absolute; + width: 100%; + height: 4px; + border-radius: 2px; + background-color: @slider-rail-background-color; + transition: background-color .3s; + } + + &-track { + position: absolute; + height: 4px; + border-radius: @border-radius-base; + background-color: @slider-track-background-color; + transition: background-color 0.3s ease; + } + + &-handle { + position: absolute; + margin-left: -7px; + margin-top: -5px; + width: 14px; + height: 14px; + cursor: pointer; + border-radius: 50%; + border: solid 2px @slider-handle-color; + background-color: @component-background; + transition: border-color .3s, transform .3s cubic-bezier(0.18, 0.89, 0.32, 1.28); + + &:focus { + border-color: @slider-handle-color-focus; + box-shadow: 0 0 0 5px @slider-handle-color-focus-shadow; + outline: none; + } + + &.@{ant-prefix}-tooltip-open { + border-color: @slider-handle-color-tooltip-open; + } + } + + &:hover { + .@{slider-prefix-cls}-rail { + background-color: @slider-rail-background-color-hover; + } + .@{slider-prefix-cls}-track { + background-color: @slider-track-background-color-hover; + } + .@{slider-prefix-cls}-handle:not(.@{ant-prefix}-tooltip-open) { + border-color: @slider-handle-color-hover; + } + } + + &-mark { + position: absolute; + top: 14px; + left: 0; + width: 100%; + font-size: @font-size-base; + } + + &-mark-text { + position: absolute; + display: inline-block; + vertical-align: middle; + text-align: center; + cursor: pointer; + color: @text-color-secondary; + + &-active { + color: @text-color; + } + } + + &-step { + position: absolute; + width: 100%; + height: 4px; + background: transparent; + } + + &-dot { + position: absolute; + top: -2px; + margin-left: -4px; + width: 8px; + height: 8px; + border: 2px solid @slider-dot-border-color; + background-color: @component-background; + cursor: pointer; + border-radius: 50%; + vertical-align: middle; + &:first-child { + margin-left: -4px; + } + &:last-child { + margin-left: -4px; + } + &-active { + border-color: @slider-dot-border-color-active; + } + } + + &-disabled { + cursor: not-allowed; + + .@{slider-prefix-cls}-track { + background-color: @slider-disabled-color !important; + } + + .@{slider-prefix-cls}-handle, + .@{slider-prefix-cls}-dot { + border-color: @slider-disabled-color !important; + background-color: @component-background; + cursor: not-allowed; + box-shadow: none; + } + + .@{slider-prefix-cls}-mark-text, + .@{slider-prefix-cls}-dot { + cursor: not-allowed !important; + } + } +} + +.vertical() { + &-vertical { + width: 12px; + height: 100%; + margin: 6px 10px; + padding: 0 4px; + + .@{slider-prefix-cls}-rail { + height: 100%; + width: 4px; + } + + .@{slider-prefix-cls}-track { + width: 4px; + } + + .@{slider-prefix-cls}-handle { + margin-left: -5px; + margin-bottom: -7px; + } + + .@{slider-prefix-cls}-mark { + top: 0; + left: 12px; + width: 18px; + height: 100%; + } + + .@{slider-prefix-cls}-mark-text { + left: 4px; + white-space: nowrap; + } + + .@{slider-prefix-cls}-step { + width: 4px; + height: 100%; + } + + .@{slider-prefix-cls}-dot { + top: auto; + left: 2px; + margin-bottom: -4px; + } + } +} diff --git a/components/style.js b/components/style.js index 94578a116..eedee02f4 100644 --- a/components/style.js +++ b/components/style.js @@ -34,3 +34,4 @@ import './steps/style' import './breadcrumb/style' import './calendar/style' import './date-picker/style' +import './slider/style' diff --git a/components/vc-slider/src/common/Marks.jsx b/components/vc-slider/src/common/Marks.jsx index 0adbcafdd..fae9c0ffe 100644 --- a/components/vc-slider/src/common/Marks.jsx +++ b/components/vc-slider/src/common/Marks.jsx @@ -1,4 +1,5 @@ import classNames from 'classnames' +import { isValidElement } from '../../../_util/props-util' const Marks = { functional: true, @@ -21,10 +22,8 @@ const Marks = { const range = max - min const elements = marksKeys.map(parseFloat).sort((a, b) => a - b).map(point => { const markPoint = marks[point] - // todo - // const markPointIsObject = typeof markPoint === 'object' && - // !React.isValidElement(markPoint) - const markPointIsObject = typeof markPoint === 'object' + const markPointIsObject = typeof markPoint === 'object' && + !isValidElement(markPoint) const markLabel = markPointIsObject ? markPoint.label : markPoint if (!markLabel && markLabel !== 0) { return null diff --git a/components/vc-slider/src/common/createSlider.jsx b/components/vc-slider/src/common/createSlider.jsx index bfd73d133..899da3afe 100644 --- a/components/vc-slider/src/common/createSlider.jsx +++ b/components/vc-slider/src/common/createSlider.jsx @@ -36,6 +36,10 @@ export default function createSlider (Component) { return { name: 'createSlider', mixins: [Component], + model: { + prop: 'value', + event: 'change', + }, props: initDefaultProps(propTypes, { ...Component.defaultProps, prefixCls: 'rc-slider', @@ -68,10 +72,6 @@ export default function createSlider (Component) { dotStyle: {}, activeDotStyle: {}, }), - model: { - prop: 'value', - event: 'change', - }, data () { if (process.env.NODE_ENV !== 'production') { const { step, max, min } = this @@ -152,7 +152,6 @@ export default function createSlider (Component) { } }, onBlur (e) { - console.dir(e) this.onEnd(e) this.$emit('blur', e) }, diff --git a/examples/routes.js b/examples/routes.js index 9ea148250..49ad2a263 100644 --- a/examples/routes.js +++ b/examples/routes.js @@ -3,7 +3,7 @@ const AsyncComp = () => { const hashs = window.location.hash.split('/') const d = hashs[hashs.length - 1] return { - component: import(`../components/vc-table/demo/${d}`), + component: import(`../components/slider/demo/${d}`), } } export default [