Files
thrilltrack-explorer/src-old/components/reviews/StarRating.tsx

110 lines
3.0 KiB
TypeScript

import { useState } from 'react';
import { Star } from 'lucide-react';
import { cn } from '@/lib/utils';
interface StarRatingProps {
rating: number;
onChange?: (rating: number) => void;
size?: 'sm' | 'md' | 'lg';
interactive?: boolean;
showLabel?: boolean;
className?: string;
}
export function StarRating({
rating,
onChange,
size = 'md',
interactive = false,
showLabel = false,
className
}: StarRatingProps) {
const [hoverRating, setHoverRating] = useState(0);
const sizes = {
sm: 'w-4 h-4',
md: 'w-6 h-6',
lg: 'w-8 h-8'
};
const starSize = sizes[size];
const displayRating = hoverRating || rating;
const handleStarClick = (starIndex: number, isHalf: boolean) => {
if (!interactive || !onChange) return;
const newRating = starIndex + (isHalf ? 0.5 : 1);
onChange(newRating);
};
const handleStarHover = (starIndex: number, isHalf: boolean) => {
if (!interactive) return;
const newHoverRating = starIndex + (isHalf ? 0.5 : 1);
setHoverRating(newHoverRating);
};
const handleMouseLeave = () => {
if (!interactive) return;
setHoverRating(0);
};
const renderStar = (index: number) => {
const starValue = index + 1;
const isFullStar = displayRating >= starValue;
const isHalfStar = displayRating >= starValue - 0.5 && displayRating < starValue;
const getStarFill = () => {
if (isFullStar) return '100%';
if (isHalfStar) return '50%';
return '0%';
};
return (
<div key={index} className="relative inline-block">
{/* Background unfilled star */}
<Star className={cn(starSize, 'text-muted-foreground')} />
{/* Filled star overlay */}
<div
className="absolute inset-0 overflow-hidden"
style={{ width: getStarFill() }}
>
<Star className={cn(starSize, 'fill-yellow-400 text-yellow-400')} />
</div>
{/* Interactive click areas */}
{interactive && (
<>
{/* Left half click area */}
<div
className="absolute top-0 left-0 w-1/2 h-full cursor-pointer z-10"
onClick={() => handleStarClick(index, true)}
onMouseEnter={() => handleStarHover(index, true)}
onMouseLeave={handleMouseLeave}
/>
{/* Right half click area */}
<div
className="absolute top-0 right-0 w-1/2 h-full cursor-pointer z-10"
onClick={() => handleStarClick(index, false)}
onMouseEnter={() => handleStarHover(index, false)}
onMouseLeave={handleMouseLeave}
/>
</>
)}
</div>
);
};
return (
<div className={cn('flex items-center gap-1', className)}>
<div className="flex items-center">
{Array.from({ length: 5 }, (_, i) => renderStar(i))}
</div>
{showLabel && (
<span className="ml-2 text-sm text-muted-foreground">
{displayRating} star{displayRating !== 1 ? 's' : ''}
</span>
)}
</div>
);
}