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 [