├── .dumirc.ts
├── .editorconfig
├── .eslintrc.js
├── .fatherrc.ts
├── .github
├── dependabot.yml
└── workflows
│ ├── codeql.yml
│ └── react-component-ci.yml
├── .gitignore
├── .prettierrc
├── CHANGELOG.md
├── LICENSE.md
├── README.md
├── assets
└── index.less
├── bunfig.toml
├── docs
├── changelog.md
├── demo
│ ├── characterRender.md
│ └── simple.md
├── examples
│ ├── characterRender.tsx
│ └── simple.tsx
└── index.md
├── index.js
├── jest.config.js
├── now.json
├── package.json
├── src
├── Rate.tsx
├── Star.tsx
├── index.tsx
├── useRefs.ts
└── util.ts
├── tests
├── __snapshots__
│ └── simple.spec.js.snap
├── props.spec.js
├── setup.js
└── simple.spec.js
├── tsconfig.json
└── typeings.d.ts
/.dumirc.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'dumi';
2 | import path from 'path';
3 |
4 | export default defineConfig({
5 | alias: {
6 | 'rc-rate$': path.resolve('src'),
7 | 'rc-rate/es': path.resolve('src'),
8 | },
9 | favicons: ['https://avatars0.githubusercontent.com/u/9441414?s=200&v=4'],
10 | themeConfig: {
11 | name: 'Rate',
12 | logo: 'https://avatars0.githubusercontent.com/u/9441414?s=200&v=4',
13 | },
14 | });
15 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 |
12 | [*.md]
13 | trim_trailing_whitespace = false
14 |
15 | [Makefile]
16 | indent_style = tab
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: [require.resolve('@umijs/fabric/dist/eslint')],
3 | rules: {
4 | 'jsx-a11y/no-autofocus': 0,
5 | },
6 | overrides: [
7 | {
8 | files: ['docs/**/*.tsx'],
9 | rules: {
10 | 'no-console': 0,
11 | },
12 | },
13 | ],
14 | };
15 |
--------------------------------------------------------------------------------
/.fatherrc.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'father';
2 |
3 | export default defineConfig({
4 | plugins: ['@rc-component/father-plugin'],
5 | });
6 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: npm
4 | directory: "/"
5 | schedule:
6 | interval: daily
7 | time: "21:00"
8 | open-pull-requests-limit: 10
9 | ignore:
10 | - dependency-name: "@types/react"
11 | versions:
12 | - 17.0.0
13 | - 17.0.1
14 | - 17.0.2
15 | - 17.0.3
16 | - dependency-name: "@types/react-dom"
17 | versions:
18 | - 17.0.0
19 | - 17.0.1
20 | - 17.0.2
21 |
--------------------------------------------------------------------------------
/.github/workflows/codeql.yml:
--------------------------------------------------------------------------------
1 | name: "CodeQL"
2 |
3 | on:
4 | push:
5 | branches: [ "master" ]
6 | pull_request:
7 | branches: [ "master" ]
8 | schedule:
9 | - cron: "7 4 * * 2"
10 |
11 | jobs:
12 | analyze:
13 | name: Analyze
14 | runs-on: ubuntu-latest
15 | permissions:
16 | actions: read
17 | contents: read
18 | security-events: write
19 |
20 | strategy:
21 | fail-fast: false
22 | matrix:
23 | language: [ javascript ]
24 |
25 | steps:
26 | - name: Checkout
27 | uses: actions/checkout@v3
28 |
29 | - name: Initialize CodeQL
30 | uses: github/codeql-action/init@v2
31 | with:
32 | languages: ${{ matrix.language }}
33 | queries: +security-and-quality
34 |
35 | - name: Autobuild
36 | uses: github/codeql-action/autobuild@v2
37 |
38 | - name: Perform CodeQL Analysis
39 | uses: github/codeql-action/analyze@v2
40 | with:
41 | category: "/language:${{ matrix.language }}"
42 |
--------------------------------------------------------------------------------
/.github/workflows/react-component-ci.yml:
--------------------------------------------------------------------------------
1 | name: ✅ test
2 | on: [push, pull_request]
3 | jobs:
4 | test:
5 | uses: react-component/rc-test/.github/workflows/test.yml@main
6 | secrets: inherit
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | *.log
3 | .idea
4 | .ipr
5 | .iws
6 | *~
7 | ~*
8 | *.diff
9 | *.patch
10 | *.bak
11 | .DS_Store
12 | Thumbs.db
13 | .project
14 | .*proj
15 | .svn
16 | *.swp
17 | *.swo
18 | *.pyc
19 | *.pyo
20 | node_modules
21 | .cache
22 | *.css
23 | build
24 | lib
25 | es
26 | coverage
27 | yarn.lock
28 | package-lock.json
29 | .doc/
30 |
31 | .doc
32 | # dumi
33 | .dumi/tmp
34 | .dumi/tmp-test
35 | .dumi/tmp-production
36 |
37 | bun.lockb
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "trailingComma": "all",
4 | "proseWrap": "never",
5 | "printWidth": 100
6 | }
7 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## 2.9.1
4 |
5 | `2020-11-17`
6 |
7 | - fix: character type [#102](https://github.com/react-component/rate/pull/102)
8 |
9 | ## 2.9.0
10 |
11 | `2020-11-02`
12 |
13 | - Update devDependencies include np/typescript/rc-tooltip
14 | - Add peerDependencies [#97](https://github.com/react-component/rate/pull/97)
15 |
16 | ## 2.8.2
17 |
18 | `2020-06-15`
19 | - fix: improve defaultvalue more than half [#86](https://github.com/react-component/rate/pull/86)
20 |
21 | - fix: star tabindex when disabled [#87](https://github.com/react-component/rate/pull/87)
22 |
23 | ## 2.8.1
24 |
25 | `2020-06-12`
26 | - feat: character support props [#85](https://github.com/react-component/rate/pull/85)
27 |
28 | ## 2.8.0
29 |
30 | `2020-06-10`
31 | - feat: expand character [#84](https://github.com/react-component/rate/pull/84)
32 |
33 | ## 2.7.0
34 |
35 | `2020-05-29`
36 | - 🆙 upgrade rc-util to 5.x
37 |
38 | ## 2.6.0
39 |
40 | `2020-04-16`
41 | - feat: add direction rtl [#80](https://github.com/react-component/rate/pull/80)
42 | - chore: use father [#81](https://github.com/react-component/rate/pull/81)
43 |
44 | ## 2.4.1
45 |
46 | - Better accessibility support.
47 |
48 | ## 2.4.0
49 |
50 | - Add allowClear support.
51 |
52 | ## 2.3.0
53 |
54 | - Add keyboard support.
55 | - Add focus() blur() and autoFocus.
56 |
57 | ## 2.1.0
58 |
59 | - Fix typo `charactor` to `character`.
60 |
61 | ## 2.0.0
62 |
63 | - Add `character`.
64 | - Add `className`.
65 | - Add `onHoverChange(value)`.
66 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014-present yiminghe
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
10 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # rc-rate
2 |
3 | React Rate Component
4 |
5 | [![NPM version][npm-image]][npm-url]
6 | [![npm download][download-image]][download-url]
7 | [![build status][github-actions-image]][github-actions-url]
8 | [![Codecov][codecov-image]][codecov-url]
9 | [![bundle size][bundlephobia-image]][bundlephobia-url]
10 | [![dumi][dumi-image]][dumi-url]
11 |
12 | [npm-image]: http://img.shields.io/npm/v/rc-rate.svg?style=flat-square
13 | [npm-url]: http://npmjs.org/package/rc-rate
14 | [github-actions-image]: https://github.com/react-component/rate/workflows/CI/badge.svg
15 | [github-actions-url]: https://github.com/react-component/rate/actions
16 | [codecov-image]: https://img.shields.io/codecov/c/github/react-component/rate/master.svg?style=flat-square
17 | [codecov-url]: https://codecov.io/gh/react-component/rate/branch/master
18 | [david-url]: https://david-dm.org/react-component/rate
19 | [david-image]: https://david-dm.org/react-component/rate/status.svg?style=flat-square
20 | [david-dev-url]: https://david-dm.org/react-component/rate?type=dev
21 | [david-dev-image]: https://david-dm.org/react-component/rate/dev-status.svg?style=flat-square
22 | [download-image]: https://img.shields.io/npm/dm/rc-rate.svg?style=flat-square
23 | [download-url]: https://npmjs.org/package/rc-rate
24 | [bundlephobia-url]: https://bundlephobia.com/result?p=rc-rate
25 | [bundlephobia-image]: https://badgen.net/bundlephobia/minzip/rc-rate
26 | [dumi-url]: https://github.com/umijs/dumi
27 | [dumi-image]: https://img.shields.io/badge/docs%20by-dumi-blue?style=flat-square
28 |
29 | ## Screenshots
30 |
31 |
32 |
33 | ## Changelog
34 |
35 | - [CHANGELOG](./CHANGELOG.md)
36 |
37 | ## Development
38 |
39 | ```
40 | npm install
41 | npm start
42 | ```
43 |
44 | ## Example
45 |
46 | - Local: http://localhost:9001/
47 |
48 | - Online: http://react-component.github.io/rate/
49 |
50 | ## install
51 |
52 | [](https://npmjs.org/package/rc-rate)
53 |
54 | ## Usage
55 |
56 | ```js
57 | import React from 'react';
58 | import ReactDOM from 'react-dom';
59 | import Rate from 'rc-rate';
60 |
61 | ReactDOM.render(
62 | ,
63 | document.getElementById('root')
64 | )
65 | ```
66 |
67 | ### with [styled-components](https://github.com/styled-components/styled-components)
68 | ```js
69 | import React from 'react';
70 | import ReactDOM from 'react-dom';
71 | import Rate from 'rc-rate';
72 | import styled from 'styled-components';
73 |
74 | const StyledRate = styled(Rate)`
75 | &.rc-rate {
76 | font-size: ${({ size }) => size}px;
77 | }
78 | `
79 |
80 | ReactDOM.render(
81 | ,
82 | document.getElementById('root')
83 | )
84 | ```
85 |
86 | ## API
87 |
88 | ### props
89 |
90 | | name | type | default | description |
91 | | ------------- | --------------------------------- | ------------- | ----------------------------------------------------- |
92 | | count | number | 5 | Star numbers |
93 | | value | number | - | Controlled value |
94 | | defaultValue | number | 0 | Initial value |
95 | | allowHalf | boolean | false | Support half star |
96 | | allowClear | boolean | true | Reset when click again |
97 | | style | object | {} | |
98 | | onChange | function | (value) => {} | `onChange` will be triggered when click |
99 | | onHoverChange | function | (value) => {} | `onHoverChange` will be triggered when hover on stars |
100 | | character | ReactNode \| (props) => ReactNode | ★ | The each character of rate |
101 | | disabled | boolean | false | |
102 | | direction | string | `ltr` | The direction of rate |
103 |
104 | ## Test Case
105 |
106 | ```
107 | npm test
108 | npm run chrome-test
109 | ```
110 |
111 | ## Coverage
112 |
113 | ```
114 | npm run coverage
115 | ```
116 |
117 | open coverage/ dir
118 |
119 | ## License
120 |
121 | rc-rate is released under the MIT license.
122 |
--------------------------------------------------------------------------------
/assets/index.less:
--------------------------------------------------------------------------------
1 | @rate-prefix-cls: rc-rate;
2 | @rate-star-color: #f5a623;
3 | @font-size-base: 13px;
4 |
5 | .@{rate-prefix-cls} {
6 | margin: 0;
7 | padding: 0;
8 | list-style: none;
9 | font-size: 18px;
10 | display: inline-block;
11 | vertical-align: middle;
12 | font-weight: normal;
13 | font-style: normal;
14 | outline: none;
15 |
16 | &-rtl {
17 | direction: rtl;
18 | }
19 |
20 | &-disabled &-star {
21 | cursor: default;
22 | &:before,
23 | &-content:before {
24 | cursor: default;
25 | }
26 | &:hover {
27 | transform: scale(1);
28 | }
29 | }
30 |
31 | &-star {
32 | margin: 0;
33 | padding: 0;
34 | display: inline-block;
35 | margin-right: 8px;
36 | position: relative;
37 | transition: all .3s;
38 | color: #e9e9e9;
39 | cursor: pointer;
40 | line-height: 1.5;
41 |
42 | .@{rate-prefix-cls}-rtl & {
43 | margin-right: 0;
44 | margin-left: 8px;
45 | float: right;
46 | }
47 |
48 | &-first,
49 | &-second {
50 | transition: all .3s;
51 | }
52 |
53 | &-focused, &:hover {
54 | transform: scale(1.1);
55 | }
56 |
57 | &-first {
58 | position: absolute;
59 | left: 0;
60 | top: 0;
61 | width: 50%;
62 | height: 100%;
63 | overflow: hidden;
64 | opacity: 0;
65 |
66 | .@{rate-prefix-cls}-rtl & {
67 | right: 0;
68 | left: auto;
69 | }
70 | }
71 |
72 | &-half &-first,
73 | &-half &-second {
74 | opacity: 1;
75 | }
76 |
77 | &-half &-first,
78 | &-full &-second {
79 | color: @rate-star-color;
80 | }
81 |
82 | &-half:hover &-first,
83 | &-full:hover &-second {
84 | color: tint(@rate-star-color,30%);
85 | }
86 | }
87 | }
88 |
89 | @icon-url: "//at.alicdn.com/t/font_r5u29ls31bgldi";
90 |
91 | @font-face {
92 | font-family: 'anticon';
93 | src: url('@{icon-url}.eot'); /* IE9*/
94 | 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- */
95 | }
96 |
97 | .anticon {
98 | font-style: normal;
99 | vertical-align: baseline;
100 | text-align: center;
101 | text-transform: none;
102 | line-height: 1;
103 | text-rendering: optimizeLegibility;
104 | -webkit-font-smoothing: antialiased;
105 | -moz-osx-font-smoothing: grayscale;
106 | &:before {
107 | display: block;
108 | font-family: "anticon" !important;
109 | }
110 | }
111 |
112 | .anticon-star:before { content: "\e660"; };
113 |
--------------------------------------------------------------------------------
/bunfig.toml:
--------------------------------------------------------------------------------
1 | [install]
2 | peer = false
--------------------------------------------------------------------------------
/docs/changelog.md:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/demo/characterRender.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: characterRender
3 | nav:
4 | title: Demo
5 | path: /demo
6 | ---
7 |
8 |
--------------------------------------------------------------------------------
/docs/demo/simple.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: simple
3 | nav:
4 | title: Demo
5 | path: /demo
6 | ---
7 |
8 |
--------------------------------------------------------------------------------
/docs/examples/characterRender.tsx:
--------------------------------------------------------------------------------
1 | /* eslint no-console: 0 */
2 | import React from 'react';
3 | import Tooltip from 'rc-tooltip';
4 | import 'rc-tooltip/assets/bootstrap_white.css';
5 | import Rate from 'rc-rate';
6 | import '../../assets/index.less';
7 |
8 | export default () => (
9 |
10 | (
13 |
14 | {node}
15 |
16 | )}
17 | />
18 |
19 | );
20 |
--------------------------------------------------------------------------------
/docs/examples/simple.tsx:
--------------------------------------------------------------------------------
1 | /* eslint no-console: 0 */
2 | import React from 'react';
3 | import Rate from 'rc-rate';
4 | import '../../assets/index.less';
5 |
6 | function onChange(v: number) {
7 | console.log('selected star', v);
8 | }
9 |
10 | export default () => (
11 |
12 |
Base
13 |
20 |
21 |
28 |
29 | {
34 | return index + 1;
35 | }}
36 | />
37 |
38 | }
44 | />
45 | Disabled
46 | }
52 | />
53 | RTL
54 | }
61 | />
62 |
63 | );
64 |
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | hero:
3 | title: rc-rate
4 | description: React Rate Component
5 | ---
6 |
7 |
8 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | // export this package's api
2 | import Rate from './src/';
3 | export default Rate;
4 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | setupFiles: ["./tests/setup.js"],
3 | snapshotSerializers: [require.resolve("enzyme-to-json/serializer")],
4 | };
5 |
--------------------------------------------------------------------------------
/now.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 2,
3 | "name": "rc-rate",
4 | "builds": [
5 | {
6 | "src": "package.json",
7 | "use": "@now/static-build",
8 | "config": { "distDir": "dist" }
9 | }
10 | ],
11 | "routes": [
12 | { "src": "/(.*)", "dest": "/dist/$1" }
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "rc-rate",
3 | "version": "2.13.1",
4 | "description": "React Star Rate Component",
5 | "engines": {
6 | "node": ">=8.x"
7 | },
8 | "keywords": [
9 | "react",
10 | "react-component",
11 | "react-rate",
12 | "rate"
13 | ],
14 | "homepage": "https://github.com/react-component/rate",
15 | "repository": {
16 | "type": "git",
17 | "url": "https://github.com/react-component/rate.git"
18 | },
19 | "bugs": {
20 | "url": "https://github.com/react-component/rate/issues"
21 | },
22 | "files": [
23 | "lib",
24 | "es",
25 | "assets/*.css"
26 | ],
27 | "license": "MIT",
28 | "main": "./lib/index",
29 | "module": "./es/index",
30 | "scripts": {
31 | "start": "dumi dev",
32 | "docs:build": "dumi build",
33 | "docs:deploy": "gh-pages -d .doc",
34 | "compile": "father build && lessc assets/index.less assets/index.css",
35 | "prepare": "dumi setup",
36 | "prepublishOnly": "npm run compile && np --yolo --no-publish",
37 | "postpublish": "npm run docs:build && npm run docs:deploy",
38 | "lint": "eslint src/ --ext .ts,.tsx,.jsx,.js,.md",
39 | "prettier": "prettier --write \"**/*.{ts,tsx,js,jsx,json,md}\"",
40 | "test": "rc-test",
41 | "coverage": "rc-test --coverage",
42 | "now-build": "npm run docs:build"
43 | },
44 | "dependencies": {
45 | "@babel/runtime": "^7.10.1",
46 | "classnames": "^2.2.5",
47 | "rc-util": "^5.0.1"
48 | },
49 | "devDependencies": {
50 | "@rc-component/father-plugin": "^1.0.0",
51 | "@types/classnames": "^2.2.9",
52 | "@types/jest": "^29.5.1",
53 | "@types/react": "^17.0.15",
54 | "@types/react-dom": "^17.0.9",
55 | "@umijs/fabric": "^3.0.0",
56 | "cheerio": "1.0.0-rc.12",
57 | "cross-env": "^7.0.0",
58 | "dumi": "^2.1.2",
59 | "enzyme": "^3.1.1",
60 | "enzyme-adapter-react-16": "^1.15.6",
61 | "enzyme-to-json": "^3.1.2",
62 | "eslint": "^7.1.0",
63 | "father": "^4.0.0",
64 | "gh-pages": "^3.1.0",
65 | "less": "^3.0.0",
66 | "np": "^7.0.0",
67 | "rc-test": "^7.0.15",
68 | "rc-tooltip": "^5.0.1",
69 | "react": "^16.0.0",
70 | "react-dom": "^16.0.0",
71 | "typescript": "^5.0.4"
72 | },
73 | "peerDependencies": {
74 | "react": ">=16.9.0",
75 | "react-dom": ">=16.9.0"
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/Rate.tsx:
--------------------------------------------------------------------------------
1 | import classNames from 'classnames';
2 | import useMergedState from 'rc-util/lib/hooks/useMergedState';
3 | import KeyCode from 'rc-util/lib/KeyCode';
4 | import pickAttrs from 'rc-util/lib/pickAttrs';
5 | import React from 'react';
6 | import type { StarProps } from './Star';
7 | import Star from './Star';
8 | import useRefs from './useRefs';
9 | import { getOffsetLeft } from './util';
10 |
11 | export interface RateProps
12 | extends Pick {
13 | value?: number;
14 | defaultValue?: number;
15 | allowClear?: boolean;
16 | style?: React.CSSProperties;
17 | prefixCls?: string;
18 | onChange?: (value: number) => void;
19 | onHoverChange?: (value: number) => void;
20 | className?: string;
21 | tabIndex?: number;
22 | onFocus?: () => void;
23 | onBlur?: () => void;
24 | onKeyDown?: React.KeyboardEventHandler;
25 | onMouseEnter?: React.MouseEventHandler;
26 | onMouseLeave?: React.MouseEventHandler;
27 | id?: string;
28 | autoFocus?: boolean;
29 | direction?: string;
30 | /**
31 | * Is keyboard control enabled.
32 | * @default true
33 | */
34 | keyboard?: boolean;
35 | }
36 |
37 | export interface RateRef {
38 | focus: VoidFunction;
39 | blur: VoidFunction;
40 | }
41 |
42 | function Rate(props: RateProps, ref: React.Ref) {
43 | const {
44 | // Base
45 | prefixCls = 'rc-rate',
46 | className,
47 |
48 | // Value
49 | defaultValue,
50 | value: propValue,
51 | count = 5,
52 | allowHalf = false,
53 | allowClear = true,
54 | keyboard = true,
55 |
56 | // Display
57 | character = '★',
58 | characterRender,
59 |
60 | // Meta
61 | disabled,
62 | direction = 'ltr',
63 | tabIndex = 0,
64 | autoFocus,
65 |
66 | // Events
67 | onHoverChange,
68 | onChange,
69 | onFocus,
70 | onBlur,
71 | onKeyDown,
72 | onMouseLeave,
73 |
74 | ...restProps
75 | } = props;
76 |
77 | const [getStarRef, setStarRef] = useRefs();
78 | const rateRef = React.useRef(null);
79 |
80 | // ============================ Ref =============================
81 | const triggerFocus = () => {
82 | if (!disabled) {
83 | rateRef.current?.focus();
84 | }
85 | };
86 |
87 | React.useImperativeHandle(ref, () => ({
88 | focus: triggerFocus,
89 | blur: () => {
90 | if (!disabled) {
91 | rateRef.current?.blur();
92 | }
93 | },
94 | }));
95 |
96 | // =========================== Value ============================
97 | const [value, setValue] = useMergedState(defaultValue || 0, {
98 | value: propValue,
99 | });
100 | const [cleanedValue, setCleanedValue] = useMergedState(null);
101 |
102 | const getStarValue = (index: number, x: number) => {
103 | const reverse = direction === 'rtl';
104 | let starValue = index + 1;
105 | if (allowHalf) {
106 | const starEle = getStarRef(index);
107 | const leftDis = getOffsetLeft(starEle);
108 | const width = starEle.clientWidth;
109 | if (reverse && x - leftDis > width / 2) {
110 | starValue -= 0.5;
111 | } else if (!reverse && x - leftDis < width / 2) {
112 | starValue -= 0.5;
113 | }
114 | }
115 | return starValue;
116 | };
117 |
118 | // >>>>> Change
119 | const changeValue = (nextValue: number) => {
120 | setValue(nextValue);
121 | onChange?.(nextValue);
122 | };
123 |
124 | // =========================== Focus ============================
125 | const [focused, setFocused] = React.useState(false);
126 |
127 | const onInternalFocus = () => {
128 | setFocused(true);
129 | onFocus?.();
130 | };
131 |
132 | const onInternalBlur = () => {
133 | setFocused(false);
134 | onBlur?.();
135 | };
136 |
137 | // =========================== Hover ============================
138 | const [hoverValue, setHoverValue] = React.useState(null);
139 |
140 | const onHover = (event: React.MouseEvent, index: number) => {
141 | const nextHoverValue = getStarValue(index, event.pageX);
142 | if (nextHoverValue !== cleanedValue) {
143 | setHoverValue(nextHoverValue);
144 | setCleanedValue(null);
145 | }
146 | onHoverChange?.(nextHoverValue);
147 | };
148 |
149 | const onMouseLeaveCallback = (event?: React.MouseEvent) => {
150 | if (!disabled) {
151 | setHoverValue(null);
152 | setCleanedValue(null);
153 | onHoverChange?.(undefined);
154 | }
155 | if (event) {
156 | onMouseLeave?.(event);
157 | }
158 | };
159 |
160 | // =========================== Click ============================
161 | const onClick = (event: React.MouseEvent | React.KeyboardEvent, index: number) => {
162 | const newValue = getStarValue(index, (event as React.MouseEvent).pageX);
163 | let isReset = false;
164 | if (allowClear) {
165 | isReset = newValue === value;
166 | }
167 | onMouseLeaveCallback();
168 | changeValue(isReset ? 0 : newValue);
169 | setCleanedValue(isReset ? newValue : null);
170 | };
171 |
172 | const onInternalKeyDown: React.KeyboardEventHandler = (event) => {
173 | const { keyCode } = event;
174 | const reverse = direction === 'rtl';
175 | const step = allowHalf ? 0.5 : 1;
176 |
177 | if (keyboard) {
178 | if (keyCode === KeyCode.RIGHT && value < count && !reverse) {
179 | changeValue(value + step);
180 | event.preventDefault();
181 | } else if (keyCode === KeyCode.LEFT && value > 0 && !reverse) {
182 | changeValue(value - step);
183 | event.preventDefault();
184 | } else if (keyCode === KeyCode.RIGHT && value > 0 && reverse) {
185 | changeValue(value - step);
186 | event.preventDefault();
187 | } else if (keyCode === KeyCode.LEFT && value < count && reverse) {
188 | changeValue(value + step);
189 | event.preventDefault();
190 | }
191 | }
192 |
193 | onKeyDown?.(event);
194 | };
195 |
196 | // =========================== Effect ===========================
197 |
198 | React.useEffect(() => {
199 | if (autoFocus && !disabled) {
200 | triggerFocus();
201 | }
202 | }, []);
203 |
204 | // =========================== Render ===========================
205 | // >>> Star
206 | const starNodes = new Array(count)
207 | .fill(0)
208 | .map((item, index) => (
209 |
224 | ));
225 |
226 | const classString = classNames(prefixCls, className, {
227 | [`${prefixCls}-disabled`]: disabled,
228 | [`${prefixCls}-rtl`]: direction === 'rtl',
229 | });
230 |
231 | // >>> Node
232 | return (
233 |
245 | );
246 | }
247 |
248 | export default React.forwardRef(Rate);
249 |
--------------------------------------------------------------------------------
/src/Star.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import KeyCode from 'rc-util/lib/KeyCode';
3 | import classNames from 'classnames';
4 |
5 | export interface StarProps {
6 | value?: number;
7 | index?: number;
8 | prefixCls?: string;
9 | allowHalf?: boolean;
10 | disabled?: boolean;
11 | onHover?: (e: React.MouseEvent, index: number) => void;
12 | onClick?: (
13 | e: React.MouseEvent | React.KeyboardEvent,
14 | index: number,
15 | ) => void;
16 | character?: React.ReactNode | ((props: StarProps) => React.ReactNode);
17 | characterRender?: (origin: React.ReactElement, props: StarProps) => React.ReactNode;
18 | focused?: boolean;
19 | count?: number;
20 | }
21 |
22 | function Star(props: StarProps, ref: React.Ref) {
23 | const {
24 | disabled,
25 | prefixCls,
26 | character,
27 | characterRender,
28 | index,
29 | count,
30 | value,
31 | allowHalf,
32 | focused,
33 | onHover,
34 | onClick,
35 | } = props;
36 |
37 | // =========================== Events ===========================
38 | const onInternalHover: React.MouseEventHandler = (e) => {
39 | onHover(e, index);
40 | };
41 |
42 | const onInternalClick: React.MouseEventHandler = (e) => {
43 | onClick(e, index);
44 | };
45 |
46 | const onInternalKeyDown: React.KeyboardEventHandler = (e) => {
47 | if (e.keyCode === KeyCode.ENTER) {
48 | onClick(e, index);
49 | }
50 | };
51 |
52 | // =========================== Render ===========================
53 | // >>>>> ClassName
54 | const starValue = index + 1;
55 | const classNameList = new Set([prefixCls]);
56 |
57 | // TODO: Current we just refactor from CC to FC. This logic seems can be optimized.
58 | if (value === 0 && index === 0 && focused) {
59 | classNameList.add(`${prefixCls}-focused`);
60 | } else if (allowHalf && value + 0.5 >= starValue && value < starValue) {
61 | classNameList.add(`${prefixCls}-half`);
62 | classNameList.add(`${prefixCls}-active`);
63 | if (focused) {
64 | classNameList.add(`${prefixCls}-focused`);
65 | }
66 | } else {
67 | if (starValue <= value) {
68 | classNameList.add(`${prefixCls}-full`);
69 | } else {
70 | classNameList.add(`${prefixCls}-zero`);
71 | }
72 | if (starValue === value && focused) {
73 | classNameList.add(`${prefixCls}-focused`);
74 | }
75 | }
76 |
77 | // >>>>> Node
78 | const characterNode = typeof character === 'function' ? character(props) : character;
79 | let start: React.ReactNode = (
80 |
81 | index ? 'true' : 'false'}
87 | aria-posinset={index + 1}
88 | aria-setsize={count}
89 | tabIndex={disabled ? -1 : 0}
90 | >
91 |
{characterNode}
92 |
{characterNode}
93 |
94 |
95 | );
96 |
97 | if (characterRender) {
98 | start = characterRender(start as React.ReactElement, props);
99 | }
100 |
101 | return start as React.ReactElement;
102 | }
103 |
104 | export default React.forwardRef(Star);
105 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import Rate from './Rate';
2 |
3 | export default Rate;
4 |
--------------------------------------------------------------------------------
/src/useRefs.ts:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | export default function useRefs(): [
4 | getRef: (index: number) => T,
5 | setRef: (index: number) => (instance: T) => void,
6 | ] {
7 | const nodeRef = React.useRef>({});
8 |
9 | function getRef(index: number) {
10 | return nodeRef.current[index];
11 | }
12 |
13 | function setRef(index: number) {
14 | return (node: T) => {
15 | nodeRef.current[index] = node;
16 | };
17 | }
18 |
19 | return [getRef, setRef];
20 | }
21 |
--------------------------------------------------------------------------------
/src/util.ts:
--------------------------------------------------------------------------------
1 | function getScroll(w: Window) {
2 | let ret = w.pageXOffset;
3 | const method = 'scrollLeft';
4 | if (typeof ret !== 'number') {
5 | const d = w.document;
6 | // ie6,7,8 standard mode
7 | ret = d.documentElement[method];
8 | if (typeof ret !== 'number') {
9 | // quirks mode
10 | ret = d.body[method];
11 | }
12 | }
13 | return ret;
14 | }
15 |
16 | function getClientPosition(elem: HTMLElement) {
17 | let x: number;
18 | let y: number;
19 | const doc = elem.ownerDocument;
20 | const { body } = doc;
21 | const docElem = doc && doc.documentElement;
22 | const box = elem.getBoundingClientRect();
23 | x = box.left;
24 | y = box.top;
25 | x -= docElem.clientLeft || body.clientLeft || 0;
26 | y -= docElem.clientTop || body.clientTop || 0;
27 | return {
28 | left: x,
29 | top: y,
30 | };
31 | }
32 |
33 | export function getOffsetLeft(el: HTMLElement) {
34 | const pos = getClientPosition(el);
35 | const doc = el.ownerDocument;
36 | // Only IE use `parentWindow`
37 | const w: Window = doc.defaultView || (doc as any).parentWindow;
38 | pos.left += getScroll(w);
39 | return pos.left;
40 | }
41 |
--------------------------------------------------------------------------------
/tests/__snapshots__/simple.spec.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`rate allowHalf render works 1`] = `
4 |
8 | -
11 |
18 |
21 | ★
22 |
23 |
26 | ★
27 |
28 |
29 |
30 | -
33 |
40 |
43 | ★
44 |
45 |
48 | ★
49 |
50 |
51 |
52 | -
55 |
62 |
65 | ★
66 |
67 |
70 | ★
71 |
72 |
73 |
74 |
75 | `;
76 |
77 | exports[`rate allowHalf render works in RTL 1`] = `
78 |
82 | -
85 |
92 |
95 | ★
96 |
97 |
100 | ★
101 |
102 |
103 |
104 | -
107 |
114 |
117 | ★
118 |
119 |
122 | ★
123 |
124 |
125 |
126 | -
129 |
136 |
139 | ★
140 |
141 |
144 | ★
145 |
146 |
147 |
148 |
149 | `;
150 |
151 | exports[`rate allowHalf render works more than half 1`] = `
152 |
156 | -
159 |
166 |
169 | ★
170 |
171 |
174 | ★
175 |
176 |
177 |
178 | -
181 |
188 |
191 | ★
192 |
193 |
196 | ★
197 |
198 |
199 |
200 | -
203 |
210 |
213 | ★
214 |
215 |
218 | ★
219 |
220 |
221 |
222 |
223 | `;
224 |
225 | exports[`rate full render works 1`] = `
226 |
230 | -
233 |
240 |
243 | ★
244 |
245 |
248 | ★
249 |
250 |
251 |
252 | -
255 |
262 |
265 | ★
266 |
267 |
270 | ★
271 |
272 |
273 |
274 | -
277 |
284 |
287 | ★
288 |
289 |
292 | ★
293 |
294 |
295 |
296 |
297 | `;
298 |
299 | exports[`rate full render works in RTL 1`] = `
300 |
304 | -
307 |
314 |
317 | ★
318 |
319 |
322 | ★
323 |
324 |
325 |
326 | -
329 |
336 |
339 | ★
340 |
341 |
344 | ★
345 |
346 |
347 |
348 | -
351 |
358 |
361 | ★
362 |
363 |
366 | ★
367 |
368 |
369 |
370 |
371 | `;
372 |
373 | exports[`rate full render works with character function 1`] = `
374 |
378 | -
381 |
388 |
391 | 1
392 |
393 |
396 | 1
397 |
398 |
399 |
400 | -
403 |
410 |
413 | 2
414 |
415 |
418 | 2
419 |
420 |
421 |
422 | -
425 |
432 |
435 | 3
436 |
437 |
440 | 3
441 |
442 |
443 |
444 |
445 | `;
446 |
447 | exports[`rate full render works with character node 1`] = `
448 |
452 | -
455 |
462 |
465 | 1
466 |
467 |
470 | 1
471 |
472 |
473 |
474 | -
477 |
484 |
487 | 1
488 |
489 |
492 | 1
493 |
494 |
495 |
496 | -
499 |
506 |
509 | 1
510 |
511 |
514 | 1
515 |
516 |
517 |
518 |
519 | `;
520 |
--------------------------------------------------------------------------------
/tests/props.spec.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { mount } from 'enzyme';
3 | import Rate from '../src';
4 |
5 | describe('props', () => {
6 | it('characterRender', () => {
7 | const wrapper = mount(
8 | {index}} />,
9 | );
10 |
11 | wrapper.find('li').forEach((li, index) => {
12 | expect(li.find('span.render-holder').length).toEqual(1);
13 | expect(li.text()).toEqual(index);
14 | });
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/tests/setup.js:
--------------------------------------------------------------------------------
1 | global.requestAnimationFrame = cb => setTimeout(cb, 0);
2 |
3 | const Enzyme = require('enzyme');
4 | const Adapter = require('enzyme-adapter-react-16');
5 |
6 | Enzyme.configure({ adapter: new Adapter() });
7 |
--------------------------------------------------------------------------------
/tests/simple.spec.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render, mount } from 'enzyme';
3 | import KeyCode from 'rc-util/lib/KeyCode';
4 | import Rate from '../src';
5 |
6 | describe('rate', () => {
7 | describe('full', () => {
8 | it('render works', () => {
9 | const wrapper = render();
10 | expect(wrapper).toMatchSnapshot();
11 | });
12 |
13 | it('render works in RTL', () => {
14 | const wrapper = render();
15 | expect(wrapper).toMatchSnapshot();
16 | });
17 |
18 | it('render works with character node', () => {
19 | const wrapper = render();
20 | expect(wrapper).toMatchSnapshot();
21 | });
22 |
23 | it('render works with character function', () => {
24 | const wrapper = render(
25 | {
29 | return index + 1;
30 | }}
31 | />,
32 | );
33 | expect(wrapper).toMatchSnapshot();
34 | });
35 |
36 | it('click works', () => {
37 | const handleChange = jest.fn();
38 | const wrapper = mount();
39 | wrapper.find('li > div').at(1).simulate('click');
40 | expect(handleChange).toBeCalledWith(2);
41 | });
42 |
43 | it('click works in RTL', () => {
44 | const handleChange = jest.fn();
45 | const wrapper = mount();
46 | wrapper.find('li > div').at(1).simulate('click');
47 | expect(handleChange).toBeCalledWith(2);
48 | });
49 |
50 | it('support mouseMove', () => {
51 | const wrapper = mount();
52 | wrapper.find('li > div').at(1).simulate('mouseMove');
53 | expect(wrapper.find('li').at(1).hasClass('rc-rate-star-full')).toBe(true);
54 | });
55 |
56 | it('support mouseMove in RTL', () => {
57 | const wrapper = mount();
58 | wrapper.find('li > div').at(1).simulate('mouseMove');
59 | expect(wrapper.find('li').at(1).hasClass('rc-rate-star-full')).toBe(true);
60 | });
61 |
62 | it('support focus and blur', () => {
63 | const wrapper = mount();
64 | wrapper.simulate('focus');
65 | expect(wrapper.find('li').at(1).hasClass('rc-rate-star-focused')).toBe(true);
66 |
67 | wrapper.simulate('blur');
68 | expect(wrapper.find('li').at(1).hasClass('rc-rate-star-focused')).toBe(false);
69 | });
70 |
71 | it('support focus and blur in RTL', () => {
72 | const wrapper = mount();
73 | wrapper.simulate('focus');
74 | expect(wrapper.find('li').at(1).hasClass('rc-rate-star-focused')).toBe(true);
75 |
76 | wrapper.simulate('blur');
77 | expect(wrapper.find('li').at(1).hasClass('rc-rate-star-focused')).toBe(false);
78 | });
79 |
80 | describe('support keyboard', () => {
81 | it('left & right', () => {
82 | const handleChange = jest.fn();
83 | const wrapper = mount();
84 | wrapper.simulate('keyDown', { keyCode: KeyCode.LEFT });
85 | expect(handleChange).toBeCalledWith(0);
86 | handleChange.mockReset();
87 | wrapper.simulate('keyDown', { keyCode: KeyCode.RIGHT });
88 | expect(handleChange).toBeCalledWith(2);
89 | });
90 |
91 | it('enter', () => {
92 | const handleChange = jest.fn();
93 | const wrapper = mount();
94 | wrapper.find('li > div').at(2).simulate('keyDown', { keyCode: KeyCode.ENTER });
95 | expect(handleChange).toBeCalledWith(3);
96 | });
97 | });
98 |
99 | describe('support keyboard in RTL', () => {
100 | it('left & right', () => {
101 | const handleChange = jest.fn();
102 | const wrapper = mount();
103 | wrapper.simulate('keyDown', { keyCode: KeyCode.LEFT });
104 | expect(handleChange).toBeCalledWith(2);
105 | handleChange.mockReset();
106 | wrapper.simulate('keyDown', { keyCode: KeyCode.RIGHT });
107 | expect(handleChange).toBeCalledWith(0);
108 | });
109 |
110 | it('enter', () => {
111 | const handleChange = jest.fn();
112 | const wrapper = mount();
113 | wrapper.find('li > div').at(2).simulate('keyDown', { keyCode: KeyCode.ENTER });
114 | expect(handleChange).toBeCalledWith(3);
115 | });
116 | });
117 | });
118 |
119 | describe('allowHalf', () => {
120 | it('render works', () => {
121 | const wrapper = render();
122 | expect(wrapper).toMatchSnapshot();
123 | });
124 |
125 | it('render works more than half ', () => {
126 | const wrapper = render();
127 | expect(wrapper).toMatchSnapshot();
128 | });
129 |
130 | it('render works in RTL', () => {
131 | const wrapper = render(
132 | ,
133 | );
134 | expect(wrapper).toMatchSnapshot();
135 | });
136 |
137 | it('click works', () => {
138 | const wrapper = mount();
139 | wrapper.find('li > div').at(2).simulate('click');
140 | expect(wrapper.find('li').at(4).hasClass('rc-rate-star-full')).toBe(false);
141 | });
142 |
143 | it('support focus and blur', () => {
144 | const wrapper = mount();
145 | wrapper.simulate('focus');
146 | expect(wrapper.find('li').at(1).hasClass('rc-rate-star-focused')).toBe(true);
147 |
148 | wrapper.simulate('blur');
149 | expect(wrapper.find('li').at(1).hasClass('rc-rate-star-focused')).toBe(false);
150 | });
151 |
152 | it('support focus and blur in RTL', () => {
153 | const wrapper = mount();
154 | wrapper.simulate('focus');
155 | expect(wrapper.find('li').at(1).hasClass('rc-rate-star-focused')).toBe(true);
156 |
157 | wrapper.simulate('blur');
158 | expect(wrapper.find('li').at(1).hasClass('rc-rate-star-focused')).toBe(false);
159 | });
160 |
161 | it('support keyboard', () => {
162 | const handleChange = jest.fn();
163 | const wrapper = mount();
164 | wrapper.simulate('keyDown', { keyCode: KeyCode.LEFT });
165 | expect(handleChange).toBeCalledWith(1);
166 | handleChange.mockReset();
167 | wrapper.simulate('keyDown', { keyCode: KeyCode.RIGHT });
168 | expect(handleChange).toBeCalledWith(2);
169 | });
170 |
171 | it('support keyboard in RTL', () => {
172 | const handleChange = jest.fn();
173 | const wrapper = mount(
174 | ,
175 | );
176 | wrapper.simulate('keyDown', { keyCode: KeyCode.LEFT });
177 | expect(handleChange).toBeCalledWith(2);
178 | handleChange.mockReset();
179 | wrapper.simulate('keyDown', { keyCode: KeyCode.RIGHT });
180 | expect(handleChange).toBeCalledWith(1);
181 | });
182 |
183 | it('hover Rate of allowHalf', () => {
184 | const onHoverChange = jest.fn();
185 | const wrapper = mount();
186 | wrapper.find('li > div').at(1).simulate('mouseMove', {
187 | pageX: -1,
188 | });
189 | expect(onHoverChange).toHaveBeenCalledWith(1.5);
190 | });
191 |
192 | it('hover Rate of allowHalf and rtl', () => {
193 | const onHoverChange = jest.fn();
194 | const wrapper = mount(
195 | ,
196 | );
197 | wrapper.find('li > div').at(1).simulate('mouseMove', {
198 | pageX: 1,
199 | });
200 | expect(onHoverChange).toHaveBeenCalledWith(1.5);
201 | });
202 | });
203 |
204 | describe('allowClear', () => {
205 | it('allowClear is false', () => {
206 | const handleChange = jest.fn();
207 | const wrapper = mount(
208 | ,
209 | );
210 | wrapper.find('li > div').at(3).simulate('click');
211 | wrapper.find('li > div').at(3).simulate('click');
212 | expect(handleChange).toBeCalledWith(4);
213 | });
214 | it('allowClear is true', () => {
215 | const handleChange = jest.fn();
216 | const wrapper = mount();
217 | wrapper.find('li > div').at(3).simulate('click');
218 | expect(handleChange).toBeCalledWith(0);
219 | });
220 | it('cleaned star disable hover', () => {
221 | const wrapper = mount();
222 | wrapper.find('li > div').at(3).simulate('click');
223 | wrapper.find('li > div').at(3).simulate('mouseMove');
224 | expect(wrapper.find('li').at(3).hasClass('rc-rate-star-full')).toBe(false);
225 | });
226 | it('cleaned star reset', () => {
227 | const wrapper = mount();
228 | wrapper.find('li > div').at(3).simulate('click');
229 | wrapper.find('ul').simulate('mouseLeave');
230 | wrapper.find('li > div').at(3).simulate('mouseMove');
231 | expect(wrapper.find('li').at(3).hasClass('rc-rate-star-full')).toBe(true);
232 | });
233 | });
234 |
235 | describe('focus & blur', () => {
236 | let container;
237 | beforeEach(() => {
238 | container = document.createElement('div');
239 | document.body.appendChild(container);
240 | });
241 |
242 | afterEach(() => {
243 | document.body.removeChild(container);
244 | });
245 |
246 | it('focus()', () => {
247 | const handleFocus = jest.fn();
248 | const rateRef = React.createRef();
249 | mount(, {
250 | attachTo: container,
251 | });
252 | rateRef.current.focus();
253 | expect(handleFocus).toBeCalled();
254 | });
255 |
256 | it('blur()', () => {
257 | const handleBlur = jest.fn();
258 | const rateRef = React.createRef();
259 | mount(, {
260 | attachTo: container,
261 | });
262 | rateRef.current.focus();
263 | rateRef.current.blur();
264 | expect(handleBlur).toBeCalled();
265 | });
266 |
267 | it('autoFocus', () => {
268 | const handleFocus = jest.fn();
269 | mount(, { attachTo: container });
270 | expect(handleFocus).toBeCalled();
271 | });
272 | });
273 |
274 | describe('right class', () => {
275 | it('rtl', () => {
276 | const wrapper = mount();
277 | expect(wrapper.find('.rc-rate-rtl').length).toBe(1);
278 | });
279 | it('disabled', () => {
280 | const wrapper = mount();
281 | expect(wrapper.find('.rc-rate-disabled').length).toBe(1);
282 | });
283 | });
284 |
285 | describe('events', () => {
286 | it('onKeyDown', () => {
287 | const onKeyDown = jest.fn();
288 | const wrapper = mount();
289 | wrapper.simulate('keydown');
290 | expect(onKeyDown).toHaveBeenCalled();
291 | });
292 |
293 | // https://github.com/ant-design/ant-design/issues/30940
294 | it('range picker should accept onMouseEnter and onMouseLeave event when Rate component is diabled', () => {
295 | const handleMouseEnter = jest.fn();
296 | const handleMouseLeave = jest.fn();
297 | const wrapper = mount(
298 | ,
299 | );
300 | wrapper.simulate('mouseenter');
301 | expect(handleMouseEnter).toHaveBeenCalled();
302 | wrapper.simulate('mouseleave');
303 | expect(handleMouseLeave).toHaveBeenCalled();
304 | });
305 |
306 | it('range picker should accept onMouseEnter and onMouseLeave event when Rate component is not diabled', () => {
307 | const handleMouseEnter = jest.fn();
308 | const handleMouseLeave = jest.fn();
309 | const wrapper = mount(
310 | ,
311 | );
312 | wrapper.simulate('mouseenter');
313 | expect(handleMouseEnter).toHaveBeenCalled();
314 | wrapper.simulate('mouseleave');
315 | expect(handleMouseLeave).toHaveBeenCalled();
316 | });
317 |
318 | it('should ignore key presses when keyboard is false', () => {
319 | const mockChange = jest.fn();
320 | const mockKeyDown = jest.fn();
321 | const wrapper = mount(
322 |
328 | );
329 | wrapper.simulate('keyDown', { keyCode: KeyCode.LEFT });
330 | expect(mockChange).not.toHaveBeenCalled();
331 | expect(mockKeyDown).toHaveBeenCalled();
332 | });
333 | });
334 |
335 | describe('html attributes', () => {
336 | it('data-* and aria-* and role', () => {
337 | const wrapper = mount();
338 | expect(wrapper.getDOMNode().getAttribute('data-number')).toBe('1');
339 | expect(wrapper.getDOMNode().getAttribute('aria-label')).toBe('label');
340 | expect(wrapper.getDOMNode().getAttribute('role')).toBe('button');
341 | });
342 | it('id', () => {
343 | const wrapper = mount();
344 | expect(wrapper.getDOMNode().getAttribute('id')).toBe('myrate');
345 | });
346 | });
347 | });
348 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "esnext",
4 | "moduleResolution": "node",
5 | "baseUrl": "./",
6 | "jsx": "react",
7 | "declaration": true,
8 | "skipLibCheck": true,
9 | "esModuleInterop": true,
10 | "paths": {
11 | "@/*": [
12 | "src/*"
13 | ],
14 | "@@/*": [
15 | ".dumi/tmp/*"
16 | ],
17 | "rc-rate": [
18 | "src/index.tsx"
19 | ]
20 | }
21 | },
22 | "include": [
23 | ".dumirc.ts",
24 | "./src/**/*.ts",
25 | "./src/**/*.tsx",
26 | "./docs/**/*.tsx"
27 | ]
28 | }
--------------------------------------------------------------------------------
/typeings.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.css';
2 | declare module '*.less';
--------------------------------------------------------------------------------