1. Star Rating System

Index.js :

import React, { useState } from "react";
import ReactDOM from "react-dom/client";
// import './index.css';
// import App from './App';

import StarRating from "./StarRating";

/* ------------------------------------------------------ */

// If we want to use the "rating number" that was set using the star value
function Test() {
  const [movieRating, setMovieRating] = useState(0);

  return (
    <>
    <StarRating
      maxRating={5}
      color={"#900fff"}
      size={30}
      className={"test"}
      defaultRating={3}
      onSetRating={setMovieRating}
    />
    <p>This movie was rated {movieRating} stars </p>
    </>
  );
}

/* ------------------------------------------------------ */

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
  <React.StrictMode>
    {/* <App /> */}
    <StarRating
      maxRating={5}
      color={"#fcc419"}
      size={48}
      className={"test"}
      messages={["Very Bad", "Bad", "Okay", "Good", "Amazing"]}
    />

    <StarRating
      maxRating={5}
      color={"#fc2419"}
      size={30}
      className={"test"}
      defaultRating={3}
    />

    <Test />
  </React.StrictMode>
);

StarRating.js

/* TO USE THIS COMPONENT : → 

    <StarRating maxRating={5} color = {"#fcc419"} size = {48} className={"test"} messages={["Very Bad", "Bad", "Okay", "Good", "Amazing"]}/>
    
    <StarRating maxRating={5} color = {"#fc2419"} size = {30} className={"test"} defaultRating={3}/>
  
... */



import { useState } from "react";
import PropTypes from "prop-types" // used to specify the type of value (integer, string, ...) that we expect the consumer of the component to pass in for each of the props. For e.g. the maxRating should only be a number and nothing else.

const containerStyle = {
  display: "flex",
  alignItems: "center",
  gap: "16px",
};

const starContainerStyle = {
  display: "flex",
  gap: "4px",
};

StarRating.propTypes = {
  maxRating: PropTypes.number, // if we now enter a string instead of a number in maxRating then we'll get an error in the console of the web browser
  defaultRating: PropTypes.number,
  color: PropTypes.string,
  size: PropTypes.number,
  messages: PropTypes.array,
  className: PropTypes.string,
  onSetRating: PropTypes.func,

  // we also have .bool and .object 
}
// {maxRating = 5} : we are destructuring and setting the default value in case the object doesn't exist or is not specified by the user
export default function StarRating({
  maxRating = 1,
  color = "#fcc419",
  size = 48,
  className = "",
  messages = [],
  defaultRating=0,
  onSetRating,
}) {
  const [rating, setRating] = useState(defaultRating);
  const [tempRating, setTempRating] = useState(0);

  function handleRating(rating) {
    setRating(rating);
    if(onSetRating) onSetRating(rating);
  }

  const textStyle = {
    lineHeight: "1",
    margin: "0",
    color: color,
    fontSize: `${size / 1.5}px`,
  };

  return (
    <div className={className} style={containerStyle}>
      <div style={starContainerStyle}>
        {Array.from({ length: maxRating }, (_, i) => (
          <Star
            key={i}
            full={tempRating ? tempRating >= i + 1 : rating >= i + 1}
            onRate={() => handleRating(i + 1)}
            onHoverIn={() => setTempRating(i + 1)}
            onHoverOut={() => setTempRating(0)}
            color={color}
            size={size}
          />
        ))}
      </div>
      <p style={textStyle}>{messages.length===maxRating ? messages[tempRating ? tempRating-1 : rating-1] : (tempRating || rating || "")}</p>
    </div>
  );
}

function Star({ onRate, full, onHoverIn, onHoverOut, color, size }) {

  const starStyle = {
    width: `${size}px`,
    height: `${size}px`,
    display: "block",
    cursor: "pointer",
    color: color,
    fontSize: `${size / 1.5}px`,
  };

  return (
    // onMouseEnter and onMouseLeave below are used for Hover Recognition
    <span
      role="button"
      style={starStyle}
      onMouseEnter={onHoverIn}
      onMouseLeave={onHoverOut}
      onClick={onRate}
    >
      {full ? (
        <svg
          xmlns="http://www.w3.org/2000/svg"
          viewBox="0 0 20 20"
          fill={color}
          stroke={color}
        >
          <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />
        </svg>
      ) : (
        <svg
          xmlns="http://www.w3.org/2000/svg"
          fill="none"
          viewBox="0 0 24 24"
          stroke={color}
        >
          <path
            strokeLinecap="round"
            strokeLinejoin="round"
            strokeWidth="{2}"
            d="M11.049 2.927c.3-.921 1.603-.921 1.902 0l1.519 4.674a1 1 0 00.95.69h4.915c.969 0 1.371 1.24.588 1.81l-3.976 2.888a1 1 0 00-.363 1.118l1.518 4.674c.3.922-.755 1.688-1.538 1.118l-3.976-2.888a1 1 0 00-1.176 0l-3.976 2.888c-.783.57-1.838-.197-1.538-1.118l1.518-4.674a1 1 0 00-.363-1.118l-3.976-2.888c-.784-.57-.38-1.81.588-1.81h4.914a1 1 0 00.951-.69l1.519-4.674z"
          />
        </svg>
      )}
    </span>
  );
}