{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "ds-rating",
  "title": "Rating",
  "description": "Componente interativo para avaliação baseada em ícones (estrelas).",
  "dependencies": [
    "class-variance-authority",
    "lucide-react"
  ],
  "registryDependencies": [
    "@lema-ds/tokens"
  ],
  "files": [
    {
      "path": "components/ds/ds-rating.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\nimport { StarIcon } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\n// ── Types ──\n\nexport type RatingSize = \"sm\" | \"md\" | \"lg\"\n\nexport interface RatingProps\n  extends\n    Omit<React.HTMLAttributes<HTMLDivElement>, \"onChange\">,\n    VariantProps<typeof ratingVariants> {\n  /**\n   * The current rating value.\n   */\n  value: number\n  /**\n   * The maximum rating value. Defaults to 5.\n   */\n  max?: number\n  /**\n   * Disables interaction and hover states.\n   */\n  readonly?: boolean\n  /**\n   * Disables the component entirely (opacity).\n   */\n  disabled?: boolean\n  /**\n   * Callback when a star is clicked.\n   */\n  onChange?: (value: number) => void\n  /**\n   * Custom icon component to render. Defaults to StarIcon.\n   */\n  icon?: React.ElementType\n}\n\n// ── Variants ──\n\nexport const ratingVariants = cva(\"inline-flex items-center\", {\n  variants: {\n    size: {\n      sm: \"gap-1\",\n      md: \"gap-1.5\",\n      lg: \"gap-2\",\n    },\n    disabled: {\n      true: \"pointer-events-none opacity-50\",\n      false: \"\",\n    },\n  },\n  defaultVariants: {\n    size: \"md\",\n    disabled: false,\n  },\n})\n\nexport const starVariants = cva(\n  \"-m-1 shrink-0 p-1 transition-colors focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background focus-visible:outline-none\",\n  {\n    variants: {\n      size: {\n        sm: \"size-4\",\n        md: \"size-5\",\n        lg: \"size-6\",\n      },\n      interactive: {\n        true: \"min-h-6 min-w-6 cursor-pointer\",\n        false: \"\",\n      },\n    },\n    defaultVariants: {\n      size: \"md\",\n      interactive: false,\n    },\n  }\n)\n\n// ── Component ──\n\nexport const Rating = React.forwardRef<HTMLDivElement, RatingProps>(\n  (\n    {\n      value = 0,\n      max = 5,\n      size = \"md\",\n      readonly = false,\n      disabled = false,\n      icon: Icon = StarIcon,\n      onChange,\n      className,\n      ...props\n    },\n    ref\n  ) => {\n    const [hoverValue, setHoverValue] = React.useState<number | null>(null)\n    const starElementsRef = React.useRef<Record<number, HTMLDivElement | null>>(\n      {}\n    )\n\n    const isInteractive = !readonly && !disabled\n\n    const handleMouseEnter = (index: number) => {\n      if (!isInteractive) return\n      setHoverValue(index)\n    }\n\n    const handleMouseLeave = () => {\n      if (!isInteractive) return\n      setHoverValue(null)\n    }\n\n    const handleClick = (index: number) => {\n      if (!isInteractive || !onChange) return\n      onChange(index)\n    }\n\n    const handleKeyDown = (e: React.KeyboardEvent, index: number) => {\n      if (!isInteractive || !onChange) return\n\n      if (e.key === \"Enter\" || e.key === \" \") {\n        e.preventDefault()\n        onChange(index)\n      } else if (e.key === \"ArrowRight\" || e.key === \"ArrowUp\") {\n        e.preventDefault()\n        if (index < max) {\n          onChange(index + 1)\n          starElementsRef.current[index + 1]?.focus()\n        }\n      } else if (e.key === \"ArrowLeft\" || e.key === \"ArrowDown\") {\n        e.preventDefault()\n        if (index > 1) {\n          onChange(index - 1)\n          starElementsRef.current[index - 1]?.focus()\n        }\n      }\n    }\n\n    return (\n      <div\n        ref={ref}\n        className={cn(ratingVariants({ size, disabled }), className)}\n        role={isInteractive ? \"radiogroup\" : \"img\"}\n        aria-label={isInteractive ? \"Rating\" : `Rating: ${value} out of ${max}`}\n        onMouseLeave={handleMouseLeave}\n        {...props}\n      >\n        {Array.from({ length: max }).map((_, i) => {\n          const ratingValue = i + 1\n          const isActive = hoverValue\n            ? ratingValue <= hoverValue\n            : ratingValue <= value\n\n          return (\n            <div\n              key={ratingValue}\n              ref={(el) => {\n                starElementsRef.current[ratingValue] = el\n              }}\n              role={isInteractive ? \"radio\" : undefined}\n              aria-label={isInteractive ? `${ratingValue}` : undefined}\n              aria-checked={isInteractive ? ratingValue === value : undefined}\n              aria-posinset={isInteractive ? ratingValue : undefined}\n              aria-setsize={isInteractive ? max : undefined}\n              tabIndex={\n                isInteractive\n                  ? ratingValue === value || (value === 0 && ratingValue === 1)\n                    ? 0\n                    : -1\n                  : undefined\n              }\n              onMouseEnter={() => handleMouseEnter(ratingValue)}\n              onClick={() => handleClick(ratingValue)}\n              onKeyDown={(e) => handleKeyDown(e, ratingValue)}\n              className={cn(\n                starVariants({ size, interactive: isInteractive }),\n                isActive\n                  ? \"fill-warning text-warning\"\n                  : \"fill-muted text-muted-foreground\"\n              )}\n            >\n              <Icon className=\"size-full\" aria-hidden=\"true\" />\n            </div>\n          )\n        })}\n      </div>\n    )\n  }\n)\nRating.displayName = \"Rating\"\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}
