UNPKG

star-meter

Version:

A highly customizable React star rating component with support for half-stars, animations, and custom styling

156 lines (148 loc) 4.64 kB
import React, { useState } from "react"; import "./StarRating.css"; // For animations export default function StarRating({ maxRating = 5, color = "#fcc419", textcolor = "#0d0d0d", size = 48, messages = [], className = "", defaultRating = 0, onSetRating = () => {}, allowHalfStars = true, animation = "none", // "scale" | "rotate" | "bounce" | "none" allowReset = true, }) { const [rating, setRating] = useState(defaultRating); const [hoverValue, setHoverValue] = useState(null); const handleMouseMove = (e, i) => { const { left, width } = e.currentTarget.getBoundingClientRect(); const x = e.clientX - left; const isHalf = x < width / 2; setHoverValue(i + (isHalf ? 0.5 : 1)); }; const handleClick = (value) => { if (allowReset && value === rating) { setRating(0); onSetRating(0); } else { setRating(value); onSetRating(value); } }; const displayRating = hoverValue ?? rating; return ( <div style={{ display: "flex", alignItems: "center", gap: "16px" }} className={className} > <div style={{ display: "flex", alignItems: "center", gap: "4px" }}> {Array.from({ length: maxRating }, (_, i) => { const full = displayRating >= i + 1; const half = displayRating === i + 0.5; const value = full ? i + 1 : half ? i + 0.5 : i + 1; return ( <Star key={i} full={full} half={half} size={size} color={color} onRate={() => handleClick(value)} onHoverIn={() => {}} onHoverOut={() => setHoverValue(null)} onMouseMove={ allowHalfStars ? (e) => handleMouseMove(e, i) : undefined } animation={animation} /> ); })} <p style={{ margin: "0 0 0 8px", lineHeight: "1", color: textcolor, fontSize: `${size / 1.5}px`, fontWeight: 500, }} > {messages.length === maxRating ? messages[Math.floor(displayRating) - 1] : displayRating || ""} </p> </div> </div> ); } function Star({ full, half, onRate, onHoverIn, onHoverOut, onMouseMove, color, size, animation, }) { return ( <span role="button" tabIndex="0" className={`star ${animation}`} style={{ cursor: "pointer", display: "inline-block" }} onClick={onRate} onMouseEnter={onHoverIn} onMouseLeave={onHoverOut} onMouseMove={onMouseMove} onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") onRate(); }} > {full ? ( <svg xmlns="http://www.w3.org/2000/svg" width={size} height={size} fill={color} viewBox="0 0 24 24" > <path d="M12 17.27l-5.18 3.73c-.68.49-1.6-.17-1.39-.95l1.64-6.36-4.9-4.24c-.61-.53-.28-1.54.52-1.63l6.56-.57 2.52-6.1c.3-.72 1.32-.72 1.62 0l2.52 6.1 6.56.57c.8.07 1.13 1.1.52 1.63l-4.9 4.24 1.64 6.36c.21.78-.71 1.44-1.39.95L12 17.27z" /> </svg> ) : half ? ( <svg xmlns="http://www.w3.org/2000/svg" width={size} height={size} viewBox="0 0 24 24" > <defs> <linearGradient id="half"> <stop offset="50%" stopColor={color} /> <stop offset="50%" stopColor="transparent" /> </linearGradient> </defs> <path fill="url(#half)" stroke={color} strokeWidth="2" d="M12 17.27l-5.18 3.73c-.68.49-1.6-.17-1.39-.95l1.64-6.36-4.9-4.24c-.61-.53-.28-1.54.52-1.63l6.56-.57 2.52-6.1c.3-.72 1.32-.72 1.62 0l2.52 6.1 6.56.57c.8.07 1.13 1.1.52 1.63l-4.9 4.24 1.64 6.36c.21.78-.71 1.44-1.39.95L12 17.27z" /> </svg> ) : ( <svg xmlns="http://www.w3.org/2000/svg" width={size} height={size} fill="none" stroke={color} strokeWidth="2" viewBox="0 0 24 24" > <path d="M12 17.27l-5.18 3.73c-.68.49-1.6-.17-1.39-.95l1.64-6.36-4.9-4.24c-.61-.53-.28-1.54.52-1.63l6.56-.57 2.52-6.1c.3-.72 1.32-.72 1.62 0l2.52 6.1 6.56.57c.8.07 1.13 1.1.52 1.63l-4.9 4.24 1.64 6.36c.21.78-.71 1.44-1.39.95L12 17.27z" /> </svg> )} </span> ); }