diff --git a/assets/index.less b/assets/index.less
index 62ca71c..9d13f1f 100644
--- a/assets/index.less
+++ b/assets/index.less
@@ -45,11 +45,6 @@
float: right;
}
- &-first,
- &-second {
- transition: all .3s;
- }
-
&-focused, &:hover {
transform: scale(1.1);
}
@@ -58,10 +53,11 @@
position: absolute;
left: 0;
top: 0;
- width: 50%;
+ width: 0%;
height: 100%;
overflow: hidden;
- opacity: 0;
+ opacity: 1;
+ color: @rate-star-color;
.@{rate-prefix-cls}-rtl & {
right: 0;
@@ -73,16 +69,6 @@
&-half &-second {
opacity: 1;
}
-
- &-half &-first,
- &-full &-second {
- color: @rate-star-color;
- }
-
- &-half:hover &-first,
- &-full:hover &-second {
- color: tint(@rate-star-color,30%);
- }
}
}
diff --git a/docs/examples/simple.tsx b/docs/examples/simple.tsx
index f550b66..9b35ae9 100644
--- a/docs/examples/simple.tsx
+++ b/docs/examples/simple.tsx
@@ -1,6 +1,6 @@
/* eslint no-console: 0 */
-import React from 'react';
import Rate from 'rc-rate';
+import React from 'react';
import '../../assets/index.less';
function onChange(v: number) {
@@ -59,5 +59,14 @@ export default () => (
style={{ fontSize: 50, marginTop: 24 }}
character={}
/>
+
Precision
+ }
+ />
);
diff --git a/src/Rate.tsx b/src/Rate.tsx
index 46602ba..88e7782 100644
--- a/src/Rate.tsx
+++ b/src/Rate.tsx
@@ -6,7 +6,7 @@ import React from 'react';
import type { StarProps } from './Star';
import Star from './Star';
import useRefs from './useRefs';
-import { getOffsetLeft } from './util';
+import { clamp, getBoundingClientRect, roundValueToPrecision } from './util';
export interface RateProps
extends Pick {
@@ -32,6 +32,10 @@ export interface RateProps
* @default true
*/
keyboard?: boolean;
+ /**
+ * Define the minimum increment value change
+ */
+ precision?: number;
}
export interface RateRef {
@@ -49,9 +53,14 @@ function Rate(props: RateProps, ref: React.Ref) {
defaultValue,
value: propValue,
count = 5,
+ /**
+ * allow half star
+ * @deprecated Since precision has been implemented, `allowHalf` is no longer needed.
+ */
allowHalf = false,
allowClear = true,
keyboard = true,
+ precision = 1,
// Display
character = '★',
@@ -76,6 +85,8 @@ function Rate(props: RateProps, ref: React.Ref) {
const [getStarRef, setStarRef] = useRefs();
const rateRef = React.useRef(null);
+ const mergedPrecision = allowHalf ? 0.5 : precision > 0 ? precision : 1;
+ const reverse = direction === 'rtl';
// ============================ Ref =============================
const triggerFocus = () => {
@@ -99,19 +110,36 @@ function Rate(props: RateProps, ref: React.Ref) {
});
const [cleanedValue, setCleanedValue] = useMergedState(null);
- const getStarValue = (index: number, x: number) => {
- const reverse = direction === 'rtl';
- let starValue = index + 1;
- if (allowHalf) {
- const starEle = getStarRef(index);
- const leftDis = getOffsetLeft(starEle);
- const width = starEle.clientWidth;
- if (reverse && x - leftDis > width / 2) {
- starValue -= 0.5;
- } else if (!reverse && x - leftDis < width / 2) {
- starValue -= 0.5;
- }
+ const calculatePercentage = (delta: number, left: number, right: number) => {
+ return (reverse ? right - delta : delta - left) / (right - left);
+ };
+
+ const dealWithMergedPrecision = (delta: number) => {
+ const rootNode = rateRef.current;
+ const { right, left } = getBoundingClientRect(rootNode);
+ const percentage = calculatePercentage(delta, left, right);
+
+ let newHover = roundValueToPrecision(count * percentage + mergedPrecision / 2, mergedPrecision);
+ newHover = clamp(newHover, mergedPrecision, count);
+ return newHover;
+ };
+
+ const getStarValue = (index: number, delta: number) => {
+ let starValue = index;
+
+ const starEle = getStarRef(index);
+ const { left, right } = getBoundingClientRect(starEle);
+ if (!left || !right) return allowHalf ? starValue + 0.5 : starValue + 1;
+
+ const percentage = calculatePercentage(delta, left, right);
+ let roundedValue = roundValueToPrecision(percentage + mergedPrecision / 2, mergedPrecision);
+ roundedValue = clamp(roundedValue, mergedPrecision, 1);
+ starValue += roundedValue;
+
+ if (mergedPrecision > 1) {
+ return dealWithMergedPrecision(delta);
}
+
return starValue;
};
@@ -138,7 +166,7 @@ function Rate(props: RateProps, ref: React.Ref) {
const [hoverValue, setHoverValue] = React.useState(null);
const onHover = (event: React.MouseEvent, index: number) => {
- const nextHoverValue = getStarValue(index, event.pageX);
+ const nextHoverValue = getStarValue(index, event.clientX);
if (nextHoverValue !== cleanedValue) {
setHoverValue(nextHoverValue);
setCleanedValue(null);
@@ -159,7 +187,7 @@ function Rate(props: RateProps, ref: React.Ref) {
// =========================== Click ============================
const onClick = (event: React.MouseEvent | React.KeyboardEvent, index: number) => {
- const newValue = getStarValue(index, (event as React.MouseEvent).pageX);
+ const newValue = getStarValue(index, (event as React.MouseEvent).clientX);
let isReset = false;
if (allowClear) {
isReset = newValue === value;
@@ -171,8 +199,7 @@ function Rate(props: RateProps, ref: React.Ref) {
const onInternalKeyDown: React.KeyboardEventHandler = (event) => {
const { keyCode } = event;
- const reverse = direction === 'rtl';
- const step = allowHalf ? 0.5 : 1;
+ const step = mergedPrecision;
if (keyboard) {
if (keyCode === KeyCode.RIGHT && value < count && !reverse) {
diff --git a/src/Star.tsx b/src/Star.tsx
index 7243069..ce0fb20 100644
--- a/src/Star.tsx
+++ b/src/Star.tsx
@@ -1,6 +1,6 @@
-import React from 'react';
-import KeyCode from 'rc-util/lib/KeyCode';
import classNames from 'classnames';
+import KeyCode from 'rc-util/lib/KeyCode';
+import React from 'react';
export interface StarProps {
value?: number;
@@ -73,7 +73,9 @@ function Star(props: StarProps, ref: React.Ref) {
classNameList.add(`${prefixCls}-focused`);
}
}
-
+ const memoWidth = React.useMemo(() => {
+ return Math.max(0, Math.min((value - index) * 100, 100));
+ }, [index, value]);
// >>>>> Node
const characterNode = typeof character === 'function' ? character(props) : character;
let start: React.ReactNode = (
@@ -88,7 +90,14 @@ function Star(props: StarProps, ref: React.Ref) {
aria-setsize={count}
tabIndex={disabled ? -1 : 0}
>
- {characterNode}
+
+ {characterNode}
+
{characterNode}
diff --git a/src/util.ts b/src/util.ts
index 1bc62e2..744a4d8 100644
--- a/src/util.ts
+++ b/src/util.ts
@@ -38,3 +38,29 @@ export function getOffsetLeft(el: HTMLElement) {
pos.left += getScroll(w);
return pos.left;
}
+
+export const getBoundingClientRect = (element: HTMLElement) => {
+ return element.getBoundingClientRect();
+};
+
+function getDecimalPrecision(value: number) {
+ const decimalPart = value.toString().split('.')[1];
+ return decimalPart ? decimalPart.length : 0;
+}
+
+export function roundValueToPrecision(value: number, precision: number) {
+ if (value == null) {
+ return value;
+ }
+
+ const nearest = Math.round(value / precision) * precision;
+ return Number(nearest.toFixed(getDecimalPrecision(precision)));
+}
+
+export function clamp(
+ val: number,
+ min: number = Number.MIN_SAFE_INTEGER,
+ max: number = Number.MAX_SAFE_INTEGER,
+): number {
+ return Math.max(min, Math.min(val, max));
+}
diff --git a/tests/__snapshots__/simple.spec.js.snap b/tests/__snapshots__/simple.spec.js.snap
index 43deb96..9617d0b 100644
--- a/tests/__snapshots__/simple.spec.js.snap
+++ b/tests/__snapshots__/simple.spec.js.snap
@@ -17,6 +17,7 @@ exports[`rate allowHalf render works 1`] = `
>
★
@@ -39,6 +40,7 @@ exports[`rate allowHalf render works 1`] = `
>
★
@@ -61,6 +63,7 @@ exports[`rate allowHalf render works 1`] = `
>
★
@@ -91,6 +94,7 @@ exports[`rate allowHalf render works in RTL 1`] = `
>
★
@@ -113,6 +117,7 @@ exports[`rate allowHalf render works in RTL 1`] = `
>
★
@@ -135,6 +140,7 @@ exports[`rate allowHalf render works in RTL 1`] = `
>
★
@@ -165,6 +171,7 @@ exports[`rate allowHalf render works more than half 1`] = `
>
★
@@ -187,6 +194,7 @@ exports[`rate allowHalf render works more than half 1`] = `
>
★
@@ -209,6 +217,7 @@ exports[`rate allowHalf render works more than half 1`] = `
>
★
@@ -239,6 +248,7 @@ exports[`rate full render works 1`] = `
>
★
@@ -261,6 +271,7 @@ exports[`rate full render works 1`] = `
>
★
@@ -283,6 +294,7 @@ exports[`rate full render works 1`] = `
>
★
@@ -313,6 +325,7 @@ exports[`rate full render works in RTL 1`] = `
>
★
@@ -335,6 +348,7 @@ exports[`rate full render works in RTL 1`] = `
>
★
@@ -357,6 +371,7 @@ exports[`rate full render works in RTL 1`] = `
>
★
@@ -387,6 +402,7 @@ exports[`rate full render works with character function 1`] = `
>
1
@@ -409,6 +425,7 @@ exports[`rate full render works with character function 1`] = `
>
2
@@ -431,6 +448,7 @@ exports[`rate full render works with character function 1`] = `
>
3
@@ -461,6 +479,7 @@ exports[`rate full render works with character node 1`] = `
>
1
@@ -483,6 +502,7 @@ exports[`rate full render works with character node 1`] = `
>
1
@@ -505,6 +525,7 @@ exports[`rate full render works with character node 1`] = `
>
1
diff --git a/tests/simple.spec.js b/tests/simple.spec.js
index 77c6271..b10afc8 100644
--- a/tests/simple.spec.js
+++ b/tests/simple.spec.js
@@ -1,6 +1,6 @@
-import React from 'react';
-import { render, mount } from 'enzyme';
+import { mount, render } from 'enzyme';
import KeyCode from 'rc-util/lib/KeyCode';
+import React from 'react';
import Rate from '../src';
describe('rate', () => {
@@ -319,12 +319,7 @@ describe('rate', () => {
const mockChange = jest.fn();
const mockKeyDown = jest.fn();
const wrapper = mount(
-
+ ,
);
wrapper.simulate('keyDown', { keyCode: KeyCode.LEFT });
expect(mockChange).not.toHaveBeenCalled();
@@ -344,4 +339,21 @@ describe('rate', () => {
expect(wrapper.getDOMNode().getAttribute('id')).toBe('myrate');
});
});
+
+ describe('precision', () => {
+ it('support precision', () => {
+ const wrapper = mount();
+
+ expect(wrapper.find('li > div > .rc-rate-star-first').at(1).prop('style').width).toBe('100%');
+ });
+
+ it('should show the number of stars at the current precision', () => {
+ const wrapper = mount();
+
+ const width = parseFloat(
+ wrapper.find('li > div > .rc-rate-star-first').at(2).prop('style').width,
+ );
+ expect(width).toBeCloseTo(20);
+ });
+ });
});