Skip to content

Commit ba5e167

Browse files
committed
feat: support show exact value
1 parent 82114f5 commit ba5e167

6 files changed

Lines changed: 76 additions & 3 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ Then open `http://localhost:8000`.
7979
| `id` | string | - | Root element id. |
8080
| `keyboard` | boolean | true | Enable keyboard control. |
8181
| `prefixCls` | string | `'rc-rate'` | Prefix class name. |
82+
| `showExactValue` | boolean | false | Display the value exactly instead of rounding to whole or half items. Only works when `disabled` is enabled. |
8283
| `style` | React.CSSProperties | - | Root style. |
8384
| `tabIndex` | number | 0 | Root tab index. |
8485
| `value` | number | - | Controlled value. |

README.zh-CN.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ npm start
7979
| `id` | string | - | 根元素 ID。 |
8080
| `keyboard` | boolean | true | 启用键盘控制。 |
8181
| `prefixCls` | string | `'rc-rate'` | className 前缀。 |
82+
| `showExactValue` | boolean | false | 按传入值精确展示,而不是取整星或半星,仅在开启 `disabled` 时生效。 |
8283
| `style` | React.CSSProperties | - | 根样式。 |
8384
| `tabIndex` | number | 0 | 根选项卡索引。 |
8485
| `value` | number | - | 受控值。 |

docs/examples/simple.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,14 @@ export default () => (
5050
style={{ fontSize: 50, marginTop: 24 }}
5151
character={<i className="anticon anticon-star" />}
5252
/>
53+
<br />
54+
<Rate
55+
value={3.8}
56+
disabled
57+
showExactValue
58+
style={{ fontSize: 50, marginTop: 24 }}
59+
character={<i className="anticon anticon-star" />}
60+
/>
5361
<h2>RTL</h2>
5462
<Rate
5563
defaultValue={1}

src/Rate.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@ import Star from './Star';
66
import useRefs from './useRefs';
77
import { getOffsetLeft } from './util';
88

9-
export interface RateProps
10-
extends Pick<StarProps, 'count' | 'character' | 'characterRender' | 'allowHalf' | 'disabled'> {
9+
export interface RateProps extends Pick<
10+
StarProps,
11+
'count' | 'character' | 'characterRender' | 'allowHalf' | 'showExactValue' | 'disabled'
12+
> {
1113
value?: number;
1214
defaultValue?: number;
1315
allowClear?: boolean;
@@ -52,6 +54,7 @@ function Rate(props: RateProps, ref: React.Ref<RateRef>) {
5254
keyboard = true,
5355

5456
// Display
57+
showExactValue = false,
5558
character = '★',
5659
characterRender,
5760

@@ -209,6 +212,7 @@ function Rate(props: RateProps, ref: React.Ref<RateRef>) {
209212
disabled={disabled}
210213
prefixCls={`${prefixCls}-star`}
211214
allowHalf={allowHalf}
215+
showExactValue={disabled && showExactValue}
212216
value={hoverValue === null ? value : hoverValue}
213217
onClick={onClick}
214218
onHover={onHover}

src/Star.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export interface StarProps {
77
index?: number;
88
prefixCls?: string;
99
allowHalf?: boolean;
10+
showExactValue?: boolean;
1011
disabled?: boolean;
1112
onHover?: (e: React.MouseEvent<HTMLDivElement>, index: number) => void;
1213
onClick?: (
@@ -29,6 +30,7 @@ function Star(props: StarProps, ref: React.Ref<HTMLLIElement>) {
2930
count,
3031
value,
3132
allowHalf,
33+
showExactValue,
3234
focused,
3335
onHover,
3436
onClick,
@@ -53,10 +55,19 @@ function Star(props: StarProps, ref: React.Ref<HTMLLIElement>) {
5355
// >>>>> ClassName
5456
const starValue = index + 1;
5557
const classNameList = new Set([prefixCls]);
58+
const fraction = value - index;
59+
let firstStyle: React.CSSProperties | undefined;
5660

5761
// TODO: Current we just refactor from CC to FC. This logic seems can be optimized.
5862
if (value === 0 && index === 0 && focused) {
5963
classNameList.add(`${prefixCls}-focused`);
64+
} else if (showExactValue && fraction > 0 && fraction < 1) {
65+
// Reuse the half state so existing themes only need the dynamic width override.
66+
classNameList.add(`${prefixCls}-half`);
67+
classNameList.add(`${prefixCls}-active`);
68+
firstStyle = {
69+
width: `${Number((fraction * 100).toFixed(10))}%`,
70+
};
6071
} else if (allowHalf && value + 0.5 >= starValue && value < starValue) {
6172
classNameList.add(`${prefixCls}-half`);
6273
classNameList.add(`${prefixCls}-active`);
@@ -89,7 +100,9 @@ function Star(props: StarProps, ref: React.Ref<HTMLLIElement>) {
89100
aria-setsize={count}
90101
tabIndex={disabled ? -1 : 0}
91102
>
92-
<div className={`${prefixCls}-first`}>{characterNode}</div>
103+
<div className={`${prefixCls}-first`} style={firstStyle}>
104+
{characterNode}
105+
</div>
93106
<div className={`${prefixCls}-second`}>{characterNode}</div>
94107
</div>
95108
</li>

tests/simple.spec.js

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,52 @@ describe('rate', () => {
237237
});
238238
});
239239

240+
describe('showExactValue', () => {
241+
it.each([
242+
[3.8, 3, '80%'],
243+
[4.9, 4, '90%'],
244+
])('renders %s in disabled mode', (value, index, width) => {
245+
const { container } = render(<Rate value={value} disabled showExactValue />);
246+
const star = getStar(container, index);
247+
248+
expect(star.classList.contains('rc-rate-star-half')).toBe(true);
249+
expect(star.querySelector('.rc-rate-star-first')).toHaveStyle({ width });
250+
});
251+
252+
it('renders fraction in RTL', () => {
253+
const { container } = render(<Rate value={3.8} disabled showExactValue direction="rtl" />);
254+
const star = getStar(container, 3);
255+
256+
expect(star.classList.contains('rc-rate-star-half')).toBe(true);
257+
expect(star.querySelector('.rc-rate-star-first')).toHaveStyle({ width: '80%' });
258+
});
259+
260+
it('only works in disabled mode', () => {
261+
const { container } = render(<Rate value={3.8} showExactValue />);
262+
const star = getStar(container, 3);
263+
264+
expect(star.classList.contains('rc-rate-star-half')).toBe(false);
265+
expect(star.classList.contains('rc-rate-star-zero')).toBe(true);
266+
expect(star.querySelector('.rc-rate-star-first')).not.toHaveAttribute('style');
267+
});
268+
269+
it('does not override allowHalf in interactive mode', () => {
270+
const { container } = render(<Rate value={3.8} allowHalf showExactValue />);
271+
const star = getStar(container, 3);
272+
273+
expect(star.classList.contains('rc-rate-star-half')).toBe(true);
274+
expect(star.querySelector('.rc-rate-star-first')).not.toHaveAttribute('style');
275+
});
276+
277+
it('takes precedence over allowHalf in disabled mode', () => {
278+
const { container } = render(<Rate value={3.8} disabled allowHalf showExactValue />);
279+
const star = getStar(container, 3);
280+
281+
expect(star.classList.contains('rc-rate-star-half')).toBe(true);
282+
expect(star.querySelector('.rc-rate-star-first')).toHaveStyle({ width: '80%' });
283+
});
284+
});
285+
240286
describe('allowClear', () => {
241287
it('allowClear is false', () => {
242288
const handleChange = jest.fn();

0 commit comments

Comments
 (0)