{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "rating",
  "type": "registry:component",
  "title": "Rating",
  "description": "Inline star rating for lightweight learner feedback.",
  "dependencies": [
    "@vllnt/ui@^0.2.1"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "registry/default/rating/rating.tsx",
      "content": "\"use client\";\n\nimport { useMemo, useState } from \"react\";\n\nimport { Star } from \"lucide-react\";\nimport type { ReactNode } from \"react\";\n\nimport { cn } from \"@vllnt/ui\";\n\nconst sizeClasses = {\n  lg: \"size-6\",\n  md: \"size-5\",\n  sm: \"size-4\",\n};\n\nexport type RatingProps = {\n  allowClear?: boolean;\n  className?: string;\n  defaultValue?: number;\n  label?: string;\n  max?: number;\n  onValueChange?: (value: number) => void;\n  readOnly?: boolean;\n  showValue?: boolean;\n  size?: keyof typeof sizeClasses;\n  value?: number;\n};\n\ntype RatingStarsProps = {\n  activeValue: number;\n  hoveredValue: number;\n  label: string;\n  max: number;\n  onHoverChange: (value: number) => void;\n  onSelect: (value: number) => void;\n  readOnly: boolean;\n  size: keyof typeof sizeClasses;\n};\n\nfunction RatingStars({\n  activeValue,\n  hoveredValue,\n  label,\n  max,\n  onHoverChange,\n  onSelect,\n  readOnly,\n  size,\n}: RatingStarsProps): ReactNode {\n  const stars = useMemo(\n    () => Array.from({ length: max }, (_, index) => index + 1),\n    [max],\n  );\n  const displayValue = hoveredValue || activeValue;\n\n  return (\n    <div\n      aria-label={label}\n      className=\"inline-flex items-center gap-1\"\n      role=\"radiogroup\"\n    >\n      {stars.map((starValue) => {\n        const isFilled = starValue <= displayValue;\n\n        return (\n          <button\n            aria-checked={activeValue === starValue}\n            aria-label={`${starValue} ${starValue === 1 ? \"star\" : \"stars\"}`}\n            className={cn(\n              \"rounded-sm text-muted-foreground transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\",\n              !readOnly && \"hover:text-amber-500\",\n              isFilled && \"text-amber-500\",\n            )}\n            disabled={readOnly}\n            key={starValue}\n            onBlur={() => {\n              onHoverChange(0);\n            }}\n            onClick={() => {\n              onSelect(starValue);\n            }}\n            onMouseEnter={() => {\n              if (!readOnly) {\n                onHoverChange(starValue);\n              }\n            }}\n            onMouseLeave={() => {\n              onHoverChange(0);\n            }}\n            role=\"radio\"\n            type=\"button\"\n          >\n            <Star\n              className={cn(sizeClasses[size], isFilled && \"fill-current\")}\n              strokeWidth={1.75}\n            />\n          </button>\n        );\n      })}\n    </div>\n  );\n}\n\nexport function Rating({\n  allowClear = false,\n  className,\n  defaultValue = 0,\n  label = \"Rating\",\n  max = 5,\n  onValueChange,\n  readOnly = false,\n  showValue = false,\n  size = \"md\",\n  value,\n}: RatingProps): ReactNode {\n  const isControlled = value !== undefined;\n  const [internalValue, setInternalValue] = useState(defaultValue);\n  const [hoveredValue, setHoveredValue] = useState(0);\n  const activeValue = isControlled ? (value ?? 0) : internalValue;\n\n  const handleSelect = (nextValue: number): void => {\n    const resolvedValue =\n      allowClear && activeValue === nextValue ? 0 : nextValue;\n\n    if (!isControlled) {\n      setInternalValue(resolvedValue);\n    }\n\n    onValueChange?.(resolvedValue);\n  };\n\n  return (\n    <div className={cn(\"inline-flex flex-col gap-2\", className)}>\n      <div className=\"inline-flex items-center gap-3\">\n        <RatingStars\n          activeValue={activeValue}\n          hoveredValue={hoveredValue}\n          label={label}\n          max={max}\n          onHoverChange={setHoveredValue}\n          onSelect={handleSelect}\n          readOnly={readOnly}\n          size={size}\n        />\n        {showValue ? (\n          <span className=\"text-sm text-muted-foreground\">\n            {activeValue}/{max}\n          </span>\n        ) : null}\n      </div>\n    </div>\n  );\n}\n",
      "type": "registry:component"
    }
  ],
  "version": "0.2.1",
  "stability": "stable"
}
