Socket
Socket
Sign inDemoInstall

react-advanced-rating

Package Overview
Dependencies
5
Maintainers
1
Versions
1
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

    react-advanced-rating

:star: Zero-dependency, highly customizable rating component for React.


Version published
Maintainers
1
Created

Readme

Source

React Advanced Rating

:star: Zero-dependency, highly customizable rating component for React.

Live demo and examples


Features

  • Use any SVG: No headaches, icon fonts or packages to install in order to use your favorite vectors.
  • Highly customizable: behavior, colors, transitions and much more.
  • Fully responsive and mobile-first
  • Fully accessible with keyboard navigation and custom labels
  • Works both on the server and the client
  • Fully typed with IntelliSense infos and autocomplete
  • Dependency-free, ~3.5Kb gzipped.

Table of contents


Installation

yarn add react-advanced-rating

Or with NPM:

npm i -S react-advanced-rating

Basic usage

As an accessible radio-group input:

import React, { useState } from "react";
import { Rating } from 'react-advanced-rating';

import 'react-advanced-rating/dist/index.css'; // <-- Import CSS

const App = () => {
  const [ratingValue, setRatingValue] = useState(3); // <-- Initial value, init with 0 for no value

  return (
      <div style={{ maxWidth: 600, width: "100%" }}> {/* <-- Wrap it in a container */}
        <Rating
            value={ratingValue}
            onChange={(selectedValue) => setRatingValue(selectedValue)}
        >
      </div>
  )
};

or as an accessible, non-interactive image element:

import React from 'react';
import { Rating } from 'react-advanced-rating';

import 'react-advanced-rating/dist/index.css';

const App = () => (
  <div style={{ maxWidth: 600, width: '100%' }}>
    <Rating readOnly value={3.78} />
  </div>
);

Usage with frameworks

  • NextJS - Import the CSS once in _app.js
  • Gatsby - Import the CSS once in gatsby-browser.js

API

Legend

ColorDescription
:green_circle:Has always effect
:large_blue_circle:Has effect only if readOnly is false
:purple_circle:Has effect only if readOnly is true

:cyclone: Core

PropTypeDescriptionDefaultRequired:thinking:
valuenumberAn integer from 0 to items. It can be a float if readOnly is true.undefinedYes:green_circle:
onChangefunctionCallback to set the rating valueundefinedOnly if readOnly is false:large_blue_circle:
onHoverChangefunctionCallback to set the hovered valueundefinedNo:large_blue_circle:
items1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10Number of rating items to display5No:green_circle:
readOnlybooleanWhether or not to render an accessible image elementfalseNo:green_circle:
resetOnSecondClickbooleanWhether or not to reset the rating value when clicking again on the current ratingfalseNo:large_blue_circle:

ref, id, className and style are also available.


:nail_care: Appearance

PropTypeDescriptionDefaultRequired:thinking:
highlightOnlySelectedbooleanWhether or not to highlight only the selected rating itemfalseNo:green_circle:
halfFillModesvg | boxWhether to half-fill the SVG or the boxsvgNo:purple_circle:
orientationhorizontal | verticalOrientation of the rating itemshorizontalNo:green_circle:
spaceInsidenone | small | medium | largeResponsive padding of each rating itemregularNo:green_circle:
spaceBetweennone | small | medium | largeResponsive gap between the rating itemssmallNo:green_circle:
radiusnone | small | medium | large | fullRadius of each rating itemnoneNo:green_circle:
transitionnone | zoom | colors | opacity | positionTransition to apply when hovering/selectingcolorsNo:large_blue_circle:
itemStylesItemStyleCustom SVGs and colorsdefaultStylesNo:green_circle:

Would you like to style it via CSS? Take a look here.


:open_umbrella: Accessibility

PropTypeDescriptionDefaultRequired:thinking:
enableKeyboardbooleanWhether or not to enable keyboard navigationtrueNo:large_blue_circle:
isRequiredbooleanWhether or not to tell assistive technologies that rating is requiredtrueNo:large_blue_circle:
invisibleLabelstringAccessible label of the rating group / imageRating or Rated <value> on <items> if readOnly is trueNo:green_circle:
invisibleItemLabelsstring[]Accessible labels of each each rating itemRate 1, Rate 2...No:large_blue_circle:
visibleLabelIdstringId of the element used as rating group label. Takes precedence over invisibleLabel.undefinedNo:large_blue_circle:
visibleItemLabelIdsstring[]Ids of the elements used as labels for each rating item. Takes precedence over invisibleItemLabels.undefinedNo:large_blue_circle:

Styling

Rating items

Pass an ItemStyle object to itemStyles prop:

type ItemStyle = {
  itemShapes: JSX.Element | JSX.Element[];

  itemStrokeWidth?: number;
  boxBorderWidth?: number;

  activeFillColor?: string | string[];
  activeStrokeColor?: string | string[];
  activeBoxColor?: string | string[];
  activeBoxBorderColor?: string | string[];

  inactiveFillColor?: string;
  inactiveStrokeColor?: string;
  inactiveBoxColor?: string;
  inactiveBoxBorderColor?: string;
};

All the properties are optional (except for itemShapes). If a property isn't defined, no classes nor CSS variables will be added to the rendered HTML.

Just set the ones you need and that's it:

const CustomStar = (
  <polygon points="478.53 189 318.53 152.69 239.26 0 160 152.69 0 189 111.02 303.45 84 478.53 239.26 396.63 394.53 478.53 367.51 303.45 478.53 189" />
);

const customStyles = {
  itemShapes: CustomStar,
  activeFillColor: '#22C55E',
  inactiveFillColor: '#BBF7D0',
};

const App = () => {
  const [ratingValue, setRatingValue] = useState(4);

  return (
    <div
      style={{
        maxWidth: 300,
        width: '100%',
      }}
    >
      <Rating
        value={ratingValue}
        onChange={(selectedValue) => setRatingValue(selectedValue)}
        itemStyles={customStyles}
      />
    </div>
  );
};

If you want to use the default rating star coming with this package, just import it:

import { Star } from 'react-advanced-rating';

const customStyles = {
  itemShapes: Star,
  activeFillColor: '#22C55E',
  inactiveFillColor: '#BBF7D0',
};
Default styles
const Star = (
  <polygon points="478.53 189 318.53 152.69 239.26 0 160 152.69 0 189 111.02 303.45 84 478.53 239.26 396.63 394.53 478.53 367.51 303.45 478.53 189" />
);

const defaultItemStyles = {
  itemShapes: Star,
  itemStrokeWidth: 40,

  activeFillColor: '#ffb23f',
  activeStrokeColor: '#e17b21',

  inactiveFillColor: '#fff7ed',
  inactiveStrokeColor: '#e17b21',
};
How itemStrokeWidth works

The stroke width is expressed in viewBox user coordinate's unit size and not in pixels.

Depending on the vector nodes provided you may have to input and try different values in order to reach the desired stroke width.

It is responsive by nature, so expect it to increase/decrease when resizing the container.

Color values

You can pass any valid CSS color string such as aliceblue, #FFF332, rgba(0, 0, 0, 0) or hsl(69, 22, 200).


How to create itemShapes elements

All you have to do is to open the SVG with a text editor, grab the inner shapes and delete any attribute from them (except for geometric and transform ones). Then create a new JSX Element that renders the cleaned shapes.

The component will take care of rendering a brand-new, responsive SVG for you.

If the SVG comes from quality sources (or you made it) such as Feather, SVG Repo, Bootstrap Icons or css.gg all you have to do is to delete a couple of fill and stroke attributes (if any):

<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
  <path
    fill="currentColor"
    stroke="2"
    stroke-linecap="round"
    stroke-linejoin="round"
    d="M3.612 15.443c-.386.198-.824-.149-.746-.592l.83-4.73L.173 6.765c-.329-.314-.158-.888.283-.95l4.898-.696L7.538.792c.197-.39.73-.39.927 0l2.184 4.327 4.898.696c.441.062.612.636.282.95l-3.522 3.356.83 4.73c.078.443-.36.79-.746.592L8 13.187l-4.389 2.256z"
  />
</svg>
const CustomStar = (
  <path d="M3.612 15.443c-.386.198-.824-.149-.746-.592l.83-4.73L.173 6.765c-.329-.314-.158-.888.283-.95l4.898-.696L7.538.792c.197-.39.73-.39.927 0l2.184 4.327 4.898.696c.441.062.612.636.282.95l-3.522 3.356.83 4.73c.078.443-.36.79-.746.592L8 13.187l-4.389 2.256z" />
);

const customStyles = {
  itemShapes: CustomStar,
  itemStrokeWidth: 2,
  activeFillColor: 'LightSeaGreen',
  activeStrokeColor: '#99F6E4',
  inactiveFillColor: '#99F6E4',
  inactiveStrokeColor: 'LightSeaGreen',
};

<Rating
  value={ratingValue}
  onChange={(selectedValue) => setRatingValue(selectedValue)}
  itemStyles={customStyles}
/>;

Quick guide for complex or messy SVGs

  1. Keep only the groups and the inner shapes of the svg: g, path, circle, rect, polygon, ellipse, polyline or line and delete any other node (e.g. <defs>).

  2. If a group is present, check if it has the transform attribute set. If the attribute is not set, keep the inner shapes and delete the g node.

  3. Delete any attribute except geometric, draw and transform ones from any group and shape.

  4. If present, delete any empty node like <circle></circle> or <g></g>.


Advanced styling

If you wish to style each rating item, you can optionally pass an array of JSX elements to itemShapes and an array of valid CSS colors to any active color property:

react-advanced-rating

const SadFace = (
  <path d="M12.0000002,1.99896738 C17.523704,1.99896738 22.0015507,6.47681407 22.0015507,12.0005179 C22.0015507,17.5242217 17.523704,22.0020684 12.0000002,22.0020684 C6.47629639,22.0020684 1.99844971,17.5242217 1.99844971,12.0005179 C1.99844971,6.47681407 6.47629639,1.99896738 12.0000002,1.99896738 Z M12.0000002,3.49896738 C7.30472352,3.49896738 3.49844971,7.30524119 3.49844971,12.0005179 C3.49844971,16.6957946 7.30472352,20.5020684 12.0000002,20.5020684 C16.6952769,20.5020684 20.5015507,16.6957946 20.5015507,12.0005179 C20.5015507,7.30524119 16.6952769,3.49896738 12.0000002,3.49896738 Z M12.0000001,13.4979816 C13.6312483,13.4979816 15.1603686,14.1528953 16.2810488,15.2934358 C16.5713583,15.5888901 16.5671876,16.0637455 16.2717333,16.354055 C15.976279,16.6443646 15.5014236,16.6401939 15.211114,16.3447396 C14.3696444,15.4883577 13.2246935,14.9979816 12.0000001,14.9979816 C10.7726114,14.9979816 9.62535029,15.4905359 8.78347552,16.3502555 C8.49366985,16.6462041 8.01882223,16.6511839 7.72287367,16.3613782 C7.4269251,16.0715726 7.4219453,15.5967249 7.71175097,15.3007764 C8.83296242,14.155799 10.3651558,13.4979816 12.0000001,13.4979816 Z M9.00044779,8.75115873 C9.69041108,8.75115873 10.2497368,9.3104845 10.2497368,10.0004478 C10.2497368,10.6904111 9.69041108,11.2497368 9.00044779,11.2497368 C8.3104845,11.2497368 7.75115873,10.6904111 7.75115873,10.0004478 C7.75115873,9.3104845 8.3104845,8.75115873 9.00044779,8.75115873 Z M15.0004478,8.75115873 C15.6904111,8.75115873 16.2497368,9.3104845 16.2497368,10.0004478 C16.2497368,10.6904111 15.6904111,11.2497368 15.0004478,11.2497368 C14.3104845,11.2497368 13.7511587,10.6904111 13.7511587,10.0004478 C13.7511587,9.3104845 14.3104845,8.75115873 15.0004478,8.75115873 Z" />
);

const SmilingFace = (
  <path d="M12.0000002,1.99896738 C17.523704,1.99896738 22.0015507,6.47681407 22.0015507,12.0005179 C22.0015507,17.5242217 17.523704,22.0020684 12.0000002,22.0020684 C6.47629639,22.0020684 1.99844971,17.5242217 1.99844971,12.0005179 C1.99844971,6.47681407 6.47629639,1.99896738 12.0000002,1.99896738 Z M12.0000002,3.49896738 C7.30472352,3.49896738 3.49844971,7.30524119 3.49844971,12.0005179 C3.49844971,16.6957946 7.30472352,20.5020684 12.0000002,20.5020684 C16.6952769,20.5020684 20.5015507,16.6957946 20.5015507,12.0005179 C20.5015507,7.30524119 16.6952769,3.49896738 12.0000002,3.49896738 Z M8.46174078,14.7838355 C9.31087697,15.8615555 10.6018926,16.5020843 11.9999849,16.5020843 C13.396209,16.5020843 14.6856803,15.8632816 15.5349376,14.7880078 C15.7916692,14.4629512 16.2633016,14.4075628 16.5883582,14.6642944 C16.9134148,14.9210259 16.9688032,15.3926584 16.7120717,15.717715 C15.5813083,17.1494133 13.8601276,18.0020843 11.9999849,18.0020843 C10.1373487,18.0020843 8.41411759,17.1471146 7.28351576,15.7121597 C7.02716611,15.3868018 7.08310832,14.9152347 7.40846617,14.6588851 C7.73382403,14.4025354 8.20539113,14.4584777 8.46174078,14.7838355 Z M9.00044779,8.75115873 C9.69041108,8.75115873 10.2497368,9.3104845 10.2497368,10.0004478 C10.2497368,10.6904111 9.69041108,11.2497368 9.00044779,11.2497368 C8.3104845,11.2497368 7.75115873,10.6904111 7.75115873,10.0004478 C7.75115873,9.3104845 8.3104845,8.75115873 9.00044779,8.75115873 Z M15.0004478,8.75115873 C15.6904111,8.75115873 16.2497368,9.3104845 16.2497368,10.0004478 C16.2497368,10.6904111 15.6904111,11.2497368 15.0004478,11.2497368 C14.3104845,11.2497368 13.7511587,10.6904111 13.7511587,10.0004478 C13.7511587,9.3104845 14.3104845,8.75115873 15.0004478,8.75115873 Z" />
);

const customStyles = {
  itemShapes: [SadFace, SmilingFace],
  activeFillColor: ['#da1600', '#61bb00'],
  inactiveFillColor: '#a8a8a8',
};

export const FacesRating = () => {
  const [ratingValue, setRatingValue] = useState(0);

  return (
    <div
      style={{
        maxWidth: 200,
        width: '100%',
      }}
    >
      <Rating
        value={ratingValue}
        onChange={(selectedValue) => setRatingValue(selectedValue)}
        items={2}
        itemStyles={customStyles}
        highlightOnlySelected
      />
    </div>
  );
};

react-advanced-rating

const Star = (
  <path d="M62 25.154H39.082L32 3l-7.082 22.154H2l18.541 13.693L13.459 61L32 47.309L50.541 61l-7.082-22.152L62 25.154z" />
);

const customStyles = {
  itemShapes: Star,
  boxBorderWidth: 3,

  activeFillColor: ['#FEE2E2', '#FFEDD5', '#FEF9C3', '#ECFCCB', '#D1FAE5'],
  activeBoxColor: ['#da1600', '#db711a', '#dcb000', '#61bb00', '#009664'],
  activeBoxBorderColor: ['#c41400', '#d05e00', '#cca300', '#498d00', '#00724c'],

  inactiveFillColor: 'white',
  inactiveBoxColor: '#dddddd',
  inactiveBoxBorderColor: '#a8a8a8',
};

export const App = () => {
  const [ratingValue, setRatingValue] = useState(4);

  return (
    <div
      style={{
        maxWidth: 500,
        width: '100%',
      }}
    >
      <Rating
        value={ratingValue}
        onChange={(selectedValue) => setRatingValue(selectedValue)}
        itemStyles={customStyles}
        radius="large"
        spaceInside="large"
      />
    </div>
  );
};

Half-fill and float values

If readOnly is set to true, value prop accepts a float:

<Rating readOnly value={1.38} />

The component will try to round it to the nearest half integer:

3.2 = 3
3.26 = 3.5
3.62 = 3.5
3.75 = 4

:warning: The value will only be rounded "internally" for graphical purposes. The accessible label will always display the value you provided.

If necessary, the SVG will be half-filled by default (halfFillMode = 'svg'):

react-advanced-rating

All the boxes will have the same background color (inactiveBoxColor) and activeBoxColor will have no effect.

You can switch between svg and box:

<Rating readOnly value={2.38} halfFillMode="box" />

react-advanced-rating

In this case instead, all the SVGs will have the same fill color (inactiveFillColor) and activeFillColor will have no effect.

If you don't want the half-fill feature, simply pass an integer to value.

:warning: If highlightOnlySelected is set to true, no half-fill will take place.


Styling via CSS

It is not necessary, however if you want to, you can do it as shown below:

  1. Assign a custom class to <Rating />:
<Rating
  value={ratingValue}
  onChange={(selectedValue) => setRatingValue(selectedValue)}
  className="my-own-class"
/>
  1. Disable any style you want to replace via props, so that no variables nor classes for that style will be generated/injected:
<Rating
  value={ratingValue}
  onChange={(selectedValue) => setRatingValue(selectedValue)}
  className="my-own-class"
  spaceBetween="none"
  spaceInside="none"
  radius="none"
  transition="none"
>
  1. Target the child elements and style them:
.my-own-class {
  gap: 20px;
}

.my-own-class .rar--svg {
  border-radius: 10px;
  padding: 5px;
}

.my-own-class .rar--svg {
  transform: scale(1);
  transition: all 300ms cubic-bezier(0.87, 0, 0.13, 1);
  opacity: 0.5;
}

.my-own-class .rar--on:hover .rar--svg {
  transform: scale(1.5);
  opacity: 1;
}

Accessibility

Keyboard navigation

  • Tab - Default behavior
  • Shift + Tab - Default behavior
  • Left Arrow | Down Arrow - Select the next rating item
  • Right Arrow | Up Arrow - Select the previous rating item
  • Spacebar | Enter - Set/unset the current selection

Labels

Check the examples on the demo website.


Troubleshooting

I can see the nodes returned from rendering, but no styles have been applied.

Check that you have imported the CSS as displayed in the Basic usage section.

I passed an array of SVGs but the stroke width looks different for each item.

When passing different shapes for each rating item, this package forces you to use icons from the same collection to keep design consistency. Be sure you are doing that.

You can find clean, attribution-free SVG collections at SVG Repo.

I keep getting the error "itemShapes" is not a valid JSX element".

Check that you are passing a JSX element and not a functional component:

:white_check_mark: Correct

const Star = <path d="M100,10L40 198 190 78 10 78 160 198z" />;

:x: Incorrect

const Star = () => <path d="M100,10L40 198 190 78 10 78 160 198z" />;

Local development

The main branch contains the latest version of this package. Rating component is imported in a simple test React App which runs on a Vite dev server.

In dev/ you can find the test app files. It is just a blank React app container with some CSS resets applied.

In src/ you can find the package core files, the build entry point is src/index.ts.

Once cloned, just run:

yarn
yarn dev

Vite's Library Mode is used to bundle the package. To build the package just run:

yarn build

License

MIT Licensed. Copyright (c) Simone Mastromattei 2022.

Keywords

FAQs

Last updated on 01 Jul 2022

Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc