{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "half-stars",
  "title": "Half Stars",
  "author": "AlgorizeTech",
  "description": "A star rating supporting half-star precision, in read-only or interactive mode. Use it for reviews and any score shown out of five.",
  "dependencies": [],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "src/components/custom/blocks/HalfStarsBlock.tsx",
      "content": "\"use client\";\n\nimport React, { useState } from \"react\";\nimport { cn } from \"@/lib/utils\";\n\nexport interface HalfStarsProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange'> {\n  rating: number;\n  maxStars?: number;\n  size?: number;\n  activeColor?: string;\n  inactiveColor?: string;\n  showText?: boolean;\n  interactive?: boolean;\n  allowHalf?: boolean; // Controls whether 0.5 increments are allowed (legacy)\n  step?: number; // 0.1, 0.5, 1, etc. Overrides allowHalf.\n  onRatingChange?: (rating: number) => void;\n  onHoverChange?: (rating: number) => void;\n}\n\nexport const HalfStars = React.forwardRef<HTMLDivElement, HalfStarsProps>(\n  (\n    {\n      rating,\n      maxStars = 5,\n      size = 24,\n      activeColor = \"text-primary\",\n      inactiveColor = \"text-foreground/15\",\n      showText = true, // Default to true as per user request\n      interactive = false,\n      allowHalf = true, // Default to allowing half stars\n      step,\n      onRatingChange,\n      onHoverChange,\n      className,\n      ...props\n    },\n    ref\n  ) => {\n    const [hoverRating, setHoverRating] = useState<number | null>(null);\n\n    // Use hoverRating if interacting, otherwise use the passed rating\n    const displayRating = hoverRating !== null ? hoverRating : rating;\n    const safeRating = Math.max(0, Math.min(displayRating, maxStars));\n\n    const calculateRatingFromEvent = (e: React.MouseEvent<HTMLDivElement>, index: number) => {\n      const rect = e.currentTarget.getBoundingClientRect();\n      const x = Math.max(0, Math.min(e.clientX - rect.left, rect.width));\n      const percent = x / rect.width;\n      \n      const activeStep = step !== undefined ? step : (allowHalf ? 0.5 : 1);\n      const fraction = Math.ceil(percent / activeStep) * activeStep;\n      const safeFraction = Math.max(fraction, activeStep);\n      \n      return parseFloat((index + safeFraction).toFixed(2));\n    };\n\n    const handleMouseMove = (e: React.MouseEvent<HTMLDivElement>, index: number) => {\n      if (!interactive) return;\n      \n      const newHoverRating = calculateRatingFromEvent(e, index);\n      \n      if (hoverRating !== newHoverRating) {\n        setHoverRating(newHoverRating);\n        onHoverChange?.(newHoverRating);\n      }\n    };\n\n    const handleMouseLeave = () => {\n      if (!interactive) return;\n      setHoverRating(null);\n      onHoverChange?.(0); // Optional: signal hover ended\n    };\n\n    const handleClick = (index: number, e: React.MouseEvent<HTMLDivElement>) => {\n      if (!interactive) return;\n      const newRating = calculateRatingFromEvent(e, index);\n      onRatingChange?.(newRating);\n    };\n\n    return (\n      <div\n        ref={ref}\n        className={cn(\"flex items-center gap-2\", className)}\n        onMouseLeave={handleMouseLeave}\n        {...props}\n      >\n        <div className=\"flex items-center gap-1\">\n          {Array.from({ length: maxStars }).map((_, index) => {\n            const fillPercentage = Math.max(\n              0,\n              Math.min(1, safeRating - index)\n            );\n\n            return (\n              <div\n                key={index}\n                style={{ width: size, height: size }}\n                className={cn(\n                  \"relative flex-shrink-0\",\n                  interactive && \"cursor-pointer transition-transform hover:scale-110\"\n                )}\n                onMouseMove={(e) => handleMouseMove(e, index)}\n                onClick={(e) => handleClick(index, e)}\n              >\n                {/* Inactive Background Star */}\n                <svg\n                  xmlns=\"http://www.w3.org/2000/svg\"\n                  viewBox=\"0 0 24 24\"\n                  fill=\"currentColor\"\n                  stroke=\"none\"\n                  className={cn(\"absolute inset-0 w-full h-full transition-colors duration-200\", inactiveColor)}\n                >\n                  <path d=\"M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z\" />\n                </svg>\n\n                {/* Active Foreground Star (Clipped) */}\n                <svg\n                  xmlns=\"http://www.w3.org/2000/svg\"\n                  viewBox=\"0 0 24 24\"\n                  fill=\"currentColor\"\n                  stroke=\"none\"\n                  className={cn(\"absolute inset-0 w-full h-full transition-all duration-200\", activeColor)}\n                  style={{\n                    clipPath: `polygon(0 0, ${fillPercentage * 100}% 0, ${fillPercentage * 100}% 100%, 0 100%)`,\n                  }}\n                >\n                  <path d=\"M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z\" />\n                </svg>\n              </div>\n            );\n          })}\n        </div>\n        {showText && (\n          <span className=\"text-xl font-semibold text-foreground tabular-nums\">\n            <span className=\"inline-block w-[45px] text-center\">\n              {Number.isInteger(safeRating) ? safeRating : safeRating.toFixed(1)}\n            </span>\n            <span className=\"text-base text-muted-foreground font-normal ml-1\">\n              / {maxStars}\n            </span>\n          </span>\n        )}\n      </div>\n    );\n  }\n);\n\nHalfStars.displayName = \"HalfStars\";\n",
      "type": "registry:component",
      "target": "components/custom/blocks/HalfStarsBlock.tsx"
    }
  ],
  "docs": "Named export: HalfStars. Set `interactive` to let users pick a rating and `allowHalf` for half-star steps. `onHoverChange` fires during hover so you can preview the value before commit. Leave `interactive` off to render a static score.",
  "categories": [
    "forms",
    "data-display"
  ],
  "type": "registry:component"
}
