Skip to main content

Rating

Rating.tsx
import React, { useState, useEffect, useCallback } from 'react';
import './Rating.css';

type RatingProps = {
value: number;
onChange: (newValue: number) => void;
}

const Rating = (props: RatingProps) => {
const { value, onChange } = props;
const [hoverValue, setHoverValue] = useState<number>(0);

const handleMouseEnter = useCallback((value: number) => {
setHoverValue(value);
}, []);

const handleMouseLeave = useCallback(() => {
setHoverValue(0);
}, []);

const handleClick = useCallback((value: number) => {
onChange(value);
}, [onChange]);

const renderStar = useCallback((value: number) => {
const halfStar = value % 1 !== 0;
const starValue = Math.floor(value);

const fullStar = (
<span
className={`star ${starValue <= hoverValue ? 'filled' : ''} ${starValue <= value ? 'active' : ''}`}
onMouseEnter={() => handleMouseEnter(starValue)}
onMouseLeave={handleMouseLeave}
onClick={() => handleClick(starValue)}
>
<i className="fa fa-star"></i>
</span>
);

const halfStarElement = (
<span
className={`star half ${starValue < hoverValue ? 'filled' : ''} ${starValue < value ? 'active' : ''}`}
onMouseEnter={() => handleMouseEnter(starValue + 0.5)}
onMouseLeave={handleMouseLeave}
onClick={() => handleClick(starValue + 0.5)}
>
<i className="fa fa-star-half-o"></i>
</span>
);

return (
<React.Fragment key={value}>
{fullStar}
{halfStar && halfStarElement}
</React.Fragment>
);
}, [hoverValue, handleMouseEnter, handleMouseLeave, handleClick]);

const stars = [1, 2, 3, 4, 5];

return (
<div className="rating">
{stars.map((starValue) => renderStar(starValue))}
</div>
);
};

export default Rating;


Rating.module.css
.rating-container {
display: flex;
align-items: center;
gap: 4px;
}

.star-icon {
font-size: 24px;
color: #d3d3d3;
cursor: pointer;
}

.filled {
color: #ffc107;
}

.half-filled:before {
content: '\2605';
position: absolute;
color: #ffc107;
overflow: hidden;
width: 50%;
}

.hidden {
display: none;
}