import * as React from 'react' import { cva, type VariantProps } from 'class-variance-authority' import { cn } from '@/lib/utils' const ratingVariants = cva('inline-flex items-center gap-1', { variants: { size: { sm: '[--rating-size:1rem]', md: '[--rating-size:1.25rem]', lg: '[--rating-size:1.5rem]', }, }, defaultVariants: { size: 'md', }, }) type RatingPrecision = 1 | 0.5 export interface RatingProps extends Omit, 'onChange'>, VariantProps { value?: number defaultValue?: number onValueChange?: (value: number) => void max?: number precision?: RatingPrecision readOnly?: boolean disabled?: boolean showValue?: boolean label?: string } function clampRating(value: number, max: number, precision: RatingPrecision) { const normalized = Math.max(0, Math.min(max, value)) return Math.round(normalized / precision) * precision } function RatingStar({ className }: { className?: string }) { return ( ) } const Rating = React.forwardRef( ( { className, size, value, defaultValue = 0, onValueChange, max = 5, precision = 1, readOnly = false, disabled = false, showValue = false, label = 'Rating', ...props }, ref ) => { const [internalValue, setInternalValue] = React.useState(defaultValue) const currentValue = clampRating(value ?? internalValue, max, precision) const interactive = !readOnly && !disabled const commitValue = (nextValue: number) => { const next = clampRating(nextValue, max, precision) if (value == null) setInternalValue(next) onValueChange?.(next) } return (
{Array.from({ length: max }, (_, index) => { const starValue = index + 1 const fillRatio = Math.max(0, Math.min(1, currentValue - index)) const halfValue = starValue - 0.5 return ( {interactive ? ( precision === 0.5 ? (
) } ) Rating.displayName = 'Rating' export { Rating, ratingVariants } export type { RatingPrecision }