{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "rating",
  "title": "Rating",
  "type": "registry:ui",
  "files": [
    {
      "path": "packages/registry-react/components/rating/Rating.tsx",
      "content": "'use client'\n\nimport * as React from 'react'\nimport { cn } from '@/lib/utils'\n\nexport interface RatingProps {\n  /** Currently selected value */\n  value?: number\n  /** Default value when uncontrolled */\n  defaultValue?: number\n  /** Emitted when the value changes */\n  onValueChange?: (value: number) => void\n  /** Maximum rating value */\n  max?: number\n  /** If true, prevents user interaction */\n  readonly?: boolean\n  /** If true, disables the rating */\n  disabled?: boolean\n  /** Density of the component */\n  density?: 'compact' | 'default' | 'comfortable'\n  /** Color of the selected stars */\n  color?: string\n  /** If true, clicking the same value clears the rating */\n  clearable?: boolean\n  /** If true, stars grow on hover */\n  hover?: boolean\n  /** ARIA label for each rating item */\n  itemAriaLabel?: string\n  /** Size of the stars */\n  size?: 'x-small' | 'small' | 'medium' | 'large' | 'x-large'\n  /** Custom class for the component */\n  className?: string\n  /** Show rating count */\n  showValue?: boolean\n  /** Card variant styling */\n  variant?: 'outlined' | 'filled' | 'soft'\n  /** If true, creates a half star at 0.5 */\n  halfIncrements?: boolean\n}\n\n// Star path shared by all three icon states.\nconst STAR_PATH =\n  'M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z'\n\n// Ported 1:1 from the Vue scoped stylesheet. React has no scoped styles, so the\n// per-size star dimensions and per-variant container chrome live inline here.\nconst variantClasses: Record<NonNullable<RatingProps['variant']>, string> = {\n  outlined: '',\n  filled: 'bg-muted p-1 rounded-lg',\n  soft: 'bg-accent p-1 rounded-lg',\n}\n\nconst sizeIcon: Record<NonNullable<RatingProps['size']>, React.CSSProperties> = {\n  'x-small': { width: '0.875rem', height: '0.875rem' },\n  small: { width: '1.125rem', height: '1.125rem' },\n  medium: { width: '1.375rem', height: '1.375rem' },\n  large: { width: '1.625rem', height: '1.625rem' },\n  'x-large': { width: '2rem', height: '2rem' },\n}\n\nconst densityPad: Record<NonNullable<RatingProps['density']>, string> = {\n  compact: '0',\n  default: '0.0625rem',\n  comfortable: '0.125rem',\n}\n\nconst Rating = React.forwardRef<HTMLDivElement, RatingProps>(\n  (\n    {\n      value,\n      defaultValue = 0,\n      onValueChange,\n      max = 5,\n      readonly = false,\n      disabled = false,\n      density = 'default',\n      color = 'var(--warning)',\n      clearable = false,\n      hover = false,\n      itemAriaLabel = 'rating',\n      size = 'medium',\n      className,\n      showValue = false,\n      variant = 'outlined',\n      halfIncrements = false,\n    },\n    ref,\n  ) => {\n    const isControlled = value !== undefined\n    const [internal, setInternal] = React.useState(defaultValue)\n    const current = isControlled ? value! : internal\n\n    function emit(next: number) {\n      if (!isControlled) setInternal(next)\n      onValueChange?.(next)\n    }\n\n    function handleClick(n: number) {\n      if (disabled || readonly) return\n      if (clearable && n === current) emit(0)\n      else emit(n)\n    }\n\n    function handleKeydown(e: React.KeyboardEvent, n: number) {\n      if (e.key === 'Enter' || e.key === ' ') {\n        e.preventDefault()\n        handleClick(n)\n      } else if (e.key === 'ArrowRight' && n < max) {\n        emit(n + 1)\n      } else if (e.key === 'ArrowLeft' && n > 1) {\n        emit(n - 1)\n      }\n    }\n\n    const iconStyle = sizeIcon[size]\n\n    return (\n      <div\n        ref={ref}\n        data-uipkge=\"\"\n        data-slot=\"rating\"\n        className={cn(\n          'inline-flex items-center gap-0.5',\n          variantClasses[variant],\n          disabled && 'cursor-not-allowed opacity-50',\n          readonly && 'cursor-default',\n          showValue && 'flex items-center gap-1',\n          className,\n        )}\n        role=\"radiogroup\"\n        aria-valuenow={current}\n        aria-valuemin={0}\n        aria-valuemax={max}\n        aria-label={`Rating: ${current} of ${max}`}\n      >\n        {Array.from({ length: max }, (_, i) => i + 1).map((n) => (\n          <button\n            key={n}\n            type=\"button\"\n            disabled={disabled || readonly}\n            aria-label={`${itemAriaLabel} ${n} of ${max}`}\n            tabIndex={readonly || disabled ? -1 : 0}\n            onClick={() => handleClick(n)}\n            onKeyDown={(e) => handleKeydown(e, n)}\n            style={{ padding: densityPad[density], outlineColor: color }}\n            className={cn(\n              'inline-flex items-center justify-center border-none bg-none leading-none',\n              'focus-visible:ring-ring focus-visible:ring-2 focus-visible:outline-none',\n              clearable && 'cursor-pointer',\n              hover && 'transition-transform duration-150 ease-in-out hover:scale-115',\n            )}\n          >\n            {current >= n ? (\n              // Full star\n              <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" style={{ ...iconStyle, color }}>\n                <path d={STAR_PATH} />\n              </svg>\n            ) : current >= n - 0.5 && halfIncrements ? (\n              // Half star\n              <svg viewBox=\"0 0 24 24\" style={{ ...iconStyle, color }}>\n                <defs>\n                  <linearGradient id={`half-${n}`}>\n                    <stop offset=\"50%\" stopColor=\"currentColor\" />\n                    <stop offset=\"50%\" stopColor=\"transparent\" />\n                  </linearGradient>\n                </defs>\n                <path d={STAR_PATH} fill={`url(#half-${n})`} stroke=\"currentColor\" strokeWidth=\"1\" />\n              </svg>\n            ) : (\n              // Empty star\n              <svg\n                viewBox=\"0 0 24 24\"\n                fill=\"none\"\n                stroke=\"currentColor\"\n                strokeWidth=\"1.5\"\n                style={{ ...iconStyle, color: 'var(--muted-foreground)' }}\n              >\n                <path d={STAR_PATH} />\n              </svg>\n            )}\n          </button>\n        ))}\n        {showValue && (\n          <span className=\"ml-2 font-semibold text-foreground\">{current}</span>\n        )}\n      </div>\n    )\n  },\n)\nRating.displayName = 'Rating'\n\nexport { Rating }\n",
      "type": "registry:ui",
      "target": "~/components/ui/rating/Rating.tsx"
    },
    {
      "path": "packages/registry-react/components/rating/index.ts",
      "content": "export { Rating, type RatingProps } from './Rating'\n",
      "type": "registry:ui",
      "target": "~/components/ui/rating/index.ts"
    }
  ],
  "dependencies": [],
  "devDependencies": [],
  "registryDependencies": [],
  "description": "Star (or custom icon) rating control — pick a value from 1 to N. Read-only mode for displaying review averages, with half-step support.",
  "categories": [
    "form"
  ]
}