Star Rating

PreviousNext

An interactive star rating component.

Docs
opticscomponent

Preview

Loading preview…
registry/optics/star-rating.jsx
"use client";

import { useState } from "react";
import { Star } from "lucide-react";
import { motion } from "motion/react";
import { cn } from "@/registry/optics/lib/utils";

export function StarRating({
	totalStars = 5,
	defaultValue = 0,
	onRate = undefined,
	size = "md",
	className = "",
	disabled = false,
}) {
	const [rating, setRating] = useState(defaultValue);
	const [hover, setHover] = useState(0);

	const handleRating = (star) => {
		if (disabled) return;
		setRating(star);
		onRate?.(star);
	};

	const starSizes = {
		sm: "h-4 w-4",
		md: "h-6 w-6",
		lg: "h-8 w-8",
	};

	return (
		<div
			className={cn(
				"flex items-center gap-2",
				disabled && "opacity-50",
				className,
			)}
		>
			{Array.from({ length: totalStars }, (_, index) => index + 1).map(
				(star) => (
					<motion.button
						key={star}
						type="button"
						className={cn(
							"relative focus-visible:outline-none focus-visible:ring-2",
							"focus-visible:ring-ring focus-visible:ring-offset-2",
							disabled && "cursor-not-allowed",
						)}
						onClick={() => handleRating(star)}
						onMouseEnter={() => !disabled && setHover(star)}
						onMouseLeave={() => !disabled && setHover(0)}
						whileHover={!disabled ? { scale: 1.3, rotate: -10 } : undefined}
						whileTap={!disabled ? { scale: 0.9, rotate: 15 } : undefined}
						disabled={disabled}
					>
						<motion.div
							className={cn(
								"transition-colors duration-300",
								(hover || rating) >= star
									? "text-yellow-400 dark:text-yellow-300"
									: "text-muted",
							)}
							initial={{ scale: 1 }}
							animate={{
								scale: (hover || rating) >= star ? 1.2 : 1,
							}}
							transition={{
								duration: 0.3,
								ease: "easeOut",
							}}
						>
							<Star
								className={cn(starSizes[size], "fill-current stroke-[1.5px]")}
							/>
						</motion.div>
					</motion.button>
				),
			)}
		</div>
	);
}

Installation

npx shadcn@latest add @optics/star-rating

Usage

import { StarRating } from "@/components/star-rating"
<StarRating />