diff --git a/components/vc-rate/assets/index.less b/components/vc-rate/assets/index.less new file mode 100644 index 000000000..f068f23d2 --- /dev/null +++ b/components/vc-rate/assets/index.less @@ -0,0 +1,96 @@ +@rate-prefix-cls: rc-rate; +@rate-star-color: #f5a623; +@font-size-base: 13px; + +.@{rate-prefix-cls} { + margin: 0; + padding: 0; + list-style: none; + font-size: 18px; + display: inline-block; + vertical-align: middle; + font-weight: normal; + font-style: normal; + outline: none; + + &-disabled &-star { + &:before, + &-content:before { + cursor: default; + } + &:hover { + transform: scale(1); + } + } + + &-star { + margin: 0; + padding: 0; + display: inline-block; + margin-right: 8px; + position: relative; + transition: all .3s; + color: #e9e9e9; + cursor: pointer; + line-height: 1.5; + + &-first, + &-second { + transition: all .3s; + } + + &-focused, &:hover { + transform: scale(1.1); + } + + &-first { + position: absolute; + left: 0; + top: 0; + width: 50%; + height: 100%; + overflow: hidden; + opacity: 0; + } + + &-half &-first, + &-half &-second { + opacity: 1; + } + + &-half &-first, + &-full &-second { + color: @rate-star-color; + } + + &-half:hover &-first, + &-full:hover &-second { + color: tint(@rate-star-color,30%); + } + } +} + +@icon-url: "//at.alicdn.com/t/font_r5u29ls31bgldi"; + +@font-face { + font-family: 'anticon'; + src: url('@{icon-url}.eot'); /* IE9*/ + src: url('@{icon-url}.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ url('@{icon-url}.woff') format('woff'), /* chrome、firefox */ url('@{icon-url}.ttf') format('truetype'), /* chrome、firefox、opera、Safari, Android, iOS 4.2+*/ url('@{icon-url}.svg#iconfont') format('svg'); /* iOS 4.1- */ +} + +.anticon { + font-style: normal; + vertical-align: baseline; + text-align: center; + text-transform: none; + line-height: 1; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + &:before { + display: block; + font-family: "anticon" !important; + } +} + +.anticon-star:before { content: "\e660"; }; diff --git a/components/vc-rate/demo/simple.jsx b/components/vc-rate/demo/simple.jsx new file mode 100644 index 000000000..2833d3872 --- /dev/null +++ b/components/vc-rate/demo/simple.jsx @@ -0,0 +1,84 @@ +import Rate from '../index' +import '../assets/index.less' + +export default { + data () { + return { + } + }, + methods: { + onChange (v) { + console.log('selected star', v) + }, + onFocus () { + console.dir('focus') + }, + }, + render () { + const { onChange, onFocus } = this + const scopedSlots = ( + + ) + const rateProps = { + props: { + defaultValue: 2.5, + allowHalf: true, + }, + on: { + change: onChange, + }, + style: { + fontSize: '50px', marginTop: '24px', + }, + scopedSlots: { + character: scopedSlots, + }, + } + const rateProps1 = { + props: { + defaultValue: 2, + }, + on: { + change: onChange, + }, + style: { + fontSize: '50px', marginTop: '24px', + }, + scopedSlots: { + character: scopedSlots, + }, + } + return ( +
+ +
+ +
+ + +
+ + +
+ ) + }, +} diff --git a/components/vc-rate/index.js b/components/vc-rate/index.js new file mode 100644 index 000000000..a8c07fe9b --- /dev/null +++ b/components/vc-rate/index.js @@ -0,0 +1,3 @@ +// export this package's api +import Rate from './src/' +export default Rate diff --git a/components/vc-rate/src/Rate.jsx b/components/vc-rate/src/Rate.jsx new file mode 100644 index 000000000..438e33d0c --- /dev/null +++ b/components/vc-rate/src/Rate.jsx @@ -0,0 +1,216 @@ +import PropTypes from '../../_util/vue-types' +import classNames from 'classnames' +import KeyCode from '../../_util/KeyCode' +import { initDefaultProps, hasProp, getOptionProps } from '../../_util/props-util' +import BaseMixin from '../../_util/BaseMixin' +import { getOffsetLeft } from './util' +import Star from './Star' + +const rateProps = { + disabled: PropTypes.bool, + value: PropTypes.number, + defaultValue: PropTypes.number, + count: PropTypes.number, + allowHalf: PropTypes.bool, + allowClear: PropTypes.bool, + prefixCls: PropTypes.string, + character: PropTypes.any, + tabIndex: PropTypes.number, + autoFocus: PropTypes.bool, +} + +export default { + name: 'Rate', + mixins: [BaseMixin], + props: initDefaultProps(rateProps, { + defaultValue: 0, + count: 5, + allowHalf: false, + allowClear: true, + prefixCls: 'rc-rate', + tabIndex: 0, + character: '★', + }), + modal: { + prop: 'value', + event: 'change', + }, + data () { + let value = this.value + if (!hasProp(this, 'value')) { + value = this.defaultValue + } + return { + sValue: value, + focused: false, + cleanedValue: null, + hoverValue: undefined, + } + }, + mounted () { + this.$nextTick(() => { + if (this.autoFocus && !this.disabled) { + this.focus() + } + }) + }, + watch: { + value (val) { + this.setState({ + sValue: val, + }) + }, + }, + methods: { + onHover (event, index) { + const hoverValue = this.getStarValue(index, event.pageX) + const { cleanedValue } = this + if (hoverValue !== cleanedValue) { + this.setState({ + hoverValue, + cleanedValue: null, + }) + } + this.$emit('hover-change', hoverValue) + }, + onMouseLeave () { + this.setState({ + hoverValue: undefined, + cleanedValue: null, + }) + this.$emit('hover-change', undefined) + }, + onClick (event, index) { + const value = this.getStarValue(index, event.pageX) + let isReset = false + if (this.allowClear) { + isReset = value === this.sValue + } + this.onMouseLeave(true) + this.changeValue(isReset ? 0 : value) + this.setState({ + cleanedValue: isReset ? value : null, + }) + }, + onFocus () { + this.setState({ + focused: true, + }) + this.$emit('focus') + }, + onBlur () { + this.setState({ + focused: false, + }) + this.$emit('blur') + }, + onKeyDown (event) { + const { keyCode } = event + const { count, allowHalf } = this + let { sValue } = this + if (keyCode === KeyCode.RIGHT && sValue < count) { + if (allowHalf) { + sValue += 0.5 + } else { + sValue += 1 + } + this.changeValue(sValue) + event.preventDefault() + } else if (keyCode === KeyCode.LEFT && sValue > 0) { + if (allowHalf) { + sValue -= 0.5 + } else { + sValue -= 1 + } + this.changeValue(sValue) + event.preventDefault() + } + this.$emit('keydown', event) + }, + getStarDOM (index) { + return this.$refs['stars' + index].$el + }, + getStarValue (index, x) { + let value = index + 1 + if (this.allowHalf) { + const starEle = this.getStarDOM(index) + const leftDis = getOffsetLeft(starEle) + const width = starEle.clientWidth + if ((x - leftDis) < width / 2) { + value -= 0.5 + } + } + return value + }, + focus () { + if (!this.disabled) { + this.$refs.rateRef.focus() + } + }, + // blur () { + // if (!this.disabled) { + // this.$refs.rateRef.blur() + // } + // }, + changeValue (value) { + if (!hasProp(this, 'value')) { + this.setState({ + sValue: value, + }) + } + this.$emit('change', value) + }, + }, + render () { + const { + count, + allowHalf, + prefixCls, + disabled, + character, + tabIndex, + } = getOptionProps(this) + const { sValue, hoverValue, focused } = this + const stars = [] + const disabledClass = disabled ? `${prefixCls}-disabled` : '' + const slotCharacter = this.$scopedSlots.character + for (let index = 0; index < count; index++) { + const starProps = { + props: { + index, + disabled, + prefixCls: `${prefixCls}-star`, + allowHalf, + value: hoverValue === undefined ? sValue : hoverValue, + character: slotCharacter === undefined ? character : undefined, + focused, + }, + on: { + click: this.onClick, + hover: this.onHover, + }, + key: index, + ref: `stars${index}`, + scopedSlots: this.$scopedSlots, + } + stars.push( + + ) + } + return ( + + ) + }, +} diff --git a/components/vc-rate/src/Star.jsx b/components/vc-rate/src/Star.jsx new file mode 100644 index 000000000..eb169a182 --- /dev/null +++ b/components/vc-rate/src/Star.jsx @@ -0,0 +1,60 @@ +import PropTypes from '../../_util/vue-types' + +export default { + name: 'Star', + props: { + value: PropTypes.number, + index: PropTypes.number, + prefixCls: PropTypes.string, + allowHalf: PropTypes.bool, + disabled: PropTypes.bool, + character: PropTypes.any, + focused: PropTypes.bool, + }, + methods: { + onHover (e) { + const { index } = this + this.$emit('hover', e, index) + }, + onClick (e) { + const { index } = this + this.$emit('click', e, index) + }, + getClassName () { + const { prefixCls, index, value, allowHalf, focused } = this + const starValue = index + 1 + let className = prefixCls + if (value === 0 && index === 0 && focused) { + className += ` ${prefixCls}-focused` + } else if (allowHalf && value + 0.5 === starValue) { + className += ` ${prefixCls}-half ${prefixCls}-active` + if (focused) { + className += ` ${prefixCls}-focused` + } + } else { + className += starValue <= value ? ` ${prefixCls}-full` : ` ${prefixCls}-zero` + if (starValue === value && focused) { + className += ` ${prefixCls}-focused` + } + } + return className + }, + }, + render () { + const { onHover, onClick, disabled, prefixCls } = this + let character = this.character + if (character === undefined) { + character = this.$scopedSlots.character + } + return ( +
  • +
    {character}
    +
    {character}
    +
  • + ) + }, +} diff --git a/components/vc-rate/src/index.js b/components/vc-rate/src/index.js new file mode 100644 index 000000000..5ea33437f --- /dev/null +++ b/components/vc-rate/src/index.js @@ -0,0 +1,2 @@ +import Rate from './Rate' +export default Rate diff --git a/components/vc-rate/src/util.js b/components/vc-rate/src/util.js new file mode 100644 index 000000000..dac92774b --- /dev/null +++ b/components/vc-rate/src/util.js @@ -0,0 +1,39 @@ +function getScroll (w, top) { + let ret = top ? w.pageYOffset : w.pageXOffset + const method = top ? 'scrollTop' : 'scrollLeft' + if (typeof ret !== 'number') { + const d = w.document + // ie6,7,8 standard mode + ret = d.documentElement[method] + if (typeof ret !== 'number') { + // quirks mode + ret = d.body[method] + } + } + return ret +} + +function getClientPosition (elem) { + let x + let y + const doc = elem.ownerDocument + const body = doc.body + const docElem = doc && doc.documentElement + const box = elem.getBoundingClientRect() + x = box.left + y = box.top + x -= docElem.clientLeft || body.clientLeft || 0 + y -= docElem.clientTop || body.clientTop || 0 + return { + left: x, + top: y, + } +} + +export function getOffsetLeft (el) { + const pos = getClientPosition(el) + const doc = el.ownerDocument + const w = doc.defaultView || doc.parentWindow + pos.left += getScroll(w) + return pos.left +} diff --git a/examples/routes.js b/examples/routes.js index 736100f67..cd3383169 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/locale-provider/demo/${d}`), + component: import(`../components/vc-rate/demo/${d}`), } } export default [