{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "rating",
  "title": "Rating",
  "description": "A controlled or uncontrolled star rating with hover preview, keyboard navigation, sizes, and native form values.",
  "dependencies": [
    "@base-ui/react",
    "lucide-react"
  ],
  "files": [
    {
      "path": "registry/rivetui/components/rating/rating.tsx",
      "content": "\"use client\";\n\nimport { Radio as RadioPrimitive } from \"@base-ui/react/radio\";\nimport { RadioGroup as RadioGroupPrimitive } from \"@base-ui/react/radio-group\";\nimport { Star } from \"lucide-react\";\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\ntype RatingSize = \"sm\" | \"md\" | \"lg\";\n\ntype RatingProps = Omit<\n  RadioGroupPrimitive.Props<number>,\n  \"children\" | \"className\" | \"defaultValue\" | \"onValueChange\" | \"readOnly\" | \"value\"\n> & {\n  className?: string;\n  defaultValue?: number;\n  getItemLabel?: (value: number, max: number) => string;\n  max?: number;\n  onValueChange?: RadioGroupPrimitive.Props<number>[\"onValueChange\"];\n  size?: RatingSize;\n  value?: number;\n};\n\nconst ratingSizeClasses: Record<RatingSize, string> = {\n  sm: \"size-8 [&_svg]:size-3.5\",\n  md: \"size-10 [&_svg]:size-4\",\n  lg: \"size-12 [&_svg]:size-5\",\n};\n\nfunction Rating({\n  className,\n  defaultValue,\n  disabled = false,\n  getItemLabel = defaultItemLabel,\n  max = 5,\n  onBlur,\n  onPointerLeave,\n  onValueChange,\n  size = \"md\",\n  value: valueProp,\n  \"aria-label\": ariaLabel = \"Rating\",\n  ...props\n}: RatingProps) {\n  const itemCount = Number.isFinite(max) ? Math.max(1, Math.floor(max)) : 5;\n  const [uncontrolledValue, setUncontrolledValue] = React.useState(defaultValue);\n  const [previewValue, setPreviewValue] = React.useState<number | null>(null);\n  const value = valueProp ?? uncontrolledValue;\n  const activeValue = disabled ? (value ?? 0) : (previewValue ?? value ?? 0);\n\n  return (\n    <RadioGroupPrimitive\n      data-slot=\"rating\"\n      className={cn(\n        \"group/rating inline-flex w-fit max-w-full items-center border border-border bg-background outline-none aria-invalid:border-destructive data-disabled:cursor-not-allowed data-disabled:opacity-40\",\n        className,\n      )}\n      value={valueProp}\n      defaultValue={defaultValue}\n      disabled={disabled}\n      aria-label={ariaLabel}\n      onValueChange={(nextValue, eventDetails) => {\n        onValueChange?.(nextValue, eventDetails);\n\n        if (!eventDetails.isCanceled && valueProp === undefined) {\n          setUncontrolledValue(nextValue);\n        }\n      }}\n      onPointerLeave={(event) => {\n        onPointerLeave?.(event);\n        setPreviewValue(null);\n      }}\n      onBlur={(event) => {\n        onBlur?.(event);\n\n        if (!event.currentTarget.contains(event.relatedTarget)) {\n          setPreviewValue(null);\n        }\n      }}\n      {...props}\n    >\n      {Array.from({ length: itemCount }, (_, index) => {\n        const itemValue = index + 1;\n        const active = itemValue <= activeValue;\n\n        return (\n          <RadioPrimitive.Root\n            key={itemValue}\n            value={itemValue}\n            data-slot=\"rating-item\"\n            data-active={active ? \"\" : undefined}\n            aria-label={getItemLabel(itemValue, itemCount)}\n            className={cn(\n              \"relative inline-flex shrink-0 cursor-pointer items-center justify-center border-l border-border text-muted-foreground outline-none transition-[background-color,color,opacity] duration-100 first:border-l-0 hover:bg-muted focus-visible:z-10 focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring/40 data-disabled:pointer-events-none data-active:text-foreground group-aria-invalid/rating:focus-visible:ring-destructive/25 motion-reduce:transition-none\",\n              ratingSizeClasses[size],\n            )}\n            onPointerEnter={(event) => {\n              if (!disabled && event.pointerType !== \"touch\") {\n                setPreviewValue(itemValue);\n              }\n            }}\n            onFocus={() => {\n              if (!disabled) {\n                setPreviewValue(itemValue);\n              }\n            }}\n          >\n            <Star\n              className={cn(\n                \"transition-[fill,color] duration-100 motion-reduce:transition-none\",\n                active && \"fill-current\",\n              )}\n              strokeWidth={1.75}\n              aria-hidden=\"true\"\n            />\n          </RadioPrimitive.Root>\n        );\n      })}\n    </RadioGroupPrimitive>\n  );\n}\n\nfunction defaultItemLabel(value: number, max: number) {\n  return `${value} out of ${max} stars`;\n}\n\nexport type { RatingProps, RatingSize };\nexport { Rating };\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}