├── .babelrc ├── .eslintrc ├── .gitignore ├── LICENSE.md ├── README.md ├── RELEASE.md ├── assets └── images │ ├── star-empty.png │ ├── star-full.png │ ├── star-grey.png │ ├── star-red.png │ └── star-yellow.png ├── index.d.ts ├── index.html ├── lib ├── react-rating.cjs.js ├── react-rating.esm.js ├── react-rating.umd.js ├── react-rating.umd.js.map ├── react-rating.umd.min.js └── react-rating.umd.min.js.map ├── package-lock.json ├── package.json ├── rollup.config.js ├── scripts └── gh-pages.sh ├── src ├── Rating.js ├── RatingAPILayer.js ├── RatingSymbol.js ├── react-rating.js ├── utils.js └── utils │ ├── noop.js │ └── style.js ├── test ├── Rating-test.js ├── RatingContainer-test.js └── RatingSymbol-test.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env", 4 | "@babel/preset-react" 5 | ], 6 | "plugins": [ 7 | ["transform-react-remove-prop-types", { 8 | "mode": "remove", 9 | "removeImport": true 10 | }] 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": "eslint:recommended", 4 | "parserOptions": { 5 | "sourceType": "module", 6 | "ecmaVersion": 6, 7 | "ecmaFeatures": { 8 | "jsx": true 9 | } 10 | }, 11 | "env": { 12 | "es6": true, 13 | "browser": true, 14 | "node": true 15 | }, 16 | "plugins": [ 17 | "react" 18 | ], 19 | "globals": { 20 | "__DEV__": true 21 | }, 22 | "rules": { 23 | "indent": ["error", 2], 24 | "func-names": ["error", "never"], 25 | "comma-dangle": ["error", "never"], 26 | "linebreak-style": "off", 27 | "no-unused-vars": ["warn", { 28 | "ignoreRestSiblings": true, 29 | "args": "none" 30 | }], 31 | "no-trailing-spaces": "error", 32 | "no-throw-literal": "off", 33 | "no-restricted-syntax": "off", 34 | "no-prototype-builtins": "off", 35 | "no-underscore-dangle": "off", 36 | "no-confusing-arrow": ["error", {"allowParens": true}], 37 | "no-param-reassign": ["error", { "props": false }], 38 | "camelcase": ["error", {properties: "never"}], 39 | "react/forbid-prop-types": "off", 40 | "react/jsx-uses-vars": "error" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # ========================= 2 | # Operating System Files 3 | # ========================= 4 | 5 | # OSX 6 | # ========================= 7 | 8 | .DS_Store 9 | .AppleDouble 10 | .LSOverride 11 | 12 | # Thumbnails 13 | ._* 14 | 15 | # Files that might appear in the root of a volume 16 | .DocumentRevisions-V100 17 | .fseventsd 18 | .Spotlight-V100 19 | .TemporaryItems 20 | .Trashes 21 | .VolumeIcon.icns 22 | 23 | # Directories potentially created on remote AFP share 24 | .AppleDB 25 | .AppleDesktop 26 | Network Trash Folder 27 | Temporary Items 28 | .apdisk 29 | 30 | # ========================= 31 | # Custom rules 32 | # ========================= 33 | 34 | # Ignore npm modules 35 | node_modules 36 | 37 | # Ignore vim temporary files 38 | *.swp 39 | 40 | # Ignore dist folder 41 | 42 | 43 | # Ignore IDE files 44 | .project 45 | .vscode 46 | .settings 47 | .cache 48 | 49 | # Logs 50 | logs 51 | *.log 52 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 dreyescat (http://github.com/dreyescat) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![npm version](https://badge.fury.io/js/react-rating.svg)](https://badge.fury.io/js/react-rating) 2 | 3 | # React Rating 4 | 5 | React Rating is a [react](https://github.com/facebook/react) rating component which supports custom symbols both with [inline styles](https://facebook.github.io/react/tips/inline-styles.html) and glyphicons found in popular CSS Toolkits like [Fontawesome](http://fortawesome.github.io/Font-Awesome/icons/) or [Bootstrap](http://getbootstrap.com/components/). 6 | 7 | This React component was inspired by the jQuery plugin [bootstrap-rating](https://github.com/dreyescat/bootstrap-rating). 8 | 9 | ## Demo 10 | 11 | See [react-rating](http://dreyescat.github.io/react-rating/) in action. 12 | 13 | ## Installation 14 | 15 | You can install `react-rating` component using the *npm* package manager: 16 | 17 | ```bash 18 | npm install --save react-rating 19 | ``` 20 | 21 | ### Dependencies 22 | 23 | The `react-rating` component [peer depends](https://docs.npmjs.com/files/package.json#peerdependencies) on the [React](http://facebook.github.io/react/) library. 24 | 25 | You can install React using *npm* too: 26 | 27 | ```bash 28 | npm install --save react 29 | ``` 30 | 31 | ## Upgrade Warning 32 | 33 | If you are using a version of React Rating < v1.0 be aware that **there are API changes between anything < v1.0 and v1.0 .** See the **Properties** and **Deprecated Properties and Callbacks** sections below for a documentation of the current API and how it compares to the old. 34 | 35 | ## Usage 36 | 37 | 1. Require the Rating Component 38 | 39 | ```javascript 40 | var Rating = require('react-rating'); 41 | ``` 42 | 43 | 2. Start using it 44 | 45 | With raw javascript: 46 | 47 | ```javascript 48 | React.createElement(Rating) 49 | ``` 50 | 51 | Or with JSX: 52 | 53 | ```jsx 54 | 55 | ``` 56 | 57 | ## Properties 58 | 59 | Property | Type | Default | Description 60 | --- | --- | --- | --- 61 | `start` | *number* | 0 | Range starting value (exclusive). 62 | `stop` | *number* | 5 | Range stop value (inclusive). 63 | `step` | *number* | 1 | Describes how many values each Symbol represents. For example, for a `start` value of 0, a `stop` value of 10 and a `step` of 2, we will end up with 5 Symbols, with each Symbol representing value increments of 2. 64 | `fractions` | *number* | 1 | Number of equal subdivisions that can be selected as a rating in each Symbol. For example, for a `fractions` value of 2, you will be able to select a rating with a precision of down to half a Symbol. Must be >= 1 65 | `initialRating` | *number* | 0 | The value that will be used as an initial rating. This is the old `initialRate`. 66 | `placeholderRating` | *number* | 0 | If you do not define an `initialRating` value, you can use a placeholder rating. Visually, this will have the same result as if you had defined an `initialRating` value. If `initialRating` is set `placeholderRating` is not taken into account. This is the old `placeholderRate` 67 | `readonly` | *bool* | false | Whether the rating can be modified or not. 68 | `quiet` | *bool* | false | Whether to animate rate hovering or not. 69 | `direction` | *ltr* or *rtl* | ltr | The direction of the rating element contents 70 | `emptySymbol` | *element* or *object* or *string* or *array* | Style.empty | React element, inline style object, or classes applied to the rating symbols when empty. Can also be an array of such symbols that will be applied in a circular manner (round-robin). This is the old `empty`. 71 | `fullSymbol` | *element* or *object* or *string* or *array* | Style.full | React element, inline style object, or classes applied to the rating symbols when full. Can also be an array of such symbols that will be applied in a circular manner (round-robin). This is the old `full`. 72 | `placeholderSymbol` | *element* or *object* or *string* or *array* | Style.placeholder | React element, inline style object, or classes applied to the placeholder rating symbols. Can also be an array of such symbols that will be applied in a circular manner (round-robin). This is the old `placeholder`. 73 | 74 | ## Callbacks 75 | 76 | Callback | Type | Description 77 | --- | --- | --- 78 | `onChange` | function (value) {} | Gets called with the `value` when a different value than the currently set is selected. 79 | `onClick` | function (value) {} | Gets called with the `value` when a symbol is clicked. The value is equal to the value that corresponds to that part of the symbol. 80 | `onHover` | function (value) {} | Gets called with the `value` when you hover over a symbol. The value is equal to the value that corresponds to that part of the symbol. Gets called in `quiet` mode too. When hover ends, gets called with no `value` (i.e. `undefined` as the value). 81 | 82 | ## Deprecated Properties and Callbacks 83 | 84 | This is a list of deprecated properties and callbacks from versions older than v1.0 85 | 86 | * `onRate` 87 | * `initialRate` 88 | * `placeholderRate` 89 | * `empty` 90 | * `full` 91 | * `placeholder` 92 | 93 | ## License 94 | 95 | [MIT License](https://github.com/dreyescat/react-rating/blob/master/LICENSE.md) 96 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | # Release process 2 | 3 | 1. Make sure you have a clean and up-to-date working directory pointing to master branch 4 | 1. Create a new version. Try to follow [Semantic Versioning](https://semver.org/). 5 | ```bash 6 | npm version [patch|minor|major] 7 | ``` 8 | 1. Publish version to npm. You will need to be logged in. Check [npm adduser](https://docs.npmjs.com/cli/adduser). 9 | ```bash 10 | npm publish 11 | ``` 12 | 1. Update demo page. 13 | ```bash 14 | npm run gh-pages 15 | ``` 16 | -------------------------------------------------------------------------------- /assets/images/star-empty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dreyescat/react-rating/bd070757452e1aa75907f2a156fda2119fb7121a/assets/images/star-empty.png -------------------------------------------------------------------------------- /assets/images/star-full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dreyescat/react-rating/bd070757452e1aa75907f2a156fda2119fb7121a/assets/images/star-full.png -------------------------------------------------------------------------------- /assets/images/star-grey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dreyescat/react-rating/bd070757452e1aa75907f2a156fda2119fb7121a/assets/images/star-grey.png -------------------------------------------------------------------------------- /assets/images/star-red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dreyescat/react-rating/bd070757452e1aa75907f2a156fda2119fb7121a/assets/images/star-red.png -------------------------------------------------------------------------------- /assets/images/star-yellow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dreyescat/react-rating/bd070757452e1aa75907f2a156fda2119fb7121a/assets/images/star-yellow.png -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | // Type definitions for react-rating 1.0.6 by 2 | // Project: https://github.com/dreyescat/react-rating 3 | // Definitions by: Kyle Davis 4 | // Konrad Szwarc 5 | 6 | import { Dictionary } from "lodash"; 7 | import * as React from "react"; 8 | 9 | declare class Rating extends React.Component {} 10 | declare namespace Rating { 11 | 12 | } 13 | 14 | export interface RatingComponentProps { 15 | /** 16 | * Range starting value (exclusive). 17 | * 18 | * Default value: 0 19 | */ 20 | start?: number; 21 | 22 | /** 23 | * Range stop value (inclusive). 24 | * 25 | * Default value: 5 26 | */ 27 | stop?: number; 28 | 29 | /** 30 | * Describes how many values each Symbol represents. For example, 31 | * for a start value of 0, a stop value of 10 and a step of 2, 32 | * we will end up with 5 Symbols, with each Symbol representing 33 | * value increments of 2. 34 | * 35 | * Default value: 1 36 | */ 37 | step?: number; 38 | 39 | /** 40 | * Number of equal subdivisions that can be selected as a rating 41 | * in each Symbol. For example, for a fractions value of 2, you 42 | * will be able to select a rating with a precision of down to 43 | * half a Symbol. Must be >= 1 44 | * 45 | * Default value: 1 46 | */ 47 | fractions?: number; 48 | 49 | /** 50 | * Range starting value (exclusive). 51 | * 52 | * Default value: 0 53 | */ 54 | initialRating?: number; 55 | 56 | /** 57 | * The value that will be used as an initial rating. This is the 58 | * old initialRate. 59 | * 60 | * Default value: 0 61 | */ 62 | className?: string; 63 | 64 | /** 65 | * If you do not define an initialRating value, you can use a 66 | * placeholder rating. Visually, this will have the same result 67 | * as if you had defined an initialRating value. If initialRating 68 | * is set placeholderRating is not taken into account. This is 69 | * the old placeholderRate 70 | * 71 | * Default value: 0 72 | */ 73 | placeholderRating?: number; 74 | 75 | /** 76 | * Whether the rating can be modified or not. 77 | * 78 | * Default value: false 79 | */ 80 | readonly?: boolean; 81 | 82 | /** 83 | * Whether to animate rate hovering or not. 84 | * 85 | * Default value: false 86 | */ 87 | quiet?: boolean; 88 | 89 | /** 90 | * The direction of the rating element contents 91 | * 92 | * Default value: "ltr" 93 | */ 94 | direction?: "rtl" | "ltr"; 95 | 96 | /** 97 | * React element, inline style object, or classes applied to 98 | * the rating symbols when empty. Can also be an array of such 99 | * symbols that will be applied in a circular manner (round-robin). 100 | * This is the old empty. 101 | * 102 | * Default value: Style.empty 103 | */ 104 | emptySymbol?: string | string[] | JSX.Element[] | JSX.Element; 105 | 106 | /** 107 | * React element, inline style object, or classes applied to the rating 108 | * symbols when full. Can also be an array of such symbols that will be 109 | * applied in a circular manner (round-robin). This is the old full. 110 | * 111 | * Default value: Style.full 112 | */ 113 | fullSymbol?: string | string[] | JSX.Element[] | JSX.Element; 114 | 115 | /** 116 | * React element, inline style object, or classes applied to the 117 | * placeholder rating symbols. Can also be an array of such symbols 118 | * that will be applied in a circular manner (round-robin). This 119 | * is the old placeholder. 120 | * 121 | * Default value: Style.placeholder 122 | */ 123 | placeholderSymbol?: string | string[] | JSX.Element[] | JSX.Element; 124 | 125 | /** 126 | * Gets called with the value when a different value than the currently 127 | * set is selected. 128 | */ 129 | onChange?: (value: number) => void; 130 | 131 | /** 132 | * Gets called with the value when you hover over a symbol. The value 133 | * is equal to the value that corresponds to that part of the symbol. 134 | * Gets called in quiet mode too. When hover ends, gets called with 135 | * no value (i.e. undefined as the value). 136 | */ 137 | onHover?: (value: number) => void; 138 | 139 | /** 140 | * Gets called with the value when a symbol is clicked. The value 141 | * is equal to the value that corresponds to that part of the symbol. 142 | */ 143 | onClick?: (value: number) => void; 144 | } 145 | 146 | export default Rating; 147 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | React Rating 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 36 | 57 | 58 | 59 |

Default rating

60 |
61 |

Reset rating

62 |
63 |

Readonly rating

64 |
65 |

Readonly fractional rating

66 |
67 |

React svg element rating

68 |
69 |

React span element rating

70 |
71 |

React img element rating

72 |
73 |

Fontawesome Five Star rating

74 |
75 |

Fontawesome Thumbs Up/Down rating (showcases background icon hiding)

76 |
77 |

Bootstrap Five Heart rating

78 |
79 |

Fractional rating

80 |
81 |

Alert when rate changes

82 |
83 |

Update a label when rate moves

84 | 85 | 86 |

Update a label when rate moves "quietly"

87 | 88 | 89 |

Colored rating

90 | 91 |

Mixed symbols

92 |
93 |

Custom each symbol

94 |
95 |

1 to 10 rating

96 |
97 |

5 to 10 rating

98 |
99 |

1 to 10 with step 2 (odd numbers)

100 |
101 |

10 to 1 with step -2 (odd numbers between [1..10] inverted order)

102 |
103 |

Rating with placeholder

104 |
105 | 106 | 442 | 443 | 444 | -------------------------------------------------------------------------------- /lib/react-rating.cjs.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } 4 | 5 | var React = _interopDefault(require('react')); 6 | 7 | function _typeof(obj) { 8 | if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { 9 | _typeof = function (obj) { 10 | return typeof obj; 11 | }; 12 | } else { 13 | _typeof = function (obj) { 14 | return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; 15 | }; 16 | } 17 | 18 | return _typeof(obj); 19 | } 20 | 21 | function _classCallCheck(instance, Constructor) { 22 | if (!(instance instanceof Constructor)) { 23 | throw new TypeError("Cannot call a class as a function"); 24 | } 25 | } 26 | 27 | function _defineProperties(target, props) { 28 | for (var i = 0; i < props.length; i++) { 29 | var descriptor = props[i]; 30 | descriptor.enumerable = descriptor.enumerable || false; 31 | descriptor.configurable = true; 32 | if ("value" in descriptor) descriptor.writable = true; 33 | Object.defineProperty(target, descriptor.key, descriptor); 34 | } 35 | } 36 | 37 | function _createClass(Constructor, protoProps, staticProps) { 38 | if (protoProps) _defineProperties(Constructor.prototype, protoProps); 39 | if (staticProps) _defineProperties(Constructor, staticProps); 40 | return Constructor; 41 | } 42 | 43 | function _defineProperty(obj, key, value) { 44 | if (key in obj) { 45 | Object.defineProperty(obj, key, { 46 | value: value, 47 | enumerable: true, 48 | configurable: true, 49 | writable: true 50 | }); 51 | } else { 52 | obj[key] = value; 53 | } 54 | 55 | return obj; 56 | } 57 | 58 | function _extends() { 59 | _extends = Object.assign || function (target) { 60 | for (var i = 1; i < arguments.length; i++) { 61 | var source = arguments[i]; 62 | 63 | for (var key in source) { 64 | if (Object.prototype.hasOwnProperty.call(source, key)) { 65 | target[key] = source[key]; 66 | } 67 | } 68 | } 69 | 70 | return target; 71 | }; 72 | 73 | return _extends.apply(this, arguments); 74 | } 75 | 76 | function _objectSpread(target) { 77 | for (var i = 1; i < arguments.length; i++) { 78 | var source = arguments[i] != null ? arguments[i] : {}; 79 | var ownKeys = Object.keys(source); 80 | 81 | if (typeof Object.getOwnPropertySymbols === 'function') { 82 | ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { 83 | return Object.getOwnPropertyDescriptor(source, sym).enumerable; 84 | })); 85 | } 86 | 87 | ownKeys.forEach(function (key) { 88 | _defineProperty(target, key, source[key]); 89 | }); 90 | } 91 | 92 | return target; 93 | } 94 | 95 | function _inherits(subClass, superClass) { 96 | if (typeof superClass !== "function" && superClass !== null) { 97 | throw new TypeError("Super expression must either be null or a function"); 98 | } 99 | 100 | subClass.prototype = Object.create(superClass && superClass.prototype, { 101 | constructor: { 102 | value: subClass, 103 | writable: true, 104 | configurable: true 105 | } 106 | }); 107 | if (superClass) _setPrototypeOf(subClass, superClass); 108 | } 109 | 110 | function _getPrototypeOf(o) { 111 | _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { 112 | return o.__proto__ || Object.getPrototypeOf(o); 113 | }; 114 | return _getPrototypeOf(o); 115 | } 116 | 117 | function _setPrototypeOf(o, p) { 118 | _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { 119 | o.__proto__ = p; 120 | return o; 121 | }; 122 | 123 | return _setPrototypeOf(o, p); 124 | } 125 | 126 | function _assertThisInitialized(self) { 127 | if (self === void 0) { 128 | throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); 129 | } 130 | 131 | return self; 132 | } 133 | 134 | function _possibleConstructorReturn(self, call) { 135 | if (call && (typeof call === "object" || typeof call === "function")) { 136 | return call; 137 | } 138 | 139 | return _assertThisInitialized(self); 140 | } 141 | 142 | var style = { 143 | display: 'inline-block', 144 | borderRadius: '50%', 145 | border: '5px double white', 146 | width: 30, 147 | height: 30 148 | }; 149 | var Style = { 150 | empty: _objectSpread({}, style, { 151 | backgroundColor: '#ccc' 152 | }), 153 | full: _objectSpread({}, style, { 154 | backgroundColor: 'black' 155 | }), 156 | placeholder: _objectSpread({}, style, { 157 | backgroundColor: 'red' 158 | }) 159 | }; 160 | 161 | // Return the corresponding React node for an icon. 162 | var _iconNode = function _iconNode(icon) { 163 | // If it is already a React Element just return it. 164 | if (React.isValidElement(icon)) { 165 | return icon; 166 | } // If it is an object, try to use it as a CSS style object. 167 | 168 | 169 | if (_typeof(icon) === 'object' && icon !== null) { 170 | return React.createElement("span", { 171 | style: icon 172 | }); 173 | } // If it is a string, use it as class names. 174 | 175 | 176 | if (Object.prototype.toString.call(icon) === '[object String]') { 177 | return React.createElement("span", { 178 | className: icon 179 | }); 180 | } 181 | }; 182 | 183 | var RatingSymbol = 184 | /*#__PURE__*/ 185 | function (_React$PureComponent) { 186 | _inherits(RatingSymbol, _React$PureComponent); 187 | 188 | function RatingSymbol() { 189 | _classCallCheck(this, RatingSymbol); 190 | 191 | return _possibleConstructorReturn(this, _getPrototypeOf(RatingSymbol).apply(this, arguments)); 192 | } 193 | 194 | _createClass(RatingSymbol, [{ 195 | key: "render", 196 | value: function render() { 197 | var _iconContainerStyle; 198 | 199 | var _this$props = this.props, 200 | index = _this$props.index, 201 | inactiveIcon = _this$props.inactiveIcon, 202 | activeIcon = _this$props.activeIcon, 203 | percent = _this$props.percent, 204 | direction = _this$props.direction, 205 | readonly = _this$props.readonly, 206 | onClick = _this$props.onClick, 207 | onMouseMove = _this$props.onMouseMove, 208 | onTouchEnd = _this$props.onTouchEnd; 209 | 210 | var backgroundNode = _iconNode(inactiveIcon); 211 | 212 | var showbgIcon = percent < 100; 213 | var bgIconContainerStyle = showbgIcon ? {} : { 214 | visibility: 'hidden' 215 | }; 216 | 217 | var iconNode = _iconNode(activeIcon); 218 | 219 | var iconContainerStyle = (_iconContainerStyle = { 220 | display: 'inline-block', 221 | position: 'absolute', 222 | overflow: 'hidden', 223 | top: 0 224 | }, _defineProperty(_iconContainerStyle, direction === 'rtl' ? 'right' : 'left', 0), _defineProperty(_iconContainerStyle, "width", "".concat(percent, "%")), _iconContainerStyle); 225 | var style = { 226 | cursor: !readonly ? 'pointer' : 'inherit', 227 | display: 'inline-block', 228 | position: 'relative' 229 | }; 230 | 231 | function handleMouseMove(e) { 232 | if (onMouseMove) { 233 | onMouseMove(index, e); 234 | } 235 | } 236 | 237 | function handleMouseClick(e) { 238 | if (onClick) { 239 | // [Supporting both TouchEvent and MouseEvent](https://developer.mozilla.org/en-US/docs/Web/API/Touch_events/Supporting_both_TouchEvent_and_MouseEvent) 240 | // We must prevent firing click event twice on touch devices. 241 | e.preventDefault(); 242 | onClick(index, e); 243 | } 244 | } 245 | 246 | function handleTouchEnd(e) { 247 | if (onTouchEnd) { 248 | onTouchEnd(index, e); 249 | } 250 | } 251 | 252 | return React.createElement("span", { 253 | style: style, 254 | onClick: handleMouseClick, 255 | onMouseMove: handleMouseMove, 256 | onTouchMove: handleMouseMove, 257 | onTouchEnd: handleTouchEnd 258 | }, React.createElement("span", { 259 | style: bgIconContainerStyle 260 | }, backgroundNode), React.createElement("span", { 261 | style: iconContainerStyle 262 | }, iconNode)); 263 | } 264 | }]); 265 | 266 | return RatingSymbol; 267 | }(React.PureComponent); // Define propTypes only in development. They will be void in production. 268 | 269 | var Rating = 270 | /*#__PURE__*/ 271 | function (_React$PureComponent) { 272 | _inherits(Rating, _React$PureComponent); 273 | 274 | function Rating(props) { 275 | var _this; 276 | 277 | _classCallCheck(this, Rating); 278 | 279 | _this = _possibleConstructorReturn(this, _getPrototypeOf(Rating).call(this, props)); 280 | _this.state = { 281 | // Indicates the value that is displayed to the user in the form of symbols. 282 | // It can be either 0 (for no displayed symbols) or (0, end] 283 | displayValue: _this.props.value, 284 | // Indicates if the user is currently hovering over the rating element 285 | interacting: false 286 | }; 287 | _this.onMouseLeave = _this.onMouseLeave.bind(_assertThisInitialized(_assertThisInitialized(_this))); 288 | _this.symbolMouseMove = _this.symbolMouseMove.bind(_assertThisInitialized(_assertThisInitialized(_this))); 289 | _this.symbolClick = _this.symbolClick.bind(_assertThisInitialized(_assertThisInitialized(_this))); 290 | _this.symbolEnd = _this.symbolEnd.bind(_assertThisInitialized(_assertThisInitialized(_this))); 291 | return _this; 292 | } 293 | 294 | _createClass(Rating, [{ 295 | key: "UNSAFE_componentWillReceiveProps", 296 | value: function UNSAFE_componentWillReceiveProps(nextProps) { 297 | var valueChanged = this.props.value !== nextProps.value; 298 | this.setState(function (prevState) { 299 | return { 300 | displayValue: valueChanged ? nextProps.value : prevState.displayValue 301 | }; 302 | }); 303 | } // NOTE: This callback is a little bit fragile. Needs some "care" because 304 | // it relies on brittle state kept with different props and state 305 | // combinations to try to figure out from where we are coming, I mean, what 306 | // caused this update. 307 | 308 | }, { 309 | key: "componentDidUpdate", 310 | value: function componentDidUpdate(prevProps, prevState) { 311 | // When hover ends, call this.props.onHover with no value. 312 | if (prevState.interacting && !this.state.interacting) { 313 | return this.props.onHover(); 314 | } // When hover over. 315 | // Hover in should only be emitted while we are hovering (interacting), 316 | // unless we changed the value, usually originated by an onClick event. 317 | // We do not want to emit a hover in event again on the clicked 318 | // symbol. 319 | 320 | 321 | if (this.state.interacting && prevProps.value == this.props.value) { 322 | this.props.onHover(this.state.displayValue); 323 | } 324 | } 325 | }, { 326 | key: "symbolEnd", 327 | value: function symbolEnd(symbolIndex, event) { 328 | // Do not raise the click event on quiet mode when a touch end is received. 329 | // On quiet mode the touch end event only notifies that we have left the 330 | // symbol. We wait for the actual click event to call the symbolClick. 331 | // On not quiet mode we simulate the click event on touch end and we just 332 | // prevent the real on click event to be raised. 333 | // NOTE: I know how we manage click events on touch devices is a little bit 334 | // weird because we do not notify the starting value that was clicked but 335 | // the last (touched) value. 336 | if (!this.props.quiet) { 337 | this.symbolClick(symbolIndex, event); 338 | event.preventDefault(); 339 | } // On touch end we are "leaving" the symbol. 340 | 341 | 342 | this.onMouseLeave(); 343 | } 344 | }, { 345 | key: "symbolClick", 346 | value: function symbolClick(symbolIndex, event) { 347 | var value = this.calculateDisplayValue(symbolIndex, event); 348 | this.props.onClick(value, event); 349 | } 350 | }, { 351 | key: "symbolMouseMove", 352 | value: function symbolMouseMove(symbolIndex, event) { 353 | var value = this.calculateDisplayValue(symbolIndex, event); // This call should cause an update only if the state changes. 354 | // Mainly the first time the mouse enters and whenever the value changes. 355 | // So DidComponentUpdate is NOT called for every mouse movement. 356 | 357 | this.setState({ 358 | interacting: !this.props.readonly, 359 | displayValue: value 360 | }); 361 | } 362 | }, { 363 | key: "onMouseLeave", 364 | value: function onMouseLeave() { 365 | this.setState({ 366 | displayValue: this.props.value, 367 | interacting: false 368 | }); 369 | } 370 | }, { 371 | key: "calculateDisplayValue", 372 | value: function calculateDisplayValue(symbolIndex, event) { 373 | var percentage = this.calculateHoverPercentage(event); // Get the closest top fraction. 374 | 375 | var fraction = Math.ceil(percentage % 1 * this.props.fractions) / this.props.fractions; // Truncate decimal trying to avoid float precission issues. 376 | 377 | var precision = Math.pow(10, 3); 378 | var displayValue = symbolIndex + (Math.floor(percentage) + Math.floor(fraction * precision) / precision); // ensure the returned value is greater than 0 and lower than totalSymbols 379 | 380 | return displayValue > 0 ? displayValue > this.props.totalSymbols ? this.props.totalSymbols : displayValue : 1 / this.props.fractions; 381 | } 382 | }, { 383 | key: "calculateHoverPercentage", 384 | value: function calculateHoverPercentage(event) { 385 | var clientX = event.nativeEvent.type.indexOf("touch") > -1 ? event.nativeEvent.type.indexOf("touchend") > -1 ? event.changedTouches[0].clientX : event.touches[0].clientX : event.clientX; 386 | var targetRect = event.target.getBoundingClientRect(); 387 | var delta = this.props.direction === 'rtl' ? targetRect.right - clientX : clientX - targetRect.left; // Returning 0 if the delta is negative solves the flickering issue 388 | 389 | return delta < 0 ? 0 : delta / targetRect.width; 390 | } 391 | }, { 392 | key: "render", 393 | value: function render() { 394 | var _this$props = this.props, 395 | readonly = _this$props.readonly, 396 | quiet = _this$props.quiet, 397 | totalSymbols = _this$props.totalSymbols, 398 | value = _this$props.value, 399 | placeholderValue = _this$props.placeholderValue, 400 | direction = _this$props.direction, 401 | emptySymbol = _this$props.emptySymbol, 402 | fullSymbol = _this$props.fullSymbol, 403 | placeholderSymbol = _this$props.placeholderSymbol, 404 | className = _this$props.className, 405 | id = _this$props.id, 406 | style = _this$props.style, 407 | tabIndex = _this$props.tabIndex; 408 | var _this$state = this.state, 409 | displayValue = _this$state.displayValue, 410 | interacting = _this$state.interacting; 411 | var symbolNodes = []; 412 | var empty = [].concat(emptySymbol); 413 | var full = [].concat(fullSymbol); 414 | var placeholder = [].concat(placeholderSymbol); 415 | var shouldDisplayPlaceholder = placeholderValue !== 0 && value === 0 && !interacting; // The value that will be used as base for calculating how to render the symbols 416 | 417 | var renderedValue; 418 | 419 | if (shouldDisplayPlaceholder) { 420 | renderedValue = placeholderValue; 421 | } else { 422 | renderedValue = quiet ? value : displayValue; 423 | } // The amount of full symbols 424 | 425 | 426 | var fullSymbols = Math.floor(renderedValue); 427 | 428 | for (var i = 0; i < totalSymbols; i++) { 429 | var percent = void 0; // Calculate each symbol's fullness percentage 430 | 431 | if (i - fullSymbols < 0) { 432 | percent = 100; 433 | } else if (i - fullSymbols === 0) { 434 | percent = (renderedValue - i) * 100; 435 | } else { 436 | percent = 0; 437 | } 438 | 439 | symbolNodes.push(React.createElement(RatingSymbol, _extends({ 440 | key: i, 441 | index: i, 442 | readonly: readonly, 443 | inactiveIcon: empty[i % empty.length], 444 | activeIcon: shouldDisplayPlaceholder ? placeholder[i % full.length] : full[i % full.length], 445 | percent: percent, 446 | direction: direction 447 | }, !readonly && { 448 | onClick: this.symbolClick, 449 | onMouseMove: this.symbolMouseMove, 450 | onTouchMove: this.symbolMouseMove, 451 | onTouchEnd: this.symbolEnd 452 | }))); 453 | } 454 | 455 | return React.createElement("span", _extends({ 456 | id: id, 457 | style: _objectSpread({}, style, { 458 | display: 'inline-block', 459 | direction: direction 460 | }), 461 | className: className, 462 | tabIndex: tabIndex, 463 | "aria-label": this.props['aria-label'] 464 | }, !readonly && { 465 | onMouseLeave: this.onMouseLeave 466 | }), symbolNodes); 467 | } 468 | }]); 469 | 470 | return Rating; 471 | }(React.PureComponent); // Define propTypes only in development. 472 | 473 | function noop() {} 474 | 475 | noop._name = 'react_rating_noop'; 476 | 477 | var RatingAPILayer = 478 | /*#__PURE__*/ 479 | function (_React$PureComponent) { 480 | _inherits(RatingAPILayer, _React$PureComponent); 481 | 482 | function RatingAPILayer(props) { 483 | var _this; 484 | 485 | _classCallCheck(this, RatingAPILayer); 486 | 487 | _this = _possibleConstructorReturn(this, _getPrototypeOf(RatingAPILayer).call(this, props)); 488 | _this.state = { 489 | value: props.initialRating 490 | }; 491 | _this.handleClick = _this.handleClick.bind(_assertThisInitialized(_assertThisInitialized(_this))); 492 | _this.handleHover = _this.handleHover.bind(_assertThisInitialized(_assertThisInitialized(_this))); 493 | return _this; 494 | } 495 | 496 | _createClass(RatingAPILayer, [{ 497 | key: "UNSAFE_componentWillReceiveProps", 498 | value: function UNSAFE_componentWillReceiveProps(nextProps) { 499 | this.setState({ 500 | value: nextProps.initialRating 501 | }); 502 | } 503 | }, { 504 | key: "handleClick", 505 | value: function handleClick(value, e) { 506 | var _this2 = this; 507 | 508 | var newValue = this.translateDisplayValueToValue(value); 509 | this.props.onClick(newValue); // Avoid calling setState if not necessary. Micro optimisation. 510 | 511 | if (this.state.value !== newValue) { 512 | // If we have a new value trigger onChange callback. 513 | this.setState({ 514 | value: newValue 515 | }, function () { 516 | return _this2.props.onChange(_this2.state.value); 517 | }); 518 | } 519 | } 520 | }, { 521 | key: "handleHover", 522 | value: function handleHover(displayValue) { 523 | var value = displayValue === undefined ? displayValue : this.translateDisplayValueToValue(displayValue); 524 | this.props.onHover(value); 525 | } 526 | }, { 527 | key: "translateDisplayValueToValue", 528 | value: function translateDisplayValueToValue(displayValue) { 529 | var translatedValue = displayValue * this.props.step + this.props.start; // minimum value cannot be equal to start, since it's exclusive 530 | 531 | return translatedValue === this.props.start ? translatedValue + 1 / this.props.fractions : translatedValue; 532 | } 533 | }, { 534 | key: "tranlateValueToDisplayValue", 535 | value: function tranlateValueToDisplayValue(value) { 536 | if (value === undefined) { 537 | return 0; 538 | } 539 | 540 | return (value - this.props.start) / this.props.step; 541 | } 542 | }, { 543 | key: "render", 544 | value: function render() { 545 | var _this$props = this.props, 546 | step = _this$props.step, 547 | emptySymbol = _this$props.emptySymbol, 548 | fullSymbol = _this$props.fullSymbol, 549 | placeholderSymbol = _this$props.placeholderSymbol, 550 | readonly = _this$props.readonly, 551 | quiet = _this$props.quiet, 552 | fractions = _this$props.fractions, 553 | direction = _this$props.direction, 554 | start = _this$props.start, 555 | stop = _this$props.stop, 556 | id = _this$props.id, 557 | className = _this$props.className, 558 | style = _this$props.style, 559 | tabIndex = _this$props.tabIndex; 560 | 561 | function calculateTotalSymbols(start, stop, step) { 562 | return Math.floor((stop - start) / step); 563 | } 564 | 565 | return React.createElement(Rating, { 566 | id: id, 567 | style: style, 568 | className: className, 569 | tabIndex: tabIndex, 570 | "aria-label": this.props['aria-label'], 571 | totalSymbols: calculateTotalSymbols(start, stop, step), 572 | value: this.tranlateValueToDisplayValue(this.state.value), 573 | placeholderValue: this.tranlateValueToDisplayValue(this.props.placeholderRating), 574 | readonly: readonly, 575 | quiet: quiet, 576 | fractions: fractions, 577 | direction: direction, 578 | emptySymbol: emptySymbol, 579 | fullSymbol: fullSymbol, 580 | placeholderSymbol: placeholderSymbol, 581 | onClick: this.handleClick, 582 | onHover: this.handleHover 583 | }); 584 | } 585 | }]); 586 | 587 | return RatingAPILayer; 588 | }(React.PureComponent); 589 | 590 | RatingAPILayer.defaultProps = { 591 | start: 0, 592 | stop: 5, 593 | step: 1, 594 | readonly: false, 595 | quiet: false, 596 | fractions: 1, 597 | direction: 'ltr', 598 | onHover: noop, 599 | onClick: noop, 600 | onChange: noop, 601 | emptySymbol: Style.empty, 602 | fullSymbol: Style.full, 603 | placeholderSymbol: Style.placeholder 604 | }; // Define propTypes only in development. 605 | 606 | module.exports = RatingAPILayer; 607 | -------------------------------------------------------------------------------- /lib/react-rating.esm.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | function _typeof(obj) { 4 | if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { 5 | _typeof = function (obj) { 6 | return typeof obj; 7 | }; 8 | } else { 9 | _typeof = function (obj) { 10 | return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; 11 | }; 12 | } 13 | 14 | return _typeof(obj); 15 | } 16 | 17 | function _classCallCheck(instance, Constructor) { 18 | if (!(instance instanceof Constructor)) { 19 | throw new TypeError("Cannot call a class as a function"); 20 | } 21 | } 22 | 23 | function _defineProperties(target, props) { 24 | for (var i = 0; i < props.length; i++) { 25 | var descriptor = props[i]; 26 | descriptor.enumerable = descriptor.enumerable || false; 27 | descriptor.configurable = true; 28 | if ("value" in descriptor) descriptor.writable = true; 29 | Object.defineProperty(target, descriptor.key, descriptor); 30 | } 31 | } 32 | 33 | function _createClass(Constructor, protoProps, staticProps) { 34 | if (protoProps) _defineProperties(Constructor.prototype, protoProps); 35 | if (staticProps) _defineProperties(Constructor, staticProps); 36 | return Constructor; 37 | } 38 | 39 | function _defineProperty(obj, key, value) { 40 | if (key in obj) { 41 | Object.defineProperty(obj, key, { 42 | value: value, 43 | enumerable: true, 44 | configurable: true, 45 | writable: true 46 | }); 47 | } else { 48 | obj[key] = value; 49 | } 50 | 51 | return obj; 52 | } 53 | 54 | function _extends() { 55 | _extends = Object.assign || function (target) { 56 | for (var i = 1; i < arguments.length; i++) { 57 | var source = arguments[i]; 58 | 59 | for (var key in source) { 60 | if (Object.prototype.hasOwnProperty.call(source, key)) { 61 | target[key] = source[key]; 62 | } 63 | } 64 | } 65 | 66 | return target; 67 | }; 68 | 69 | return _extends.apply(this, arguments); 70 | } 71 | 72 | function _objectSpread(target) { 73 | for (var i = 1; i < arguments.length; i++) { 74 | var source = arguments[i] != null ? arguments[i] : {}; 75 | var ownKeys = Object.keys(source); 76 | 77 | if (typeof Object.getOwnPropertySymbols === 'function') { 78 | ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { 79 | return Object.getOwnPropertyDescriptor(source, sym).enumerable; 80 | })); 81 | } 82 | 83 | ownKeys.forEach(function (key) { 84 | _defineProperty(target, key, source[key]); 85 | }); 86 | } 87 | 88 | return target; 89 | } 90 | 91 | function _inherits(subClass, superClass) { 92 | if (typeof superClass !== "function" && superClass !== null) { 93 | throw new TypeError("Super expression must either be null or a function"); 94 | } 95 | 96 | subClass.prototype = Object.create(superClass && superClass.prototype, { 97 | constructor: { 98 | value: subClass, 99 | writable: true, 100 | configurable: true 101 | } 102 | }); 103 | if (superClass) _setPrototypeOf(subClass, superClass); 104 | } 105 | 106 | function _getPrototypeOf(o) { 107 | _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { 108 | return o.__proto__ || Object.getPrototypeOf(o); 109 | }; 110 | return _getPrototypeOf(o); 111 | } 112 | 113 | function _setPrototypeOf(o, p) { 114 | _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { 115 | o.__proto__ = p; 116 | return o; 117 | }; 118 | 119 | return _setPrototypeOf(o, p); 120 | } 121 | 122 | function _assertThisInitialized(self) { 123 | if (self === void 0) { 124 | throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); 125 | } 126 | 127 | return self; 128 | } 129 | 130 | function _possibleConstructorReturn(self, call) { 131 | if (call && (typeof call === "object" || typeof call === "function")) { 132 | return call; 133 | } 134 | 135 | return _assertThisInitialized(self); 136 | } 137 | 138 | var style = { 139 | display: 'inline-block', 140 | borderRadius: '50%', 141 | border: '5px double white', 142 | width: 30, 143 | height: 30 144 | }; 145 | var Style = { 146 | empty: _objectSpread({}, style, { 147 | backgroundColor: '#ccc' 148 | }), 149 | full: _objectSpread({}, style, { 150 | backgroundColor: 'black' 151 | }), 152 | placeholder: _objectSpread({}, style, { 153 | backgroundColor: 'red' 154 | }) 155 | }; 156 | 157 | // Return the corresponding React node for an icon. 158 | var _iconNode = function _iconNode(icon) { 159 | // If it is already a React Element just return it. 160 | if (React.isValidElement(icon)) { 161 | return icon; 162 | } // If it is an object, try to use it as a CSS style object. 163 | 164 | 165 | if (_typeof(icon) === 'object' && icon !== null) { 166 | return React.createElement("span", { 167 | style: icon 168 | }); 169 | } // If it is a string, use it as class names. 170 | 171 | 172 | if (Object.prototype.toString.call(icon) === '[object String]') { 173 | return React.createElement("span", { 174 | className: icon 175 | }); 176 | } 177 | }; 178 | 179 | var RatingSymbol = 180 | /*#__PURE__*/ 181 | function (_React$PureComponent) { 182 | _inherits(RatingSymbol, _React$PureComponent); 183 | 184 | function RatingSymbol() { 185 | _classCallCheck(this, RatingSymbol); 186 | 187 | return _possibleConstructorReturn(this, _getPrototypeOf(RatingSymbol).apply(this, arguments)); 188 | } 189 | 190 | _createClass(RatingSymbol, [{ 191 | key: "render", 192 | value: function render() { 193 | var _iconContainerStyle; 194 | 195 | var _this$props = this.props, 196 | index = _this$props.index, 197 | inactiveIcon = _this$props.inactiveIcon, 198 | activeIcon = _this$props.activeIcon, 199 | percent = _this$props.percent, 200 | direction = _this$props.direction, 201 | readonly = _this$props.readonly, 202 | onClick = _this$props.onClick, 203 | onMouseMove = _this$props.onMouseMove, 204 | onTouchEnd = _this$props.onTouchEnd; 205 | 206 | var backgroundNode = _iconNode(inactiveIcon); 207 | 208 | var showbgIcon = percent < 100; 209 | var bgIconContainerStyle = showbgIcon ? {} : { 210 | visibility: 'hidden' 211 | }; 212 | 213 | var iconNode = _iconNode(activeIcon); 214 | 215 | var iconContainerStyle = (_iconContainerStyle = { 216 | display: 'inline-block', 217 | position: 'absolute', 218 | overflow: 'hidden', 219 | top: 0 220 | }, _defineProperty(_iconContainerStyle, direction === 'rtl' ? 'right' : 'left', 0), _defineProperty(_iconContainerStyle, "width", "".concat(percent, "%")), _iconContainerStyle); 221 | var style = { 222 | cursor: !readonly ? 'pointer' : 'inherit', 223 | display: 'inline-block', 224 | position: 'relative' 225 | }; 226 | 227 | function handleMouseMove(e) { 228 | if (onMouseMove) { 229 | onMouseMove(index, e); 230 | } 231 | } 232 | 233 | function handleMouseClick(e) { 234 | if (onClick) { 235 | // [Supporting both TouchEvent and MouseEvent](https://developer.mozilla.org/en-US/docs/Web/API/Touch_events/Supporting_both_TouchEvent_and_MouseEvent) 236 | // We must prevent firing click event twice on touch devices. 237 | e.preventDefault(); 238 | onClick(index, e); 239 | } 240 | } 241 | 242 | function handleTouchEnd(e) { 243 | if (onTouchEnd) { 244 | onTouchEnd(index, e); 245 | } 246 | } 247 | 248 | return React.createElement("span", { 249 | style: style, 250 | onClick: handleMouseClick, 251 | onMouseMove: handleMouseMove, 252 | onTouchMove: handleMouseMove, 253 | onTouchEnd: handleTouchEnd 254 | }, React.createElement("span", { 255 | style: bgIconContainerStyle 256 | }, backgroundNode), React.createElement("span", { 257 | style: iconContainerStyle 258 | }, iconNode)); 259 | } 260 | }]); 261 | 262 | return RatingSymbol; 263 | }(React.PureComponent); // Define propTypes only in development. They will be void in production. 264 | 265 | var Rating = 266 | /*#__PURE__*/ 267 | function (_React$PureComponent) { 268 | _inherits(Rating, _React$PureComponent); 269 | 270 | function Rating(props) { 271 | var _this; 272 | 273 | _classCallCheck(this, Rating); 274 | 275 | _this = _possibleConstructorReturn(this, _getPrototypeOf(Rating).call(this, props)); 276 | _this.state = { 277 | // Indicates the value that is displayed to the user in the form of symbols. 278 | // It can be either 0 (for no displayed symbols) or (0, end] 279 | displayValue: _this.props.value, 280 | // Indicates if the user is currently hovering over the rating element 281 | interacting: false 282 | }; 283 | _this.onMouseLeave = _this.onMouseLeave.bind(_assertThisInitialized(_assertThisInitialized(_this))); 284 | _this.symbolMouseMove = _this.symbolMouseMove.bind(_assertThisInitialized(_assertThisInitialized(_this))); 285 | _this.symbolClick = _this.symbolClick.bind(_assertThisInitialized(_assertThisInitialized(_this))); 286 | _this.symbolEnd = _this.symbolEnd.bind(_assertThisInitialized(_assertThisInitialized(_this))); 287 | return _this; 288 | } 289 | 290 | _createClass(Rating, [{ 291 | key: "UNSAFE_componentWillReceiveProps", 292 | value: function UNSAFE_componentWillReceiveProps(nextProps) { 293 | var valueChanged = this.props.value !== nextProps.value; 294 | this.setState(function (prevState) { 295 | return { 296 | displayValue: valueChanged ? nextProps.value : prevState.displayValue 297 | }; 298 | }); 299 | } // NOTE: This callback is a little bit fragile. Needs some "care" because 300 | // it relies on brittle state kept with different props and state 301 | // combinations to try to figure out from where we are coming, I mean, what 302 | // caused this update. 303 | 304 | }, { 305 | key: "componentDidUpdate", 306 | value: function componentDidUpdate(prevProps, prevState) { 307 | // When hover ends, call this.props.onHover with no value. 308 | if (prevState.interacting && !this.state.interacting) { 309 | return this.props.onHover(); 310 | } // When hover over. 311 | // Hover in should only be emitted while we are hovering (interacting), 312 | // unless we changed the value, usually originated by an onClick event. 313 | // We do not want to emit a hover in event again on the clicked 314 | // symbol. 315 | 316 | 317 | if (this.state.interacting && prevProps.value == this.props.value) { 318 | this.props.onHover(this.state.displayValue); 319 | } 320 | } 321 | }, { 322 | key: "symbolEnd", 323 | value: function symbolEnd(symbolIndex, event) { 324 | // Do not raise the click event on quiet mode when a touch end is received. 325 | // On quiet mode the touch end event only notifies that we have left the 326 | // symbol. We wait for the actual click event to call the symbolClick. 327 | // On not quiet mode we simulate the click event on touch end and we just 328 | // prevent the real on click event to be raised. 329 | // NOTE: I know how we manage click events on touch devices is a little bit 330 | // weird because we do not notify the starting value that was clicked but 331 | // the last (touched) value. 332 | if (!this.props.quiet) { 333 | this.symbolClick(symbolIndex, event); 334 | event.preventDefault(); 335 | } // On touch end we are "leaving" the symbol. 336 | 337 | 338 | this.onMouseLeave(); 339 | } 340 | }, { 341 | key: "symbolClick", 342 | value: function symbolClick(symbolIndex, event) { 343 | var value = this.calculateDisplayValue(symbolIndex, event); 344 | this.props.onClick(value, event); 345 | } 346 | }, { 347 | key: "symbolMouseMove", 348 | value: function symbolMouseMove(symbolIndex, event) { 349 | var value = this.calculateDisplayValue(symbolIndex, event); // This call should cause an update only if the state changes. 350 | // Mainly the first time the mouse enters and whenever the value changes. 351 | // So DidComponentUpdate is NOT called for every mouse movement. 352 | 353 | this.setState({ 354 | interacting: !this.props.readonly, 355 | displayValue: value 356 | }); 357 | } 358 | }, { 359 | key: "onMouseLeave", 360 | value: function onMouseLeave() { 361 | this.setState({ 362 | displayValue: this.props.value, 363 | interacting: false 364 | }); 365 | } 366 | }, { 367 | key: "calculateDisplayValue", 368 | value: function calculateDisplayValue(symbolIndex, event) { 369 | var percentage = this.calculateHoverPercentage(event); // Get the closest top fraction. 370 | 371 | var fraction = Math.ceil(percentage % 1 * this.props.fractions) / this.props.fractions; // Truncate decimal trying to avoid float precission issues. 372 | 373 | var precision = Math.pow(10, 3); 374 | var displayValue = symbolIndex + (Math.floor(percentage) + Math.floor(fraction * precision) / precision); // ensure the returned value is greater than 0 and lower than totalSymbols 375 | 376 | return displayValue > 0 ? displayValue > this.props.totalSymbols ? this.props.totalSymbols : displayValue : 1 / this.props.fractions; 377 | } 378 | }, { 379 | key: "calculateHoverPercentage", 380 | value: function calculateHoverPercentage(event) { 381 | var clientX = event.nativeEvent.type.indexOf("touch") > -1 ? event.nativeEvent.type.indexOf("touchend") > -1 ? event.changedTouches[0].clientX : event.touches[0].clientX : event.clientX; 382 | var targetRect = event.target.getBoundingClientRect(); 383 | var delta = this.props.direction === 'rtl' ? targetRect.right - clientX : clientX - targetRect.left; // Returning 0 if the delta is negative solves the flickering issue 384 | 385 | return delta < 0 ? 0 : delta / targetRect.width; 386 | } 387 | }, { 388 | key: "render", 389 | value: function render() { 390 | var _this$props = this.props, 391 | readonly = _this$props.readonly, 392 | quiet = _this$props.quiet, 393 | totalSymbols = _this$props.totalSymbols, 394 | value = _this$props.value, 395 | placeholderValue = _this$props.placeholderValue, 396 | direction = _this$props.direction, 397 | emptySymbol = _this$props.emptySymbol, 398 | fullSymbol = _this$props.fullSymbol, 399 | placeholderSymbol = _this$props.placeholderSymbol, 400 | className = _this$props.className, 401 | id = _this$props.id, 402 | style = _this$props.style, 403 | tabIndex = _this$props.tabIndex; 404 | var _this$state = this.state, 405 | displayValue = _this$state.displayValue, 406 | interacting = _this$state.interacting; 407 | var symbolNodes = []; 408 | var empty = [].concat(emptySymbol); 409 | var full = [].concat(fullSymbol); 410 | var placeholder = [].concat(placeholderSymbol); 411 | var shouldDisplayPlaceholder = placeholderValue !== 0 && value === 0 && !interacting; // The value that will be used as base for calculating how to render the symbols 412 | 413 | var renderedValue; 414 | 415 | if (shouldDisplayPlaceholder) { 416 | renderedValue = placeholderValue; 417 | } else { 418 | renderedValue = quiet ? value : displayValue; 419 | } // The amount of full symbols 420 | 421 | 422 | var fullSymbols = Math.floor(renderedValue); 423 | 424 | for (var i = 0; i < totalSymbols; i++) { 425 | var percent = void 0; // Calculate each symbol's fullness percentage 426 | 427 | if (i - fullSymbols < 0) { 428 | percent = 100; 429 | } else if (i - fullSymbols === 0) { 430 | percent = (renderedValue - i) * 100; 431 | } else { 432 | percent = 0; 433 | } 434 | 435 | symbolNodes.push(React.createElement(RatingSymbol, _extends({ 436 | key: i, 437 | index: i, 438 | readonly: readonly, 439 | inactiveIcon: empty[i % empty.length], 440 | activeIcon: shouldDisplayPlaceholder ? placeholder[i % full.length] : full[i % full.length], 441 | percent: percent, 442 | direction: direction 443 | }, !readonly && { 444 | onClick: this.symbolClick, 445 | onMouseMove: this.symbolMouseMove, 446 | onTouchMove: this.symbolMouseMove, 447 | onTouchEnd: this.symbolEnd 448 | }))); 449 | } 450 | 451 | return React.createElement("span", _extends({ 452 | id: id, 453 | style: _objectSpread({}, style, { 454 | display: 'inline-block', 455 | direction: direction 456 | }), 457 | className: className, 458 | tabIndex: tabIndex, 459 | "aria-label": this.props['aria-label'] 460 | }, !readonly && { 461 | onMouseLeave: this.onMouseLeave 462 | }), symbolNodes); 463 | } 464 | }]); 465 | 466 | return Rating; 467 | }(React.PureComponent); // Define propTypes only in development. 468 | 469 | function noop() {} 470 | 471 | noop._name = 'react_rating_noop'; 472 | 473 | var RatingAPILayer = 474 | /*#__PURE__*/ 475 | function (_React$PureComponent) { 476 | _inherits(RatingAPILayer, _React$PureComponent); 477 | 478 | function RatingAPILayer(props) { 479 | var _this; 480 | 481 | _classCallCheck(this, RatingAPILayer); 482 | 483 | _this = _possibleConstructorReturn(this, _getPrototypeOf(RatingAPILayer).call(this, props)); 484 | _this.state = { 485 | value: props.initialRating 486 | }; 487 | _this.handleClick = _this.handleClick.bind(_assertThisInitialized(_assertThisInitialized(_this))); 488 | _this.handleHover = _this.handleHover.bind(_assertThisInitialized(_assertThisInitialized(_this))); 489 | return _this; 490 | } 491 | 492 | _createClass(RatingAPILayer, [{ 493 | key: "UNSAFE_componentWillReceiveProps", 494 | value: function UNSAFE_componentWillReceiveProps(nextProps) { 495 | this.setState({ 496 | value: nextProps.initialRating 497 | }); 498 | } 499 | }, { 500 | key: "handleClick", 501 | value: function handleClick(value, e) { 502 | var _this2 = this; 503 | 504 | var newValue = this.translateDisplayValueToValue(value); 505 | this.props.onClick(newValue); // Avoid calling setState if not necessary. Micro optimisation. 506 | 507 | if (this.state.value !== newValue) { 508 | // If we have a new value trigger onChange callback. 509 | this.setState({ 510 | value: newValue 511 | }, function () { 512 | return _this2.props.onChange(_this2.state.value); 513 | }); 514 | } 515 | } 516 | }, { 517 | key: "handleHover", 518 | value: function handleHover(displayValue) { 519 | var value = displayValue === undefined ? displayValue : this.translateDisplayValueToValue(displayValue); 520 | this.props.onHover(value); 521 | } 522 | }, { 523 | key: "translateDisplayValueToValue", 524 | value: function translateDisplayValueToValue(displayValue) { 525 | var translatedValue = displayValue * this.props.step + this.props.start; // minimum value cannot be equal to start, since it's exclusive 526 | 527 | return translatedValue === this.props.start ? translatedValue + 1 / this.props.fractions : translatedValue; 528 | } 529 | }, { 530 | key: "tranlateValueToDisplayValue", 531 | value: function tranlateValueToDisplayValue(value) { 532 | if (value === undefined) { 533 | return 0; 534 | } 535 | 536 | return (value - this.props.start) / this.props.step; 537 | } 538 | }, { 539 | key: "render", 540 | value: function render() { 541 | var _this$props = this.props, 542 | step = _this$props.step, 543 | emptySymbol = _this$props.emptySymbol, 544 | fullSymbol = _this$props.fullSymbol, 545 | placeholderSymbol = _this$props.placeholderSymbol, 546 | readonly = _this$props.readonly, 547 | quiet = _this$props.quiet, 548 | fractions = _this$props.fractions, 549 | direction = _this$props.direction, 550 | start = _this$props.start, 551 | stop = _this$props.stop, 552 | id = _this$props.id, 553 | className = _this$props.className, 554 | style = _this$props.style, 555 | tabIndex = _this$props.tabIndex; 556 | 557 | function calculateTotalSymbols(start, stop, step) { 558 | return Math.floor((stop - start) / step); 559 | } 560 | 561 | return React.createElement(Rating, { 562 | id: id, 563 | style: style, 564 | className: className, 565 | tabIndex: tabIndex, 566 | "aria-label": this.props['aria-label'], 567 | totalSymbols: calculateTotalSymbols(start, stop, step), 568 | value: this.tranlateValueToDisplayValue(this.state.value), 569 | placeholderValue: this.tranlateValueToDisplayValue(this.props.placeholderRating), 570 | readonly: readonly, 571 | quiet: quiet, 572 | fractions: fractions, 573 | direction: direction, 574 | emptySymbol: emptySymbol, 575 | fullSymbol: fullSymbol, 576 | placeholderSymbol: placeholderSymbol, 577 | onClick: this.handleClick, 578 | onHover: this.handleHover 579 | }); 580 | } 581 | }]); 582 | 583 | return RatingAPILayer; 584 | }(React.PureComponent); 585 | 586 | RatingAPILayer.defaultProps = { 587 | start: 0, 588 | stop: 5, 589 | step: 1, 590 | readonly: false, 591 | quiet: false, 592 | fractions: 1, 593 | direction: 'ltr', 594 | onHover: noop, 595 | onClick: noop, 596 | onChange: noop, 597 | emptySymbol: Style.empty, 598 | fullSymbol: Style.full, 599 | placeholderSymbol: Style.placeholder 600 | }; // Define propTypes only in development. 601 | 602 | export default RatingAPILayer; 603 | -------------------------------------------------------------------------------- /lib/react-rating.umd.js: -------------------------------------------------------------------------------- 1 | (function (global, factory) { 2 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('react')) : 3 | typeof define === 'function' && define.amd ? define(['react'], factory) : 4 | (global = global || self, global.ReactRating = factory(global.React)); 5 | }(this, function (React) { 'use strict'; 6 | 7 | React = React && React.hasOwnProperty('default') ? React['default'] : React; 8 | 9 | function _typeof(obj) { 10 | if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { 11 | _typeof = function (obj) { 12 | return typeof obj; 13 | }; 14 | } else { 15 | _typeof = function (obj) { 16 | return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; 17 | }; 18 | } 19 | 20 | return _typeof(obj); 21 | } 22 | 23 | function _classCallCheck(instance, Constructor) { 24 | if (!(instance instanceof Constructor)) { 25 | throw new TypeError("Cannot call a class as a function"); 26 | } 27 | } 28 | 29 | function _defineProperties(target, props) { 30 | for (var i = 0; i < props.length; i++) { 31 | var descriptor = props[i]; 32 | descriptor.enumerable = descriptor.enumerable || false; 33 | descriptor.configurable = true; 34 | if ("value" in descriptor) descriptor.writable = true; 35 | Object.defineProperty(target, descriptor.key, descriptor); 36 | } 37 | } 38 | 39 | function _createClass(Constructor, protoProps, staticProps) { 40 | if (protoProps) _defineProperties(Constructor.prototype, protoProps); 41 | if (staticProps) _defineProperties(Constructor, staticProps); 42 | return Constructor; 43 | } 44 | 45 | function _defineProperty(obj, key, value) { 46 | if (key in obj) { 47 | Object.defineProperty(obj, key, { 48 | value: value, 49 | enumerable: true, 50 | configurable: true, 51 | writable: true 52 | }); 53 | } else { 54 | obj[key] = value; 55 | } 56 | 57 | return obj; 58 | } 59 | 60 | function _extends() { 61 | _extends = Object.assign || function (target) { 62 | for (var i = 1; i < arguments.length; i++) { 63 | var source = arguments[i]; 64 | 65 | for (var key in source) { 66 | if (Object.prototype.hasOwnProperty.call(source, key)) { 67 | target[key] = source[key]; 68 | } 69 | } 70 | } 71 | 72 | return target; 73 | }; 74 | 75 | return _extends.apply(this, arguments); 76 | } 77 | 78 | function _objectSpread(target) { 79 | for (var i = 1; i < arguments.length; i++) { 80 | var source = arguments[i] != null ? arguments[i] : {}; 81 | var ownKeys = Object.keys(source); 82 | 83 | if (typeof Object.getOwnPropertySymbols === 'function') { 84 | ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { 85 | return Object.getOwnPropertyDescriptor(source, sym).enumerable; 86 | })); 87 | } 88 | 89 | ownKeys.forEach(function (key) { 90 | _defineProperty(target, key, source[key]); 91 | }); 92 | } 93 | 94 | return target; 95 | } 96 | 97 | function _inherits(subClass, superClass) { 98 | if (typeof superClass !== "function" && superClass !== null) { 99 | throw new TypeError("Super expression must either be null or a function"); 100 | } 101 | 102 | subClass.prototype = Object.create(superClass && superClass.prototype, { 103 | constructor: { 104 | value: subClass, 105 | writable: true, 106 | configurable: true 107 | } 108 | }); 109 | if (superClass) _setPrototypeOf(subClass, superClass); 110 | } 111 | 112 | function _getPrototypeOf(o) { 113 | _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { 114 | return o.__proto__ || Object.getPrototypeOf(o); 115 | }; 116 | return _getPrototypeOf(o); 117 | } 118 | 119 | function _setPrototypeOf(o, p) { 120 | _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { 121 | o.__proto__ = p; 122 | return o; 123 | }; 124 | 125 | return _setPrototypeOf(o, p); 126 | } 127 | 128 | function _assertThisInitialized(self) { 129 | if (self === void 0) { 130 | throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); 131 | } 132 | 133 | return self; 134 | } 135 | 136 | function _possibleConstructorReturn(self, call) { 137 | if (call && (typeof call === "object" || typeof call === "function")) { 138 | return call; 139 | } 140 | 141 | return _assertThisInitialized(self); 142 | } 143 | 144 | var style = { 145 | display: 'inline-block', 146 | borderRadius: '50%', 147 | border: '5px double white', 148 | width: 30, 149 | height: 30 150 | }; 151 | var Style = { 152 | empty: _objectSpread({}, style, { 153 | backgroundColor: '#ccc' 154 | }), 155 | full: _objectSpread({}, style, { 156 | backgroundColor: 'black' 157 | }), 158 | placeholder: _objectSpread({}, style, { 159 | backgroundColor: 'red' 160 | }) 161 | }; 162 | 163 | // Return the corresponding React node for an icon. 164 | var _iconNode = function _iconNode(icon) { 165 | // If it is already a React Element just return it. 166 | if (React.isValidElement(icon)) { 167 | return icon; 168 | } // If it is an object, try to use it as a CSS style object. 169 | 170 | 171 | if (_typeof(icon) === 'object' && icon !== null) { 172 | return React.createElement("span", { 173 | style: icon 174 | }); 175 | } // If it is a string, use it as class names. 176 | 177 | 178 | if (Object.prototype.toString.call(icon) === '[object String]') { 179 | return React.createElement("span", { 180 | className: icon 181 | }); 182 | } 183 | }; 184 | 185 | var RatingSymbol = 186 | /*#__PURE__*/ 187 | function (_React$PureComponent) { 188 | _inherits(RatingSymbol, _React$PureComponent); 189 | 190 | function RatingSymbol() { 191 | _classCallCheck(this, RatingSymbol); 192 | 193 | return _possibleConstructorReturn(this, _getPrototypeOf(RatingSymbol).apply(this, arguments)); 194 | } 195 | 196 | _createClass(RatingSymbol, [{ 197 | key: "render", 198 | value: function render() { 199 | var _iconContainerStyle; 200 | 201 | var _this$props = this.props, 202 | index = _this$props.index, 203 | inactiveIcon = _this$props.inactiveIcon, 204 | activeIcon = _this$props.activeIcon, 205 | percent = _this$props.percent, 206 | direction = _this$props.direction, 207 | readonly = _this$props.readonly, 208 | onClick = _this$props.onClick, 209 | onMouseMove = _this$props.onMouseMove, 210 | onTouchEnd = _this$props.onTouchEnd; 211 | 212 | var backgroundNode = _iconNode(inactiveIcon); 213 | 214 | var showbgIcon = percent < 100; 215 | var bgIconContainerStyle = showbgIcon ? {} : { 216 | visibility: 'hidden' 217 | }; 218 | 219 | var iconNode = _iconNode(activeIcon); 220 | 221 | var iconContainerStyle = (_iconContainerStyle = { 222 | display: 'inline-block', 223 | position: 'absolute', 224 | overflow: 'hidden', 225 | top: 0 226 | }, _defineProperty(_iconContainerStyle, direction === 'rtl' ? 'right' : 'left', 0), _defineProperty(_iconContainerStyle, "width", "".concat(percent, "%")), _iconContainerStyle); 227 | var style = { 228 | cursor: !readonly ? 'pointer' : 'inherit', 229 | display: 'inline-block', 230 | position: 'relative' 231 | }; 232 | 233 | function handleMouseMove(e) { 234 | if (onMouseMove) { 235 | onMouseMove(index, e); 236 | } 237 | } 238 | 239 | function handleMouseClick(e) { 240 | if (onClick) { 241 | // [Supporting both TouchEvent and MouseEvent](https://developer.mozilla.org/en-US/docs/Web/API/Touch_events/Supporting_both_TouchEvent_and_MouseEvent) 242 | // We must prevent firing click event twice on touch devices. 243 | e.preventDefault(); 244 | onClick(index, e); 245 | } 246 | } 247 | 248 | function handleTouchEnd(e) { 249 | if (onTouchEnd) { 250 | onTouchEnd(index, e); 251 | } 252 | } 253 | 254 | return React.createElement("span", { 255 | style: style, 256 | onClick: handleMouseClick, 257 | onMouseMove: handleMouseMove, 258 | onTouchMove: handleMouseMove, 259 | onTouchEnd: handleTouchEnd 260 | }, React.createElement("span", { 261 | style: bgIconContainerStyle 262 | }, backgroundNode), React.createElement("span", { 263 | style: iconContainerStyle 264 | }, iconNode)); 265 | } 266 | }]); 267 | 268 | return RatingSymbol; 269 | }(React.PureComponent); // Define propTypes only in development. They will be void in production. 270 | 271 | var Rating = 272 | /*#__PURE__*/ 273 | function (_React$PureComponent) { 274 | _inherits(Rating, _React$PureComponent); 275 | 276 | function Rating(props) { 277 | var _this; 278 | 279 | _classCallCheck(this, Rating); 280 | 281 | _this = _possibleConstructorReturn(this, _getPrototypeOf(Rating).call(this, props)); 282 | _this.state = { 283 | // Indicates the value that is displayed to the user in the form of symbols. 284 | // It can be either 0 (for no displayed symbols) or (0, end] 285 | displayValue: _this.props.value, 286 | // Indicates if the user is currently hovering over the rating element 287 | interacting: false 288 | }; 289 | _this.onMouseLeave = _this.onMouseLeave.bind(_assertThisInitialized(_assertThisInitialized(_this))); 290 | _this.symbolMouseMove = _this.symbolMouseMove.bind(_assertThisInitialized(_assertThisInitialized(_this))); 291 | _this.symbolClick = _this.symbolClick.bind(_assertThisInitialized(_assertThisInitialized(_this))); 292 | _this.symbolEnd = _this.symbolEnd.bind(_assertThisInitialized(_assertThisInitialized(_this))); 293 | return _this; 294 | } 295 | 296 | _createClass(Rating, [{ 297 | key: "UNSAFE_componentWillReceiveProps", 298 | value: function UNSAFE_componentWillReceiveProps(nextProps) { 299 | var valueChanged = this.props.value !== nextProps.value; 300 | this.setState(function (prevState) { 301 | return { 302 | displayValue: valueChanged ? nextProps.value : prevState.displayValue 303 | }; 304 | }); 305 | } // NOTE: This callback is a little bit fragile. Needs some "care" because 306 | // it relies on brittle state kept with different props and state 307 | // combinations to try to figure out from where we are coming, I mean, what 308 | // caused this update. 309 | 310 | }, { 311 | key: "componentDidUpdate", 312 | value: function componentDidUpdate(prevProps, prevState) { 313 | // When hover ends, call this.props.onHover with no value. 314 | if (prevState.interacting && !this.state.interacting) { 315 | return this.props.onHover(); 316 | } // When hover over. 317 | // Hover in should only be emitted while we are hovering (interacting), 318 | // unless we changed the value, usually originated by an onClick event. 319 | // We do not want to emit a hover in event again on the clicked 320 | // symbol. 321 | 322 | 323 | if (this.state.interacting && prevProps.value == this.props.value) { 324 | this.props.onHover(this.state.displayValue); 325 | } 326 | } 327 | }, { 328 | key: "symbolEnd", 329 | value: function symbolEnd(symbolIndex, event) { 330 | // Do not raise the click event on quiet mode when a touch end is received. 331 | // On quiet mode the touch end event only notifies that we have left the 332 | // symbol. We wait for the actual click event to call the symbolClick. 333 | // On not quiet mode we simulate the click event on touch end and we just 334 | // prevent the real on click event to be raised. 335 | // NOTE: I know how we manage click events on touch devices is a little bit 336 | // weird because we do not notify the starting value that was clicked but 337 | // the last (touched) value. 338 | if (!this.props.quiet) { 339 | this.symbolClick(symbolIndex, event); 340 | event.preventDefault(); 341 | } // On touch end we are "leaving" the symbol. 342 | 343 | 344 | this.onMouseLeave(); 345 | } 346 | }, { 347 | key: "symbolClick", 348 | value: function symbolClick(symbolIndex, event) { 349 | var value = this.calculateDisplayValue(symbolIndex, event); 350 | this.props.onClick(value, event); 351 | } 352 | }, { 353 | key: "symbolMouseMove", 354 | value: function symbolMouseMove(symbolIndex, event) { 355 | var value = this.calculateDisplayValue(symbolIndex, event); // This call should cause an update only if the state changes. 356 | // Mainly the first time the mouse enters and whenever the value changes. 357 | // So DidComponentUpdate is NOT called for every mouse movement. 358 | 359 | this.setState({ 360 | interacting: !this.props.readonly, 361 | displayValue: value 362 | }); 363 | } 364 | }, { 365 | key: "onMouseLeave", 366 | value: function onMouseLeave() { 367 | this.setState({ 368 | displayValue: this.props.value, 369 | interacting: false 370 | }); 371 | } 372 | }, { 373 | key: "calculateDisplayValue", 374 | value: function calculateDisplayValue(symbolIndex, event) { 375 | var percentage = this.calculateHoverPercentage(event); // Get the closest top fraction. 376 | 377 | var fraction = Math.ceil(percentage % 1 * this.props.fractions) / this.props.fractions; // Truncate decimal trying to avoid float precission issues. 378 | 379 | var precision = Math.pow(10, 3); 380 | var displayValue = symbolIndex + (Math.floor(percentage) + Math.floor(fraction * precision) / precision); // ensure the returned value is greater than 0 and lower than totalSymbols 381 | 382 | return displayValue > 0 ? displayValue > this.props.totalSymbols ? this.props.totalSymbols : displayValue : 1 / this.props.fractions; 383 | } 384 | }, { 385 | key: "calculateHoverPercentage", 386 | value: function calculateHoverPercentage(event) { 387 | var clientX = event.nativeEvent.type.indexOf("touch") > -1 ? event.nativeEvent.type.indexOf("touchend") > -1 ? event.changedTouches[0].clientX : event.touches[0].clientX : event.clientX; 388 | var targetRect = event.target.getBoundingClientRect(); 389 | var delta = this.props.direction === 'rtl' ? targetRect.right - clientX : clientX - targetRect.left; // Returning 0 if the delta is negative solves the flickering issue 390 | 391 | return delta < 0 ? 0 : delta / targetRect.width; 392 | } 393 | }, { 394 | key: "render", 395 | value: function render() { 396 | var _this$props = this.props, 397 | readonly = _this$props.readonly, 398 | quiet = _this$props.quiet, 399 | totalSymbols = _this$props.totalSymbols, 400 | value = _this$props.value, 401 | placeholderValue = _this$props.placeholderValue, 402 | direction = _this$props.direction, 403 | emptySymbol = _this$props.emptySymbol, 404 | fullSymbol = _this$props.fullSymbol, 405 | placeholderSymbol = _this$props.placeholderSymbol, 406 | className = _this$props.className, 407 | id = _this$props.id, 408 | style = _this$props.style, 409 | tabIndex = _this$props.tabIndex; 410 | var _this$state = this.state, 411 | displayValue = _this$state.displayValue, 412 | interacting = _this$state.interacting; 413 | var symbolNodes = []; 414 | var empty = [].concat(emptySymbol); 415 | var full = [].concat(fullSymbol); 416 | var placeholder = [].concat(placeholderSymbol); 417 | var shouldDisplayPlaceholder = placeholderValue !== 0 && value === 0 && !interacting; // The value that will be used as base for calculating how to render the symbols 418 | 419 | var renderedValue; 420 | 421 | if (shouldDisplayPlaceholder) { 422 | renderedValue = placeholderValue; 423 | } else { 424 | renderedValue = quiet ? value : displayValue; 425 | } // The amount of full symbols 426 | 427 | 428 | var fullSymbols = Math.floor(renderedValue); 429 | 430 | for (var i = 0; i < totalSymbols; i++) { 431 | var percent = void 0; // Calculate each symbol's fullness percentage 432 | 433 | if (i - fullSymbols < 0) { 434 | percent = 100; 435 | } else if (i - fullSymbols === 0) { 436 | percent = (renderedValue - i) * 100; 437 | } else { 438 | percent = 0; 439 | } 440 | 441 | symbolNodes.push(React.createElement(RatingSymbol, _extends({ 442 | key: i, 443 | index: i, 444 | readonly: readonly, 445 | inactiveIcon: empty[i % empty.length], 446 | activeIcon: shouldDisplayPlaceholder ? placeholder[i % full.length] : full[i % full.length], 447 | percent: percent, 448 | direction: direction 449 | }, !readonly && { 450 | onClick: this.symbolClick, 451 | onMouseMove: this.symbolMouseMove, 452 | onTouchMove: this.symbolMouseMove, 453 | onTouchEnd: this.symbolEnd 454 | }))); 455 | } 456 | 457 | return React.createElement("span", _extends({ 458 | id: id, 459 | style: _objectSpread({}, style, { 460 | display: 'inline-block', 461 | direction: direction 462 | }), 463 | className: className, 464 | tabIndex: tabIndex, 465 | "aria-label": this.props['aria-label'] 466 | }, !readonly && { 467 | onMouseLeave: this.onMouseLeave 468 | }), symbolNodes); 469 | } 470 | }]); 471 | 472 | return Rating; 473 | }(React.PureComponent); // Define propTypes only in development. 474 | 475 | function noop() {} 476 | 477 | noop._name = 'react_rating_noop'; 478 | 479 | var RatingAPILayer = 480 | /*#__PURE__*/ 481 | function (_React$PureComponent) { 482 | _inherits(RatingAPILayer, _React$PureComponent); 483 | 484 | function RatingAPILayer(props) { 485 | var _this; 486 | 487 | _classCallCheck(this, RatingAPILayer); 488 | 489 | _this = _possibleConstructorReturn(this, _getPrototypeOf(RatingAPILayer).call(this, props)); 490 | _this.state = { 491 | value: props.initialRating 492 | }; 493 | _this.handleClick = _this.handleClick.bind(_assertThisInitialized(_assertThisInitialized(_this))); 494 | _this.handleHover = _this.handleHover.bind(_assertThisInitialized(_assertThisInitialized(_this))); 495 | return _this; 496 | } 497 | 498 | _createClass(RatingAPILayer, [{ 499 | key: "UNSAFE_componentWillReceiveProps", 500 | value: function UNSAFE_componentWillReceiveProps(nextProps) { 501 | this.setState({ 502 | value: nextProps.initialRating 503 | }); 504 | } 505 | }, { 506 | key: "handleClick", 507 | value: function handleClick(value, e) { 508 | var _this2 = this; 509 | 510 | var newValue = this.translateDisplayValueToValue(value); 511 | this.props.onClick(newValue); // Avoid calling setState if not necessary. Micro optimisation. 512 | 513 | if (this.state.value !== newValue) { 514 | // If we have a new value trigger onChange callback. 515 | this.setState({ 516 | value: newValue 517 | }, function () { 518 | return _this2.props.onChange(_this2.state.value); 519 | }); 520 | } 521 | } 522 | }, { 523 | key: "handleHover", 524 | value: function handleHover(displayValue) { 525 | var value = displayValue === undefined ? displayValue : this.translateDisplayValueToValue(displayValue); 526 | this.props.onHover(value); 527 | } 528 | }, { 529 | key: "translateDisplayValueToValue", 530 | value: function translateDisplayValueToValue(displayValue) { 531 | var translatedValue = displayValue * this.props.step + this.props.start; // minimum value cannot be equal to start, since it's exclusive 532 | 533 | return translatedValue === this.props.start ? translatedValue + 1 / this.props.fractions : translatedValue; 534 | } 535 | }, { 536 | key: "tranlateValueToDisplayValue", 537 | value: function tranlateValueToDisplayValue(value) { 538 | if (value === undefined) { 539 | return 0; 540 | } 541 | 542 | return (value - this.props.start) / this.props.step; 543 | } 544 | }, { 545 | key: "render", 546 | value: function render() { 547 | var _this$props = this.props, 548 | step = _this$props.step, 549 | emptySymbol = _this$props.emptySymbol, 550 | fullSymbol = _this$props.fullSymbol, 551 | placeholderSymbol = _this$props.placeholderSymbol, 552 | readonly = _this$props.readonly, 553 | quiet = _this$props.quiet, 554 | fractions = _this$props.fractions, 555 | direction = _this$props.direction, 556 | start = _this$props.start, 557 | stop = _this$props.stop, 558 | id = _this$props.id, 559 | className = _this$props.className, 560 | style = _this$props.style, 561 | tabIndex = _this$props.tabIndex; 562 | 563 | function calculateTotalSymbols(start, stop, step) { 564 | return Math.floor((stop - start) / step); 565 | } 566 | 567 | return React.createElement(Rating, { 568 | id: id, 569 | style: style, 570 | className: className, 571 | tabIndex: tabIndex, 572 | "aria-label": this.props['aria-label'], 573 | totalSymbols: calculateTotalSymbols(start, stop, step), 574 | value: this.tranlateValueToDisplayValue(this.state.value), 575 | placeholderValue: this.tranlateValueToDisplayValue(this.props.placeholderRating), 576 | readonly: readonly, 577 | quiet: quiet, 578 | fractions: fractions, 579 | direction: direction, 580 | emptySymbol: emptySymbol, 581 | fullSymbol: fullSymbol, 582 | placeholderSymbol: placeholderSymbol, 583 | onClick: this.handleClick, 584 | onHover: this.handleHover 585 | }); 586 | } 587 | }]); 588 | 589 | return RatingAPILayer; 590 | }(React.PureComponent); 591 | 592 | RatingAPILayer.defaultProps = { 593 | start: 0, 594 | stop: 5, 595 | step: 1, 596 | readonly: false, 597 | quiet: false, 598 | fractions: 1, 599 | direction: 'ltr', 600 | onHover: noop, 601 | onClick: noop, 602 | onChange: noop, 603 | emptySymbol: Style.empty, 604 | fullSymbol: Style.full, 605 | placeholderSymbol: Style.placeholder 606 | }; // Define propTypes only in development. 607 | 608 | return RatingAPILayer; 609 | 610 | })); 611 | //# sourceMappingURL=react-rating.umd.js.map 612 | -------------------------------------------------------------------------------- /lib/react-rating.umd.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"react-rating.umd.js","sources":["../src/utils/style.js","../src/RatingSymbol.js","../src/Rating.js","../src/utils/noop.js","../src/RatingAPILayer.js"],"sourcesContent":["var style = {\n display: 'inline-block',\n borderRadius: '50%',\n border: '5px double white',\n width: 30,\n height: 30\n};\n\nexport default {\n empty: {\n ...style,\n backgroundColor: '#ccc'\n },\n full: {\n ...style,\n backgroundColor: 'black'\n },\n placeholder: {\n ...style,\n backgroundColor: 'red'\n }\n};\n","import React from 'react';\nimport PropTypes from 'prop-types';\n\n// Return the corresponding React node for an icon.\nconst _iconNode = (icon) => {\n // If it is already a React Element just return it.\n if (React.isValidElement(icon)) {\n return icon;\n }\n // If it is an object, try to use it as a CSS style object.\n if (typeof icon === 'object' && icon !== null) {\n return ;\n }\n // If it is a string, use it as class names.\n if (Object.prototype.toString.call(icon) === '[object String]') {\n return ;\n }\n};\n\nclass RatingSymbol extends React.PureComponent {\n render() {\n const {\n index,\n inactiveIcon,\n activeIcon,\n percent,\n direction,\n readonly,\n onClick,\n onMouseMove,\n onTouchEnd\n } = this.props;\n const backgroundNode = _iconNode(inactiveIcon);\n const showbgIcon = percent < 100;\n const bgIconContainerStyle = showbgIcon\n ? {}\n : {\n visibility: 'hidden'\n };\n const iconNode = _iconNode(activeIcon);\n const iconContainerStyle = {\n display: 'inline-block',\n position: 'absolute',\n overflow: 'hidden',\n top: 0,\n [direction === 'rtl' ? 'right' : 'left']: 0,\n width: `${percent}%`\n };\n const style = {\n cursor: !readonly ? 'pointer' : 'inherit',\n display: 'inline-block',\n position: 'relative'\n };\n\n function handleMouseMove(e) {\n if (onMouseMove) {\n onMouseMove(index, e);\n }\n }\n\n function handleMouseClick(e) {\n if (onClick) {\n // [Supporting both TouchEvent and MouseEvent](https://developer.mozilla.org/en-US/docs/Web/API/Touch_events/Supporting_both_TouchEvent_and_MouseEvent)\n // We must prevent firing click event twice on touch devices.\n e.preventDefault();\n onClick(index, e);\n }\n }\n\n function handleTouchEnd(e) {\n if (onTouchEnd) {\n onTouchEnd(index, e);\n }\n }\n\n return (\n \n \n {backgroundNode}\n \n \n {iconNode}\n \n \n );\n }\n}\n\n// Define propTypes only in development. They will be void in production.\nRatingSymbol.propTypes = typeof __DEV__ !== 'undefined' && __DEV__ && {\n index: PropTypes.number.isRequired,\n readonly: PropTypes.bool.isRequired,\n inactiveIcon: PropTypes.oneOfType([\n PropTypes.string,\n PropTypes.object,\n PropTypes.element\n ]).isRequired,\n activeIcon: PropTypes.oneOfType([\n PropTypes.string,\n PropTypes.object,\n PropTypes.element\n ]).isRequired,\n percent: PropTypes.number.isRequired,\n direction: PropTypes.string.isRequired,\n onClick: PropTypes.func,\n onMouseMove: PropTypes.func,\n onTouchMove: PropTypes.func,\n onTouchEnd: PropTypes.func\n};\n\nexport default RatingSymbol;\n","import React from 'react';\nimport PropTypes from 'prop-types';\nimport Symbol from './RatingSymbol';\n\nclass Rating extends React.PureComponent {\n constructor(props) {\n super(props);\n this.state = {\n // Indicates the value that is displayed to the user in the form of symbols.\n // It can be either 0 (for no displayed symbols) or (0, end]\n displayValue: this.props.value,\n // Indicates if the user is currently hovering over the rating element\n interacting: false\n };\n this.onMouseLeave = this.onMouseLeave.bind(this);\n this.symbolMouseMove = this.symbolMouseMove.bind(this);\n this.symbolClick = this.symbolClick.bind(this);\n this.symbolEnd = this.symbolEnd.bind(this);\n }\n\n UNSAFE_componentWillReceiveProps(nextProps) {\n const valueChanged = this.props.value !== nextProps.value;\n this.setState((prevState) => ({\n displayValue: valueChanged ? nextProps.value : prevState.displayValue\n }));\n }\n\n // NOTE: This callback is a little bit fragile. Needs some \"care\" because\n // it relies on brittle state kept with different props and state\n // combinations to try to figure out from where we are coming, I mean, what\n // caused this update.\n componentDidUpdate(prevProps, prevState) {\n // When hover ends, call this.props.onHover with no value.\n if (prevState.interacting && !this.state.interacting) {\n return this.props.onHover();\n }\n\n // When hover over.\n // Hover in should only be emitted while we are hovering (interacting),\n // unless we changed the value, usually originated by an onClick event.\n // We do not want to emit a hover in event again on the clicked\n // symbol.\n if (this.state.interacting && prevProps.value == this.props.value) {\n this.props.onHover(this.state.displayValue);\n }\n }\n\n symbolEnd(symbolIndex, event) {\n // Do not raise the click event on quiet mode when a touch end is received.\n // On quiet mode the touch end event only notifies that we have left the\n // symbol. We wait for the actual click event to call the symbolClick.\n // On not quiet mode we simulate the click event on touch end and we just\n // prevent the real on click event to be raised.\n // NOTE: I know how we manage click events on touch devices is a little bit\n // weird because we do not notify the starting value that was clicked but\n // the last (touched) value.\n if (!this.props.quiet) {\n this.symbolClick(symbolIndex, event);\n event.preventDefault();\n }\n // On touch end we are \"leaving\" the symbol.\n this.onMouseLeave();\n }\n\n symbolClick(symbolIndex, event) {\n const value = this.calculateDisplayValue(symbolIndex, event);\n this.props.onClick(value, event);\n }\n\n symbolMouseMove(symbolIndex, event) {\n const value = this.calculateDisplayValue(symbolIndex, event);\n // This call should cause an update only if the state changes.\n // Mainly the first time the mouse enters and whenever the value changes.\n // So DidComponentUpdate is NOT called for every mouse movement.\n this.setState({\n interacting: !this.props.readonly,\n displayValue: value\n });\n }\n\n onMouseLeave() {\n this.setState({\n displayValue: this.props.value,\n interacting: false\n });\n }\n\n calculateDisplayValue(symbolIndex, event) {\n const percentage = this.calculateHoverPercentage(event);\n // Get the closest top fraction.\n const fraction = Math.ceil(percentage % 1 * this.props.fractions) / this.props.fractions;\n // Truncate decimal trying to avoid float precission issues.\n const precision = 10 ** 3;\n const displayValue =\n symbolIndex + (Math.floor(percentage) + Math.floor(fraction * precision) / precision);\n // ensure the returned value is greater than 0 and lower than totalSymbols\n return displayValue > 0 ? displayValue > this.props.totalSymbols ? this.props.totalSymbols : displayValue : 1 / this.props.fractions;\n }\n\n calculateHoverPercentage(event) {\n const clientX = event.nativeEvent.type.indexOf(\"touch\") > -1\n ? event.nativeEvent.type.indexOf(\"touchend\") > -1\n ? event.changedTouches[0].clientX\n : event.touches[0].clientX\n : event.clientX;\n\n const targetRect = event.target.getBoundingClientRect();\n const delta = this.props.direction === 'rtl'\n ? targetRect.right - clientX\n : clientX - targetRect.left;\n\n // Returning 0 if the delta is negative solves the flickering issue\n return delta < 0 ? 0 : delta / targetRect.width;\n }\n\n render() {\n const {\n readonly,\n quiet,\n totalSymbols,\n value,\n placeholderValue,\n direction,\n emptySymbol,\n fullSymbol,\n placeholderSymbol,\n className,\n id,\n style,\n tabIndex\n } = this.props;\n const { displayValue, interacting } = this.state;\n const symbolNodes = [];\n const empty = [].concat(emptySymbol);\n const full = [].concat(fullSymbol);\n const placeholder = [].concat(placeholderSymbol);\n const shouldDisplayPlaceholder =\n placeholderValue !== 0 &&\n value === 0 &&\n !interacting;\n\n // The value that will be used as base for calculating how to render the symbols\n let renderedValue;\n if (shouldDisplayPlaceholder) {\n renderedValue = placeholderValue;\n } else {\n renderedValue = quiet ? value : displayValue;\n }\n\n // The amount of full symbols\n const fullSymbols = Math.floor(renderedValue);\n\n for (let i = 0; i < totalSymbols; i++) {\n let percent;\n // Calculate each symbol's fullness percentage\n if (i - fullSymbols < 0) {\n percent = 100;\n } else if (i - fullSymbols === 0) {\n percent = (renderedValue - i) * 100;\n } else {\n percent = 0;\n }\n\n symbolNodes.push(\n \n );\n }\n\n return (\n \n {symbolNodes}\n \n );\n }\n}\n\n// Define propTypes only in development.\nRating.propTypes = typeof __DEV__ !== 'undefined' && __DEV__ && {\n totalSymbols: PropTypes.number.isRequired,\n value: PropTypes.number.isRequired, // Always >= 0\n placeholderValue: PropTypes.number.isRequired,\n readonly: PropTypes.bool.isRequired,\n quiet: PropTypes.bool.isRequired,\n fractions: PropTypes.number.isRequired,\n direction: PropTypes.string.isRequired,\n emptySymbol: PropTypes.oneOfType([\n // Array of class names and/or style objects.\n PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.object, PropTypes.element])),\n // Class names.\n PropTypes.string,\n // Style objects.\n PropTypes.object,\n // React element\n PropTypes.element\n ]).isRequired,\n fullSymbol: PropTypes.oneOfType([\n // Array of class names and/or style objects.\n PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.object, PropTypes.element])),\n // Class names.\n PropTypes.string,\n // Style objects.\n PropTypes.object,\n // React element\n PropTypes.element\n ]).isRequired,\n placeholderSymbol: PropTypes.oneOfType([\n // Array of class names and/or style objects.\n PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.object, PropTypes.element])),\n // Class names.\n PropTypes.string,\n // Style objects.\n PropTypes.object,\n // React element\n PropTypes.element\n ]),\n onClick: PropTypes.func.isRequired,\n onHover: PropTypes.func.isRequired\n};\n\nexport default Rating;\n","function noop() {}\nnoop._name = 'react_rating_noop';\n\nexport default noop;\n","import React from 'react';\nimport PropTypes from 'prop-types';\nimport Style from './utils/style';\nimport Rating from './Rating';\nimport noop from './utils/noop';\n\nclass RatingAPILayer extends React.PureComponent {\n constructor(props) {\n super(props);\n this.state = {\n value: props.initialRating\n };\n this.handleClick = this.handleClick.bind(this);\n this.handleHover = this.handleHover.bind(this);\n }\n\n UNSAFE_componentWillReceiveProps(nextProps) {\n this.setState({\n value: nextProps.initialRating\n });\n }\n\n handleClick(value, e) {\n const newValue = this.translateDisplayValueToValue(value);\n this.props.onClick(newValue);\n // Avoid calling setState if not necessary. Micro optimisation.\n if (this.state.value !== newValue) {\n // If we have a new value trigger onChange callback.\n this.setState({\n value: newValue\n }, () => this.props.onChange(this.state.value));\n }\n }\n\n handleHover(displayValue) {\n const value = displayValue === undefined\n ? displayValue\n : this.translateDisplayValueToValue(displayValue);\n this.props.onHover(value);\n }\n\n translateDisplayValueToValue(displayValue) {\n const translatedValue = displayValue * this.props.step + this.props.start;\n // minimum value cannot be equal to start, since it's exclusive\n return translatedValue === this.props.start\n ? translatedValue + 1 / this.props.fractions\n : translatedValue;\n }\n\n tranlateValueToDisplayValue(value) {\n if (value === undefined) {\n return 0;\n }\n return (value - this.props.start) / this.props.step;\n }\n\n render() {\n const {\n step,\n emptySymbol,\n fullSymbol,\n placeholderSymbol,\n readonly,\n quiet,\n fractions,\n direction,\n start,\n stop,\n id,\n className,\n style,\n tabIndex\n } = this.props;\n\n function calculateTotalSymbols(start, stop, step) {\n return Math.floor((stop - start) / step);\n }\n\n return (\n \n );\n }\n}\n\nRatingAPILayer.defaultProps = {\n start: 0,\n stop: 5,\n step: 1,\n readonly: false,\n quiet: false,\n fractions: 1,\n direction: 'ltr',\n onHover: noop,\n onClick: noop,\n onChange: noop,\n emptySymbol: Style.empty,\n fullSymbol: Style.full,\n placeholderSymbol: Style.placeholder\n};\n\n// Define propTypes only in development.\nRatingAPILayer.propTypes = typeof __DEV__ !== 'undefined' && __DEV__ && {\n start: PropTypes.number,\n stop: PropTypes.number,\n step: PropTypes.number,\n initialRating: PropTypes.number,\n placeholderRating: PropTypes.number,\n readonly: PropTypes.bool,\n quiet: PropTypes.bool,\n fractions: PropTypes.number,\n direction: PropTypes.string,\n emptySymbol: PropTypes.oneOfType([\n // Array of class names and/or style objects.\n PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.object, PropTypes.element])),\n // Class names.\n PropTypes.string,\n // Style objects.\n PropTypes.object,\n // React element\n PropTypes.element\n ]),\n fullSymbol: PropTypes.oneOfType([\n // Array of class names and/or style objects.\n PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.object, PropTypes.element])),\n // Class names.\n PropTypes.string,\n // Style objects.\n PropTypes.object,\n // React element\n PropTypes.element\n ]),\n placeholderSymbol: PropTypes.oneOfType([\n // Array of class names and/or style objects.\n PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.object, PropTypes.element])),\n // Class names.\n PropTypes.string,\n // Style objects.\n PropTypes.object,\n // React element\n PropTypes.element\n ]),\n onHover: PropTypes.func,\n onClick: PropTypes.func,\n onChange: PropTypes.func\n};\n\nexport default RatingAPILayer;\n"],"names":["style","display","borderRadius","border","width","height","empty","backgroundColor","full","placeholder","_iconNode","icon","React","isValidElement","Object","prototype","toString","call","RatingSymbol","props","index","inactiveIcon","activeIcon","percent","direction","readonly","onClick","onMouseMove","onTouchEnd","backgroundNode","showbgIcon","bgIconContainerStyle","visibility","iconNode","iconContainerStyle","position","overflow","top","cursor","handleMouseMove","e","handleMouseClick","preventDefault","handleTouchEnd","PureComponent","Rating","state","displayValue","value","interacting","onMouseLeave","bind","symbolMouseMove","symbolClick","symbolEnd","nextProps","valueChanged","setState","prevState","prevProps","onHover","symbolIndex","event","quiet","calculateDisplayValue","percentage","calculateHoverPercentage","fraction","Math","ceil","fractions","precision","floor","totalSymbols","clientX","nativeEvent","type","indexOf","changedTouches","touches","targetRect","target","getBoundingClientRect","delta","right","left","placeholderValue","emptySymbol","fullSymbol","placeholderSymbol","className","id","tabIndex","symbolNodes","concat","shouldDisplayPlaceholder","renderedValue","fullSymbols","i","push","_Symbol","length","onTouchMove","noop","_name","RatingAPILayer","initialRating","handleClick","handleHover","newValue","translateDisplayValueToValue","onChange","undefined","translatedValue","step","start","stop","calculateTotalSymbols","tranlateValueToDisplayValue","placeholderRating","defaultProps","Style"],"mappings":"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"} -------------------------------------------------------------------------------- /lib/react-rating.umd.min.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t(require("react")):"function"==typeof define&&define.amd?define(["react"],t):(e=e||self).ReactRating=t(e.React)}(this,function(e){"use strict";function t(e){return(t="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function n(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e,t){for(var n=0;n0?r>this.props.totalSymbols?this.props.totalSymbols:r:1/this.props.fractions}},{key:"calculateHoverPercentage",value:function(e){var t=e.nativeEvent.type.indexOf("touch")>-1?e.nativeEvent.type.indexOf("touchend")>-1?e.changedTouches[0].clientX:e.touches[0].clientX:e.clientX,n=e.target.getBoundingClientRect(),o="rtl"===this.props.direction?n.right-t:t-n.left;return o<0?0:o/n.width}},{key:"render",value:function(){var t,n=this.props,o=n.readonly,i=n.quiet,r=n.totalSymbols,s=n.value,u=n.placeholderValue,c=n.direction,p=n.emptySymbol,y=n.fullSymbol,h=n.placeholderSymbol,f=n.className,d=n.id,b=n.style,m=n.tabIndex,g=this.state,k=g.displayValue,S=g.interacting,M=[],C=[].concat(p),O=[].concat(y),V=[].concat(h),E=0!==u&&0===s&&!S;t=E?u:i?s:k;for(var w=Math.floor(t),P=0;P {\n // If it is already a React Element just return it.\n if (React.isValidElement(icon)) {\n return icon;\n }\n // If it is an object, try to use it as a CSS style object.\n if (typeof icon === 'object' && icon !== null) {\n return ;\n }\n // If it is a string, use it as class names.\n if (Object.prototype.toString.call(icon) === '[object String]') {\n return ;\n }\n};\n\nclass RatingSymbol extends React.PureComponent {\n render() {\n const {\n index,\n inactiveIcon,\n activeIcon,\n percent,\n direction,\n readonly,\n onClick,\n onMouseMove,\n onTouchEnd\n } = this.props;\n const backgroundNode = _iconNode(inactiveIcon);\n const showbgIcon = percent < 100;\n const bgIconContainerStyle = showbgIcon\n ? {}\n : {\n visibility: 'hidden'\n };\n const iconNode = _iconNode(activeIcon);\n const iconContainerStyle = {\n display: 'inline-block',\n position: 'absolute',\n overflow: 'hidden',\n top: 0,\n [direction === 'rtl' ? 'right' : 'left']: 0,\n width: `${percent}%`\n };\n const style = {\n cursor: !readonly ? 'pointer' : 'inherit',\n display: 'inline-block',\n position: 'relative'\n };\n\n function handleMouseMove(e) {\n if (onMouseMove) {\n onMouseMove(index, e);\n }\n }\n\n function handleMouseClick(e) {\n if (onClick) {\n // [Supporting both TouchEvent and MouseEvent](https://developer.mozilla.org/en-US/docs/Web/API/Touch_events/Supporting_both_TouchEvent_and_MouseEvent)\n // We must prevent firing click event twice on touch devices.\n e.preventDefault();\n onClick(index, e);\n }\n }\n\n function handleTouchEnd(e) {\n if (onTouchEnd) {\n onTouchEnd(index, e);\n }\n }\n\n return (\n \n \n {backgroundNode}\n \n \n {iconNode}\n \n \n );\n }\n}\n\n// Define propTypes only in development. They will be void in production.\nRatingSymbol.propTypes = typeof __DEV__ !== 'undefined' && __DEV__ && {\n index: PropTypes.number.isRequired,\n readonly: PropTypes.bool.isRequired,\n inactiveIcon: PropTypes.oneOfType([\n PropTypes.string,\n PropTypes.object,\n PropTypes.element\n ]).isRequired,\n activeIcon: PropTypes.oneOfType([\n PropTypes.string,\n PropTypes.object,\n PropTypes.element\n ]).isRequired,\n percent: PropTypes.number.isRequired,\n direction: PropTypes.string.isRequired,\n onClick: PropTypes.func,\n onMouseMove: PropTypes.func,\n onTouchMove: PropTypes.func,\n onTouchEnd: PropTypes.func\n};\n\nexport default RatingSymbol;\n","import React from 'react';\nimport PropTypes from 'prop-types';\nimport Symbol from './RatingSymbol';\n\nclass Rating extends React.PureComponent {\n constructor(props) {\n super(props);\n this.state = {\n // Indicates the value that is displayed to the user in the form of symbols.\n // It can be either 0 (for no displayed symbols) or (0, end]\n displayValue: this.props.value,\n // Indicates if the user is currently hovering over the rating element\n interacting: false\n };\n this.onMouseLeave = this.onMouseLeave.bind(this);\n this.symbolMouseMove = this.symbolMouseMove.bind(this);\n this.symbolClick = this.symbolClick.bind(this);\n this.symbolEnd = this.symbolEnd.bind(this);\n }\n\n UNSAFE_componentWillReceiveProps(nextProps) {\n const valueChanged = this.props.value !== nextProps.value;\n this.setState((prevState) => ({\n displayValue: valueChanged ? nextProps.value : prevState.displayValue\n }));\n }\n\n // NOTE: This callback is a little bit fragile. Needs some \"care\" because\n // it relies on brittle state kept with different props and state\n // combinations to try to figure out from where we are coming, I mean, what\n // caused this update.\n componentDidUpdate(prevProps, prevState) {\n // When hover ends, call this.props.onHover with no value.\n if (prevState.interacting && !this.state.interacting) {\n return this.props.onHover();\n }\n\n // When hover over.\n // Hover in should only be emitted while we are hovering (interacting),\n // unless we changed the value, usually originated by an onClick event.\n // We do not want to emit a hover in event again on the clicked\n // symbol.\n if (this.state.interacting && prevProps.value == this.props.value) {\n this.props.onHover(this.state.displayValue);\n }\n }\n\n symbolEnd(symbolIndex, event) {\n // Do not raise the click event on quiet mode when a touch end is received.\n // On quiet mode the touch end event only notifies that we have left the\n // symbol. We wait for the actual click event to call the symbolClick.\n // On not quiet mode we simulate the click event on touch end and we just\n // prevent the real on click event to be raised.\n // NOTE: I know how we manage click events on touch devices is a little bit\n // weird because we do not notify the starting value that was clicked but\n // the last (touched) value.\n if (!this.props.quiet) {\n this.symbolClick(symbolIndex, event);\n event.preventDefault();\n }\n // On touch end we are \"leaving\" the symbol.\n this.onMouseLeave();\n }\n\n symbolClick(symbolIndex, event) {\n const value = this.calculateDisplayValue(symbolIndex, event);\n this.props.onClick(value, event);\n }\n\n symbolMouseMove(symbolIndex, event) {\n const value = this.calculateDisplayValue(symbolIndex, event);\n // This call should cause an update only if the state changes.\n // Mainly the first time the mouse enters and whenever the value changes.\n // So DidComponentUpdate is NOT called for every mouse movement.\n this.setState({\n interacting: !this.props.readonly,\n displayValue: value\n });\n }\n\n onMouseLeave() {\n this.setState({\n displayValue: this.props.value,\n interacting: false\n });\n }\n\n calculateDisplayValue(symbolIndex, event) {\n const percentage = this.calculateHoverPercentage(event);\n // Get the closest top fraction.\n const fraction = Math.ceil(percentage % 1 * this.props.fractions) / this.props.fractions;\n // Truncate decimal trying to avoid float precission issues.\n const precision = 10 ** 3;\n const displayValue =\n symbolIndex + (Math.floor(percentage) + Math.floor(fraction * precision) / precision);\n // ensure the returned value is greater than 0 and lower than totalSymbols\n return displayValue > 0 ? displayValue > this.props.totalSymbols ? this.props.totalSymbols : displayValue : 1 / this.props.fractions;\n }\n\n calculateHoverPercentage(event) {\n const clientX = event.nativeEvent.type.indexOf(\"touch\") > -1\n ? event.nativeEvent.type.indexOf(\"touchend\") > -1\n ? event.changedTouches[0].clientX\n : event.touches[0].clientX\n : event.clientX;\n\n const targetRect = event.target.getBoundingClientRect();\n const delta = this.props.direction === 'rtl'\n ? targetRect.right - clientX\n : clientX - targetRect.left;\n\n // Returning 0 if the delta is negative solves the flickering issue\n return delta < 0 ? 0 : delta / targetRect.width;\n }\n\n render() {\n const {\n readonly,\n quiet,\n totalSymbols,\n value,\n placeholderValue,\n direction,\n emptySymbol,\n fullSymbol,\n placeholderSymbol,\n className,\n id,\n style,\n tabIndex\n } = this.props;\n const { displayValue, interacting } = this.state;\n const symbolNodes = [];\n const empty = [].concat(emptySymbol);\n const full = [].concat(fullSymbol);\n const placeholder = [].concat(placeholderSymbol);\n const shouldDisplayPlaceholder =\n placeholderValue !== 0 &&\n value === 0 &&\n !interacting;\n\n // The value that will be used as base for calculating how to render the symbols\n let renderedValue;\n if (shouldDisplayPlaceholder) {\n renderedValue = placeholderValue;\n } else {\n renderedValue = quiet ? value : displayValue;\n }\n\n // The amount of full symbols\n const fullSymbols = Math.floor(renderedValue);\n\n for (let i = 0; i < totalSymbols; i++) {\n let percent;\n // Calculate each symbol's fullness percentage\n if (i - fullSymbols < 0) {\n percent = 100;\n } else if (i - fullSymbols === 0) {\n percent = (renderedValue - i) * 100;\n } else {\n percent = 0;\n }\n\n symbolNodes.push(\n \n );\n }\n\n return (\n \n {symbolNodes}\n \n );\n }\n}\n\n// Define propTypes only in development.\nRating.propTypes = typeof __DEV__ !== 'undefined' && __DEV__ && {\n totalSymbols: PropTypes.number.isRequired,\n value: PropTypes.number.isRequired, // Always >= 0\n placeholderValue: PropTypes.number.isRequired,\n readonly: PropTypes.bool.isRequired,\n quiet: PropTypes.bool.isRequired,\n fractions: PropTypes.number.isRequired,\n direction: PropTypes.string.isRequired,\n emptySymbol: PropTypes.oneOfType([\n // Array of class names and/or style objects.\n PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.object, PropTypes.element])),\n // Class names.\n PropTypes.string,\n // Style objects.\n PropTypes.object,\n // React element\n PropTypes.element\n ]).isRequired,\n fullSymbol: PropTypes.oneOfType([\n // Array of class names and/or style objects.\n PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.object, PropTypes.element])),\n // Class names.\n PropTypes.string,\n // Style objects.\n PropTypes.object,\n // React element\n PropTypes.element\n ]).isRequired,\n placeholderSymbol: PropTypes.oneOfType([\n // Array of class names and/or style objects.\n PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.object, PropTypes.element])),\n // Class names.\n PropTypes.string,\n // Style objects.\n PropTypes.object,\n // React element\n PropTypes.element\n ]),\n onClick: PropTypes.func.isRequired,\n onHover: PropTypes.func.isRequired\n};\n\nexport default Rating;\n","function noop() {}\nnoop._name = 'react_rating_noop';\n\nexport default noop;\n","import React from 'react';\nimport PropTypes from 'prop-types';\nimport Style from './utils/style';\nimport Rating from './Rating';\nimport noop from './utils/noop';\n\nclass RatingAPILayer extends React.PureComponent {\n constructor(props) {\n super(props);\n this.state = {\n value: props.initialRating\n };\n this.handleClick = this.handleClick.bind(this);\n this.handleHover = this.handleHover.bind(this);\n }\n\n UNSAFE_componentWillReceiveProps(nextProps) {\n this.setState({\n value: nextProps.initialRating\n });\n }\n\n handleClick(value, e) {\n const newValue = this.translateDisplayValueToValue(value);\n this.props.onClick(newValue);\n // Avoid calling setState if not necessary. Micro optimisation.\n if (this.state.value !== newValue) {\n // If we have a new value trigger onChange callback.\n this.setState({\n value: newValue\n }, () => this.props.onChange(this.state.value));\n }\n }\n\n handleHover(displayValue) {\n const value = displayValue === undefined\n ? displayValue\n : this.translateDisplayValueToValue(displayValue);\n this.props.onHover(value);\n }\n\n translateDisplayValueToValue(displayValue) {\n const translatedValue = displayValue * this.props.step + this.props.start;\n // minimum value cannot be equal to start, since it's exclusive\n return translatedValue === this.props.start\n ? translatedValue + 1 / this.props.fractions\n : translatedValue;\n }\n\n tranlateValueToDisplayValue(value) {\n if (value === undefined) {\n return 0;\n }\n return (value - this.props.start) / this.props.step;\n }\n\n render() {\n const {\n step,\n emptySymbol,\n fullSymbol,\n placeholderSymbol,\n readonly,\n quiet,\n fractions,\n direction,\n start,\n stop,\n id,\n className,\n style,\n tabIndex\n } = this.props;\n\n function calculateTotalSymbols(start, stop, step) {\n return Math.floor((stop - start) / step);\n }\n\n return (\n \n );\n }\n}\n\nRatingAPILayer.defaultProps = {\n start: 0,\n stop: 5,\n step: 1,\n readonly: false,\n quiet: false,\n fractions: 1,\n direction: 'ltr',\n onHover: noop,\n onClick: noop,\n onChange: noop,\n emptySymbol: Style.empty,\n fullSymbol: Style.full,\n placeholderSymbol: Style.placeholder\n};\n\n// Define propTypes only in development.\nRatingAPILayer.propTypes = typeof __DEV__ !== 'undefined' && __DEV__ && {\n start: PropTypes.number,\n stop: PropTypes.number,\n step: PropTypes.number,\n initialRating: PropTypes.number,\n placeholderRating: PropTypes.number,\n readonly: PropTypes.bool,\n quiet: PropTypes.bool,\n fractions: PropTypes.number,\n direction: PropTypes.string,\n emptySymbol: PropTypes.oneOfType([\n // Array of class names and/or style objects.\n PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.object, PropTypes.element])),\n // Class names.\n PropTypes.string,\n // Style objects.\n PropTypes.object,\n // React element\n PropTypes.element\n ]),\n fullSymbol: PropTypes.oneOfType([\n // Array of class names and/or style objects.\n PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.object, PropTypes.element])),\n // Class names.\n PropTypes.string,\n // Style objects.\n PropTypes.object,\n // React element\n PropTypes.element\n ]),\n placeholderSymbol: PropTypes.oneOfType([\n // Array of class names and/or style objects.\n PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.object, PropTypes.element])),\n // Class names.\n PropTypes.string,\n // Style objects.\n PropTypes.object,\n // React element\n PropTypes.element\n ]),\n onHover: PropTypes.func,\n onClick: PropTypes.func,\n onChange: PropTypes.func\n};\n\nexport default RatingAPILayer;\n"],"names":["style","display","borderRadius","border","width","height","empty","backgroundColor","full","placeholder","_iconNode","icon","React","isValidElement","_typeof","Object","prototype","toString","call","className","RatingSymbol","PureComponent","this","props","index","inactiveIcon","activeIcon","percent","direction","readonly","onClick","onMouseMove","onTouchEnd","backgroundNode","bgIconContainerStyle","visibility","iconNode","iconContainerStyle","position","overflow","top","cursor","handleMouseMove","e","preventDefault","onTouchMove","Rating","state","displayValue","_this","value","interacting","onMouseLeave","bind","symbolMouseMove","symbolClick","symbolEnd","nextProps","valueChanged","setState","prevState","prevProps","onHover","symbolIndex","event","quiet","calculateDisplayValue","percentage","calculateHoverPercentage","fraction","Math","ceil","fractions","precision","floor","totalSymbols","clientX","nativeEvent","type","indexOf","changedTouches","touches","targetRect","target","getBoundingClientRect","delta","right","left","renderedValue","placeholderValue","emptySymbol","fullSymbol","placeholderSymbol","id","tabIndex","symbolNodes","concat","shouldDisplayPlaceholder","fullSymbols","i","push","_Symbol","key","length","noop","_name","RatingAPILayer","initialRating","handleClick","handleHover","newValue","translateDisplayValueToValue","_this2","onChange","undefined","translatedValue","step","start","stop","calculateTotalSymbols","tranlateValueToDisplayValue","placeholderRating","defaultProps","Style"],"mappings":"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"} -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-rating", 3 | "version": "2.0.6", 4 | "description": "A rating react component with custom symbols", 5 | "main": "lib/react-rating.cjs.js", 6 | "module": "lib/react-rating.esm.js", 7 | "typings": "./index.d.ts", 8 | "files": [ 9 | "index.d.ts", 10 | "README.md", 11 | "LICENSE.md", 12 | "lib/" 13 | ], 14 | "scripts": { 15 | "test": "mocha --compilers js:@babel/register --recursive", 16 | "lint": "eslint src --ext .js,.jsx", 17 | "dev": "rollup -c -w", 18 | "build": "rollup -c", 19 | "version": "npm run build && git add -A lib", 20 | "postversion": "git push && git push --tags", 21 | "gh-pages": "./scripts/gh-pages.sh" 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "https://github.com/dreyescat/react-rating.git" 26 | }, 27 | "keywords": [ 28 | "rating", 29 | "react", 30 | "component", 31 | "react-component", 32 | "bootstrap", 33 | "fontawesome" 34 | ], 35 | "author": "dreyescat", 36 | "license": "MIT", 37 | "bugs": { 38 | "url": "https://github.com/dreyescat/react-rating/issues" 39 | }, 40 | "homepage": "https://github.com/dreyescat/react-rating", 41 | "peerDependencies": { 42 | "react": ">=16.3.0", 43 | "react-dom": ">=16.3.0" 44 | }, 45 | "devDependencies": { 46 | "@babel/core": "^7.0.0", 47 | "@babel/preset-env": "^7.0.0", 48 | "@babel/preset-react": "^7.0.0", 49 | "@babel/register": "^7.0.0", 50 | "@babel/standalone": "^7.0.0", 51 | "babel-eslint": "^9.0.0", 52 | "babel-plugin-transform-react-remove-prop-types": "^0.4.24", 53 | "bootstrap": "^3.3.5", 54 | "chai": "^4.1.2", 55 | "cross-env": "^5.2.0", 56 | "eslint": "^5.5.0", 57 | "eslint-plugin-react": "^7.11.1", 58 | "font-awesome": "^4.3.0", 59 | "mocha": "^5.2.0", 60 | "prismjs": "^1.6.0", 61 | "prop-types": "^15.6.0", 62 | "react": "^16.0.0", 63 | "react-dom": "^16.0.0", 64 | "react-test-renderer": "^16.4.2", 65 | "rollup": "^1.10.1", 66 | "rollup-plugin-babel": "^4.3.2", 67 | "rollup-plugin-terser": "^4.0.4" 68 | }, 69 | "dependencies": { 70 | "@types/lodash": "^4.14.105", 71 | "@types/react": "^16.0.40" 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import babel from 'rollup-plugin-babel'; 2 | import {terser} from 'rollup-plugin-terser'; 3 | import pkg from './package.json'; 4 | 5 | const umd = pkg.main.replace(/\.cjs\.js$/, '.umd.js'); 6 | 7 | export default [ 8 | // UMD build 9 | { 10 | input: 'src/react-rating.js', 11 | output: { 12 | file: umd, 13 | format: 'umd', 14 | name: 'ReactRating', 15 | globals: { 16 | react: 'React' 17 | }, 18 | sourcemap: true 19 | }, 20 | plugins: [ 21 | babel({ 22 | exclude: 'node_modules/**' 23 | }), 24 | ], 25 | external: [ 26 | 'react' 27 | ] 28 | }, 29 | // Minified UMD build 30 | { 31 | input: 'src/react-rating.js', 32 | output: { 33 | file: umd.replace(/\.js/, '.min.js'), 34 | format: 'umd', 35 | name: 'ReactRating', 36 | globals: { 37 | react: 'React' 38 | }, 39 | sourcemap: true 40 | }, 41 | plugins: [ 42 | babel({ 43 | exclude: 'node_modules/**' 44 | }), 45 | terser() 46 | ], 47 | external: [ 48 | 'react' 49 | ] 50 | }, 51 | // CommonJS (for Node) and ES module (for bundlers) build. 52 | { 53 | input: 'src/react-rating.js', 54 | output: [ 55 | { 56 | file: pkg.main, 57 | format: 'cjs' 58 | }, 59 | { 60 | file: pkg.module, 61 | format: 'es' 62 | } 63 | ], 64 | plugins: [ 65 | babel({ 66 | exclude: 'node_modules/**' 67 | }) 68 | ], 69 | external: [ 70 | 'react' 71 | ] 72 | } 73 | ]; 74 | -------------------------------------------------------------------------------- /scripts/gh-pages.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Clone remote repository to dist and point to gh-pages branch 4 | # http://stackoverflow.com/questions/1911109/clone-a-specific-git-branch 5 | # Clone gh-pages preventing fetching of all branches add --single-branch 6 | git clone -b gh-pages https://github.com/dreyescat/react-rating.git dist 7 | 8 | # Checkout changes from master 9 | # Using git without having to change directory (-C dist) 10 | # http://stackoverflow.com/questions/5083224/git-pull-while-not-in-a-git-directory 11 | git -C dist checkout origin/master -- :/index.html :/lib :/assets 12 | 13 | # Sync dependencies keeping full path (-R) 14 | rsync -avR node_modules/react/umd dist 15 | rsync -avR node_modules/react-dom/umd dist 16 | 17 | rsync -avR node_modules/bootstrap/dist dist 18 | 19 | rsync -avR node_modules/font-awesome/css dist 20 | rsync -avR node_modules/font-awesome/fonts dist 21 | 22 | rsync -avR node_modules/prismjs/themes/prism.css dist 23 | rsync -avR node_modules/prismjs/prism.js dist 24 | rsync -avR node_modules/prismjs/components/prism-jsx.min.js dist 25 | 26 | rsync -avR node_modules/@babel/standalone/babel.min.js 27 | 28 | # Add synched dependencies changes 29 | git -C dist add . 30 | 31 | # Commit with the last commit message from master (usually release version) 32 | git -C dist commit -m "$(git log origin/master -1 --pretty=%B)" 33 | 34 | # Push to remote gh-pages 35 | git -C dist push origin gh-pages 36 | 37 | # Remove temporary dist folder 38 | rm -rf dist 39 | -------------------------------------------------------------------------------- /src/Rating.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import Symbol from './RatingSymbol'; 4 | 5 | class Rating extends React.PureComponent { 6 | constructor(props) { 7 | super(props); 8 | this.state = { 9 | // Indicates the value that is displayed to the user in the form of symbols. 10 | // It can be either 0 (for no displayed symbols) or (0, end] 11 | displayValue: this.props.value, 12 | // Indicates if the user is currently hovering over the rating element 13 | interacting: false 14 | }; 15 | this.onMouseLeave = this.onMouseLeave.bind(this); 16 | this.symbolMouseMove = this.symbolMouseMove.bind(this); 17 | this.symbolClick = this.symbolClick.bind(this); 18 | this.symbolEnd = this.symbolEnd.bind(this); 19 | } 20 | 21 | UNSAFE_componentWillReceiveProps(nextProps) { 22 | const valueChanged = this.props.value !== nextProps.value; 23 | this.setState((prevState) => ({ 24 | displayValue: valueChanged ? nextProps.value : prevState.displayValue 25 | })); 26 | } 27 | 28 | // NOTE: This callback is a little bit fragile. Needs some "care" because 29 | // it relies on brittle state kept with different props and state 30 | // combinations to try to figure out from where we are coming, I mean, what 31 | // caused this update. 32 | componentDidUpdate(prevProps, prevState) { 33 | // When hover ends, call this.props.onHover with no value. 34 | if (prevState.interacting && !this.state.interacting) { 35 | return this.props.onHover(); 36 | } 37 | 38 | // When hover over. 39 | // Hover in should only be emitted while we are hovering (interacting), 40 | // unless we changed the value, usually originated by an onClick event. 41 | // We do not want to emit a hover in event again on the clicked 42 | // symbol. 43 | if (this.state.interacting && prevProps.value == this.props.value) { 44 | this.props.onHover(this.state.displayValue); 45 | } 46 | } 47 | 48 | symbolEnd(symbolIndex, event) { 49 | // Do not raise the click event on quiet mode when a touch end is received. 50 | // On quiet mode the touch end event only notifies that we have left the 51 | // symbol. We wait for the actual click event to call the symbolClick. 52 | // On not quiet mode we simulate the click event on touch end and we just 53 | // prevent the real on click event to be raised. 54 | // NOTE: I know how we manage click events on touch devices is a little bit 55 | // weird because we do not notify the starting value that was clicked but 56 | // the last (touched) value. 57 | if (!this.props.quiet) { 58 | this.symbolClick(symbolIndex, event); 59 | event.preventDefault(); 60 | } 61 | // On touch end we are "leaving" the symbol. 62 | this.onMouseLeave(); 63 | } 64 | 65 | symbolClick(symbolIndex, event) { 66 | const value = this.calculateDisplayValue(symbolIndex, event); 67 | this.props.onClick(value, event); 68 | } 69 | 70 | symbolMouseMove(symbolIndex, event) { 71 | const value = this.calculateDisplayValue(symbolIndex, event); 72 | // This call should cause an update only if the state changes. 73 | // Mainly the first time the mouse enters and whenever the value changes. 74 | // So DidComponentUpdate is NOT called for every mouse movement. 75 | this.setState({ 76 | interacting: !this.props.readonly, 77 | displayValue: value 78 | }); 79 | } 80 | 81 | onMouseLeave() { 82 | this.setState({ 83 | displayValue: this.props.value, 84 | interacting: false 85 | }); 86 | } 87 | 88 | calculateDisplayValue(symbolIndex, event) { 89 | const percentage = this.calculateHoverPercentage(event); 90 | // Get the closest top fraction. 91 | const fraction = Math.ceil(percentage % 1 * this.props.fractions) / this.props.fractions; 92 | // Truncate decimal trying to avoid float precission issues. 93 | const precision = 10 ** 3; 94 | const displayValue = 95 | symbolIndex + (Math.floor(percentage) + Math.floor(fraction * precision) / precision); 96 | // ensure the returned value is greater than 0 and lower than totalSymbols 97 | return displayValue > 0 ? displayValue > this.props.totalSymbols ? this.props.totalSymbols : displayValue : 1 / this.props.fractions; 98 | } 99 | 100 | calculateHoverPercentage(event) { 101 | const clientX = event.nativeEvent.type.indexOf("touch") > -1 102 | ? event.nativeEvent.type.indexOf("touchend") > -1 103 | ? event.changedTouches[0].clientX 104 | : event.touches[0].clientX 105 | : event.clientX; 106 | 107 | const targetRect = event.target.getBoundingClientRect(); 108 | const delta = this.props.direction === 'rtl' 109 | ? targetRect.right - clientX 110 | : clientX - targetRect.left; 111 | 112 | // Returning 0 if the delta is negative solves the flickering issue 113 | return delta < 0 ? 0 : delta / targetRect.width; 114 | } 115 | 116 | render() { 117 | const { 118 | readonly, 119 | quiet, 120 | totalSymbols, 121 | value, 122 | placeholderValue, 123 | direction, 124 | emptySymbol, 125 | fullSymbol, 126 | placeholderSymbol, 127 | className, 128 | id, 129 | style, 130 | tabIndex 131 | } = this.props; 132 | const { displayValue, interacting } = this.state; 133 | const symbolNodes = []; 134 | const empty = [].concat(emptySymbol); 135 | const full = [].concat(fullSymbol); 136 | const placeholder = [].concat(placeholderSymbol); 137 | const shouldDisplayPlaceholder = 138 | placeholderValue !== 0 && 139 | value === 0 && 140 | !interacting; 141 | 142 | // The value that will be used as base for calculating how to render the symbols 143 | let renderedValue; 144 | if (shouldDisplayPlaceholder) { 145 | renderedValue = placeholderValue; 146 | } else { 147 | renderedValue = quiet ? value : displayValue; 148 | } 149 | 150 | // The amount of full symbols 151 | const fullSymbols = Math.floor(renderedValue); 152 | 153 | for (let i = 0; i < totalSymbols; i++) { 154 | let percent; 155 | // Calculate each symbol's fullness percentage 156 | if (i - fullSymbols < 0) { 157 | percent = 100; 158 | } else if (i - fullSymbols === 0) { 159 | percent = (renderedValue - i) * 100; 160 | } else { 161 | percent = 0; 162 | } 163 | 164 | symbolNodes.push( 165 | 182 | ); 183 | } 184 | 185 | return ( 186 | 196 | {symbolNodes} 197 | 198 | ); 199 | } 200 | } 201 | 202 | // Define propTypes only in development. 203 | Rating.propTypes = typeof __DEV__ !== 'undefined' && __DEV__ && { 204 | totalSymbols: PropTypes.number.isRequired, 205 | value: PropTypes.number.isRequired, // Always >= 0 206 | placeholderValue: PropTypes.number.isRequired, 207 | readonly: PropTypes.bool.isRequired, 208 | quiet: PropTypes.bool.isRequired, 209 | fractions: PropTypes.number.isRequired, 210 | direction: PropTypes.string.isRequired, 211 | emptySymbol: PropTypes.oneOfType([ 212 | // Array of class names and/or style objects. 213 | PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.object, PropTypes.element])), 214 | // Class names. 215 | PropTypes.string, 216 | // Style objects. 217 | PropTypes.object, 218 | // React element 219 | PropTypes.element 220 | ]).isRequired, 221 | fullSymbol: PropTypes.oneOfType([ 222 | // Array of class names and/or style objects. 223 | PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.object, PropTypes.element])), 224 | // Class names. 225 | PropTypes.string, 226 | // Style objects. 227 | PropTypes.object, 228 | // React element 229 | PropTypes.element 230 | ]).isRequired, 231 | placeholderSymbol: PropTypes.oneOfType([ 232 | // Array of class names and/or style objects. 233 | PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.object, PropTypes.element])), 234 | // Class names. 235 | PropTypes.string, 236 | // Style objects. 237 | PropTypes.object, 238 | // React element 239 | PropTypes.element 240 | ]), 241 | onClick: PropTypes.func.isRequired, 242 | onHover: PropTypes.func.isRequired 243 | }; 244 | 245 | export default Rating; 246 | -------------------------------------------------------------------------------- /src/RatingAPILayer.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import Style from './utils/style'; 4 | import Rating from './Rating'; 5 | import noop from './utils/noop'; 6 | 7 | class RatingAPILayer extends React.PureComponent { 8 | constructor(props) { 9 | super(props); 10 | this.state = { 11 | value: props.initialRating 12 | }; 13 | this.handleClick = this.handleClick.bind(this); 14 | this.handleHover = this.handleHover.bind(this); 15 | } 16 | 17 | UNSAFE_componentWillReceiveProps(nextProps) { 18 | this.setState({ 19 | value: nextProps.initialRating 20 | }); 21 | } 22 | 23 | handleClick(value, e) { 24 | const newValue = this.translateDisplayValueToValue(value); 25 | this.props.onClick(newValue); 26 | // Avoid calling setState if not necessary. Micro optimisation. 27 | if (this.state.value !== newValue) { 28 | // If we have a new value trigger onChange callback. 29 | this.setState({ 30 | value: newValue 31 | }, () => this.props.onChange(this.state.value)); 32 | } 33 | } 34 | 35 | handleHover(displayValue) { 36 | const value = displayValue === undefined 37 | ? displayValue 38 | : this.translateDisplayValueToValue(displayValue); 39 | this.props.onHover(value); 40 | } 41 | 42 | translateDisplayValueToValue(displayValue) { 43 | const translatedValue = displayValue * this.props.step + this.props.start; 44 | // minimum value cannot be equal to start, since it's exclusive 45 | return translatedValue === this.props.start 46 | ? translatedValue + 1 / this.props.fractions 47 | : translatedValue; 48 | } 49 | 50 | tranlateValueToDisplayValue(value) { 51 | if (value === undefined) { 52 | return 0; 53 | } 54 | return (value - this.props.start) / this.props.step; 55 | } 56 | 57 | render() { 58 | const { 59 | step, 60 | emptySymbol, 61 | fullSymbol, 62 | placeholderSymbol, 63 | readonly, 64 | quiet, 65 | fractions, 66 | direction, 67 | start, 68 | stop, 69 | id, 70 | className, 71 | style, 72 | tabIndex 73 | } = this.props; 74 | 75 | function calculateTotalSymbols(start, stop, step) { 76 | return Math.floor((stop - start) / step); 77 | } 78 | 79 | return ( 80 | 99 | ); 100 | } 101 | } 102 | 103 | RatingAPILayer.defaultProps = { 104 | start: 0, 105 | stop: 5, 106 | step: 1, 107 | readonly: false, 108 | quiet: false, 109 | fractions: 1, 110 | direction: 'ltr', 111 | onHover: noop, 112 | onClick: noop, 113 | onChange: noop, 114 | emptySymbol: Style.empty, 115 | fullSymbol: Style.full, 116 | placeholderSymbol: Style.placeholder 117 | }; 118 | 119 | // Define propTypes only in development. 120 | RatingAPILayer.propTypes = typeof __DEV__ !== 'undefined' && __DEV__ && { 121 | start: PropTypes.number, 122 | stop: PropTypes.number, 123 | step: PropTypes.number, 124 | initialRating: PropTypes.number, 125 | placeholderRating: PropTypes.number, 126 | readonly: PropTypes.bool, 127 | quiet: PropTypes.bool, 128 | fractions: PropTypes.number, 129 | direction: PropTypes.string, 130 | emptySymbol: PropTypes.oneOfType([ 131 | // Array of class names and/or style objects. 132 | PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.object, PropTypes.element])), 133 | // Class names. 134 | PropTypes.string, 135 | // Style objects. 136 | PropTypes.object, 137 | // React element 138 | PropTypes.element 139 | ]), 140 | fullSymbol: PropTypes.oneOfType([ 141 | // Array of class names and/or style objects. 142 | PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.object, PropTypes.element])), 143 | // Class names. 144 | PropTypes.string, 145 | // Style objects. 146 | PropTypes.object, 147 | // React element 148 | PropTypes.element 149 | ]), 150 | placeholderSymbol: PropTypes.oneOfType([ 151 | // Array of class names and/or style objects. 152 | PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.object, PropTypes.element])), 153 | // Class names. 154 | PropTypes.string, 155 | // Style objects. 156 | PropTypes.object, 157 | // React element 158 | PropTypes.element 159 | ]), 160 | onHover: PropTypes.func, 161 | onClick: PropTypes.func, 162 | onChange: PropTypes.func 163 | }; 164 | 165 | export default RatingAPILayer; 166 | -------------------------------------------------------------------------------- /src/RatingSymbol.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | // Return the corresponding React node for an icon. 5 | const _iconNode = (icon) => { 6 | // If it is already a React Element just return it. 7 | if (React.isValidElement(icon)) { 8 | return icon; 9 | } 10 | // If it is an object, try to use it as a CSS style object. 11 | if (typeof icon === 'object' && icon !== null) { 12 | return ; 13 | } 14 | // If it is a string, use it as class names. 15 | if (Object.prototype.toString.call(icon) === '[object String]') { 16 | return ; 17 | } 18 | }; 19 | 20 | class RatingSymbol extends React.PureComponent { 21 | render() { 22 | const { 23 | index, 24 | inactiveIcon, 25 | activeIcon, 26 | percent, 27 | direction, 28 | readonly, 29 | onClick, 30 | onMouseMove, 31 | onTouchEnd 32 | } = this.props; 33 | const backgroundNode = _iconNode(inactiveIcon); 34 | const showbgIcon = percent < 100; 35 | const bgIconContainerStyle = showbgIcon 36 | ? {} 37 | : { 38 | visibility: 'hidden' 39 | }; 40 | const iconNode = _iconNode(activeIcon); 41 | const iconContainerStyle = { 42 | display: 'inline-block', 43 | position: 'absolute', 44 | overflow: 'hidden', 45 | top: 0, 46 | [direction === 'rtl' ? 'right' : 'left']: 0, 47 | width: `${percent}%` 48 | }; 49 | const style = { 50 | cursor: !readonly ? 'pointer' : 'inherit', 51 | display: 'inline-block', 52 | position: 'relative' 53 | }; 54 | 55 | function handleMouseMove(e) { 56 | if (onMouseMove) { 57 | onMouseMove(index, e); 58 | } 59 | } 60 | 61 | function handleMouseClick(e) { 62 | if (onClick) { 63 | // [Supporting both TouchEvent and MouseEvent](https://developer.mozilla.org/en-US/docs/Web/API/Touch_events/Supporting_both_TouchEvent_and_MouseEvent) 64 | // We must prevent firing click event twice on touch devices. 65 | e.preventDefault(); 66 | onClick(index, e); 67 | } 68 | } 69 | 70 | function handleTouchEnd(e) { 71 | if (onTouchEnd) { 72 | onTouchEnd(index, e); 73 | } 74 | } 75 | 76 | return ( 77 | 84 | 85 | {backgroundNode} 86 | 87 | 88 | {iconNode} 89 | 90 | 91 | ); 92 | } 93 | } 94 | 95 | // Define propTypes only in development. They will be void in production. 96 | RatingSymbol.propTypes = typeof __DEV__ !== 'undefined' && __DEV__ && { 97 | index: PropTypes.number.isRequired, 98 | readonly: PropTypes.bool.isRequired, 99 | inactiveIcon: PropTypes.oneOfType([ 100 | PropTypes.string, 101 | PropTypes.object, 102 | PropTypes.element 103 | ]).isRequired, 104 | activeIcon: PropTypes.oneOfType([ 105 | PropTypes.string, 106 | PropTypes.object, 107 | PropTypes.element 108 | ]).isRequired, 109 | percent: PropTypes.number.isRequired, 110 | direction: PropTypes.string.isRequired, 111 | onClick: PropTypes.func, 112 | onMouseMove: PropTypes.func, 113 | onTouchMove: PropTypes.func, 114 | onTouchEnd: PropTypes.func 115 | }; 116 | 117 | export default RatingSymbol; 118 | -------------------------------------------------------------------------------- /src/react-rating.js: -------------------------------------------------------------------------------- 1 | import RatingAPILayer from './RatingAPILayer'; 2 | export default RatingAPILayer; 3 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dreyescat/react-rating/bd070757452e1aa75907f2a156fda2119fb7121a/src/utils.js -------------------------------------------------------------------------------- /src/utils/noop.js: -------------------------------------------------------------------------------- 1 | function noop() {} 2 | noop._name = 'react_rating_noop'; 3 | 4 | export default noop; 5 | -------------------------------------------------------------------------------- /src/utils/style.js: -------------------------------------------------------------------------------- 1 | var style = { 2 | display: 'inline-block', 3 | borderRadius: '50%', 4 | border: '5px double white', 5 | width: 30, 6 | height: 30 7 | }; 8 | 9 | export default { 10 | empty: { 11 | ...style, 12 | backgroundColor: '#ccc' 13 | }, 14 | full: { 15 | ...style, 16 | backgroundColor: 'black' 17 | }, 18 | placeholder: { 19 | ...style, 20 | backgroundColor: 'red' 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /test/Rating-test.js: -------------------------------------------------------------------------------- 1 | // Use expect BDD assertion style. 2 | var expect = require('chai').expect; 3 | var React = require('react'); 4 | var TestUtils = require('react-dom/test-utils'); 5 | var createRenderer = require('react-test-renderer/shallow').createRenderer; 6 | import Rating from '../src/Rating'; 7 | var Style = require('../src/utils/style.js'); 8 | 9 | var render = function (component) { 10 | var renderer = createRenderer(); 11 | renderer.render(component); 12 | return renderer.getRenderOutput() 13 | }; 14 | 15 | describe('Rating', function () { 16 | describe('with total symbols of 5', function () { 17 | var rating; 18 | 19 | beforeEach(function () { 20 | rating = render( 21 | 22 | ); 23 | }); 24 | 25 | it('should render a 5 symbol rating', function () { 26 | var Symbol = require('../src/RatingSymbol'); 27 | var children = rating.props.children; 28 | var symbols = children.filter(function (child) { 29 | return TestUtils.isElementOfType(child, Symbol); 30 | }); 31 | expect(children).to.have.same.length(symbols.length).which.is.length(5); 32 | }); 33 | 34 | it('should have all symbols empty', function () { 35 | rating.props.children.forEach(function (symbol) { 36 | expect(symbol.props.percent).to.be.equal(0); 37 | }); 38 | }); 39 | }); 40 | 41 | describe('with an initial rate of 3', function () { 42 | var rating; 43 | 44 | beforeEach(function () { 45 | rating = render(); 46 | }); 47 | 48 | it('should render a 5 symbol rating with the 3 first symbols full', function () { 49 | rating.props.children.forEach(function (symbol, i) { 50 | expect(symbol.props.percent).to.be.equal(i < 3 ? 100 : 0); 51 | }); 52 | }); 53 | }); 54 | 55 | describe('that is readonly', function () { 56 | var rating; 57 | 58 | beforeEach(function () { 59 | rating = render(); 60 | }); 61 | 62 | it('should have all symbols readonly', function () { 63 | rating.props.children.forEach(function (symbol, i) { 64 | expect(symbol.props.readonly).to.be.false; 65 | }); 66 | }); 67 | it('should not have mouse move handler', function () { 68 | var noop = require('../src/utils/noop'); 69 | rating.props.children.forEach(function (symbol, i) { 70 | expect(symbol.props.onMouseMove).to.equal(noop); 71 | }); 72 | }); 73 | 74 | it('should not have click handler', function () { 75 | var noop = require('../src/utils/noop'); 76 | rating.props.children.forEach(function (symbol, i) { 77 | expect(symbol.props.onClick).to.equal(noop); 78 | }); 79 | }); 80 | 81 | it('should not have mouse leave handler', function () { 82 | var noop = require('../src/utils/noop'); 83 | expect(rating.props.onMouseLeave).to.equal(noop); 84 | }); 85 | }); 86 | 87 | ///////////////////////////////////////////////////////////////////////////// 88 | // Custom symbol style 89 | ///////////////////////////////////////////////////////////////////////////// 90 | describe('with custom class name style', function () { 91 | var rating, 92 | empty = 'fa fa-star-o fa-2x', 93 | full = 'fa fa-star fa-2x'; 94 | 95 | beforeEach(function () { 96 | rating = render(); 97 | }); 98 | 99 | it('should render all symbols with custom style', function () { 100 | rating.props.children.forEach(function (symbol) { 101 | expect(symbol.props.icon).to.be.equal(full); 102 | expect(symbol.props.background).to.be.equal(empty); 103 | }); 104 | }); 105 | }); 106 | 107 | describe('with custom inline style', function () { 108 | var rating; 109 | 110 | beforeEach(function () { 111 | rating = render(); 112 | }); 113 | 114 | it('should render all symbols with custom style', function () { 115 | rating.props.children.forEach(function (symbol) { 116 | expect(symbol.props.icon).to.be.equal(Style.full); 117 | expect(symbol.props.background).to.be.equal(Style.empty); 118 | }); 119 | }); 120 | }); 121 | 122 | describe('with custom list of class name styles', function () { 123 | var rating, 124 | empty = ['fa fa-star-o fa-2x', 'fa fa-heart-o fa-2x'], 125 | full = ['fa fa-star fa-2x', 'fa fa-heart-o fa-2x']; 126 | 127 | beforeEach(function () { 128 | rating = render(); 129 | }); 130 | 131 | it('should render symbols with circular custom style', function () { 132 | rating.props.children.forEach(function (symbol, i) { 133 | expect(symbol.props.icon).to.be.equal(full[i % 2]); 134 | expect(symbol.props.background).to.be.equal(empty[i % 2]); 135 | }); 136 | }); 137 | }); 138 | }); 139 | -------------------------------------------------------------------------------- /test/RatingContainer-test.js: -------------------------------------------------------------------------------- 1 | // Use expect BDD assertion style. 2 | var expect = require('chai').expect; 3 | var React = require('react'); 4 | var TestUtils = require('react-dom/test-utils'); 5 | var createRenderer = require('react-test-renderer/shallow').createRenderer; 6 | import RatingContainer from '../src/RatingAPILayer'; 7 | 8 | var render = function (component) { 9 | var renderer = createRenderer(); 10 | renderer.render(component); 11 | return renderer.getRenderOutput() 12 | }; 13 | 14 | describe('RatingContainer', function () { 15 | describe('with a stop range of 10', function () { 16 | var rating; 17 | 18 | beforeEach(function () { 19 | rating = render(); 20 | }); 21 | 22 | it('should render a 10 symbol rating', function () { 23 | expect(rating.props.totalSymbols).to.be.equal(10); 24 | }); 25 | }); 26 | 27 | describe('with a range (5, 10]', function () { 28 | var rating; 29 | 30 | beforeEach(function () { 31 | rating = render(); 32 | }); 33 | 34 | it('should render a 5 symbol rating', function () { 35 | expect(rating.props.totalSymbols).to.be.equal(5); 36 | }); 37 | }); 38 | 39 | describe('with a range (0, 0]', function () { 40 | var rating; 41 | 42 | beforeEach(function () { 43 | rating = render(); 44 | }); 45 | 46 | it('should render a 0 symbol rating', function () { 47 | expect(rating.props.totalSymbols).to.be.equal(0); 48 | }); 49 | }); 50 | 51 | describe('with a range (0, 10] step 2', function () { 52 | var rating; 53 | 54 | beforeEach(function () { 55 | rating = render(); 56 | }); 57 | 58 | it('should render a 5 symbol rating', function () { 59 | expect(rating.props.totalSymbols).to.be.equal(5); 60 | }); 61 | }); 62 | 63 | describe('with a range (10, 0] step -2', function () { 64 | var rating; 65 | 66 | beforeEach(function () { 67 | rating = render(); 68 | }); 69 | 70 | it('should render a 5 symbol rating', function () { 71 | expect(rating.props.totalSymbols).to.be.equal(5); 72 | }); 73 | }); 74 | }); 75 | -------------------------------------------------------------------------------- /test/RatingSymbol-test.js: -------------------------------------------------------------------------------- 1 | // Use expect BDD assertion style. 2 | var expect = require('chai').expect; 3 | var React = require('react'); 4 | var TestUtils = require('react-dom/test-utils'); 5 | var createRenderer = require('react-test-renderer/shallow').createRenderer; 6 | import RatingSymbol from '../src/RatingSymbol'; 7 | 8 | var render = function (component) { 9 | var renderer = createRenderer(); 10 | renderer.render(component); 11 | return renderer.getRenderOutput() 12 | }; 13 | 14 | describe('RatingSymbol', function () { 15 | describe('with inline object icon and background', function () { 16 | var symbol, 17 | Style = require('../src/utils/style.js'), 18 | icon = Style.full, 19 | background= Style.empty; 20 | 21 | beforeEach(function () { 22 | symbol = render( 23 | 24 | ); 25 | }); 26 | 27 | it('should have inline styled background', function () { 28 | var backgroundNode = symbol.props.children[0]; 29 | expect(backgroundNode.props.style).to.be.equal(background); 30 | }); 31 | 32 | it('should have inline styled foreground', function () { 33 | var iconNode = symbol.props.children[1].props.children; 34 | expect(iconNode.props.style).to.be.equal(icon); 35 | }); 36 | 37 | it('should show pointer cursor', function () { 38 | expect(symbol.props.style.cursor).to.be.equal('pointer'); 39 | }); 40 | }); 41 | 42 | describe('with class name icon and background', function () { 43 | var symbol, 44 | icon = 'fa fa-star fa-2x', 45 | background = 'fa fa-star-o fa-2x'; 46 | 47 | beforeEach(function () { 48 | symbol = render(); 49 | }); 50 | 51 | it('should have class styled background', function () { 52 | var backgroundNode = symbol.props.children[0]; 53 | expect(backgroundNode.props.className).to.contain(background); 54 | }); 55 | 56 | it('should have class styled foreground', function () { 57 | var iconNode = symbol.props.children[1].props.children; 58 | expect(iconNode.props.className).to.contain(icon); 59 | }); 60 | }); 61 | 62 | describe('with React element icon and background', function () { 63 | var symbol; 64 | 65 | beforeEach(function () { 66 | symbol = render(+} background={-} />); 67 | }); 68 | 69 | it('should have a React element background', function () { 70 | var backgroundNode = symbol.props.children[0]; 71 | expect(TestUtils.isElement(backgroundNode)); 72 | }); 73 | 74 | it('should have a React element foreground', function () { 75 | var foregroundNode = symbol.props.children[0]; 76 | expect(TestUtils.isElement(foregroundNode)); 77 | }); 78 | }); 79 | 80 | describe('with 25 percent icon', function () { 81 | var symbol, 82 | Style = require('../src/utils/style.js'), 83 | icon = Style.full, 84 | background= Style.empty; 85 | 86 | beforeEach(function () { 87 | symbol = render(); 91 | }); 92 | 93 | it('should show 25% icon width', function () { 94 | var iconContainerNode = symbol.props.children[1]; 95 | expect(iconContainerNode.props.style.width).to.be.equal('25%'); 96 | }); 97 | }); 98 | 99 | describe('readonly', function () { 100 | var symbol, 101 | Style = require('../src/utils/style.js'), 102 | icon = Style.full, 103 | background= Style.empty; 104 | 105 | beforeEach(function () { 106 | symbol = render( 107 | 108 | ); 109 | }); 110 | 111 | it('should show auto cursor', function () { 112 | expect(symbol.props.style.cursor).to.be.equal('auto'); 113 | }); 114 | }); 115 | 116 | }); 117 | --------------------------------------------------------------------------------