{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "rating",
  "title": "Rating",
  "description": "Rating component",
  "registryDependencies": [],
  "files": [
    {
      "path": "src/components/ui/rating.tsx",
      "content": "'use client'\n\nimport { useControllableState } from '@radix-ui/react-use-controllable-state'\nimport { type LucideProps, StarIcon } from 'lucide-react'\nimport type { KeyboardEvent, MouseEvent, ReactElement, ReactNode } from 'react'\nimport {\n\tChildren,\n\tcloneElement,\n\tcreateContext,\n\tuseCallback,\n\tuseContext,\n\tuseEffect,\n\tuseRef,\n\tuseState,\n} from 'react'\nimport { cn } from '@/lib/utils'\n\ntype RatingContextValue = {\n\tvalue: number\n\treadOnly: boolean\n\thoverValue: number | null\n\tfocusedStar: number | null\n\thandleValueChange: (\n\t\tevent: MouseEvent<HTMLButtonElement> | KeyboardEvent<HTMLButtonElement>,\n\t\tvalue: number,\n\t) => void\n\thandleKeyDown: (event: KeyboardEvent<HTMLButtonElement>) => void\n\tsetHoverValue: (value: number | null) => void\n\tsetFocusedStar: (value: number | null) => void\n}\n\nconst RatingContext = createContext<RatingContextValue | null>(null)\n\nconst useRating = () => {\n\tconst context = useContext(RatingContext)\n\tif (!context) {\n\t\tthrow new Error('useRating must be used within a Rating component')\n\t}\n\treturn context\n}\n\nexport type RatingButtonProps = LucideProps & {\n\tindex?: number\n\ticon?: ReactElement<LucideProps>\n}\n\nexport const RatingButton = ({\n\tindex: providedIndex,\n\tsize = 20,\n\tclassName,\n\ticon = <StarIcon />,\n}: RatingButtonProps) => {\n\tconst {\n\t\tvalue,\n\t\treadOnly,\n\t\thoverValue,\n\t\tfocusedStar,\n\t\thandleValueChange,\n\t\thandleKeyDown,\n\t\tsetHoverValue,\n\t\tsetFocusedStar,\n\t} = useRating()\n\n\tconst index = providedIndex ?? 0\n\tconst isActive = index < (hoverValue ?? focusedStar ?? value ?? 0)\n\tlet tabIndex = -1\n\n\tif (!readOnly) {\n\t\ttabIndex = value === index + 1 ? 0 : -1\n\t}\n\n\tconst handleClick = useCallback(\n\t\t(event: MouseEvent<HTMLButtonElement>) => {\n\t\t\thandleValueChange(event, index + 1)\n\t\t},\n\t\t[handleValueChange, index],\n\t)\n\n\tconst handleMouseEnter = useCallback(() => {\n\t\tif (!readOnly) {\n\t\t\tsetHoverValue(index + 1)\n\t\t}\n\t}, [readOnly, setHoverValue, index])\n\n\tconst handleFocus = useCallback(() => {\n\t\tsetFocusedStar(index + 1)\n\t}, [setFocusedStar, index])\n\n\tconst handleBlur = useCallback(() => {\n\t\tsetFocusedStar(null)\n\t}, [setFocusedStar])\n\n\treturn (\n\t\t<button\n\t\t\tclassName={cn(\n\t\t\t\t'rounded-full focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',\n\t\t\t\t'p-0.5',\n\t\t\t\treadOnly && 'cursor-not-allowed',\n\t\t\t\tclassName,\n\t\t\t)}\n\t\t\tdisabled={readOnly}\n\t\t\tonBlur={handleBlur}\n\t\t\tonClick={handleClick}\n\t\t\tonFocus={handleFocus}\n\t\t\tonKeyDown={handleKeyDown}\n\t\t\tonMouseEnter={handleMouseEnter}\n\t\t\ttabIndex={tabIndex}\n\t\t\ttype=\"button\"\n\t\t>\n\t\t\t{cloneElement(icon, {\n\t\t\t\tsize,\n\t\t\t\tclassName: cn(\n\t\t\t\t\t'transition-colors duration-200',\n\t\t\t\t\tisActive && 'fill-current',\n\t\t\t\t\t!readOnly && 'cursor-pointer',\n\t\t\t\t),\n\t\t\t\t'aria-hidden': 'true',\n\t\t\t})}\n\t\t</button>\n\t)\n}\n\nexport type RatingProps = {\n\tdefaultValue?: number\n\tvalue?: number\n\tonChange?: (\n\t\tevent: MouseEvent<HTMLButtonElement> | KeyboardEvent<HTMLButtonElement>,\n\t\tvalue: number,\n\t) => void\n\tonValueChange?: (value: number) => void\n\treadOnly?: boolean\n\tclassName?: string\n\tchildren?: ReactNode\n}\n\nexport const Rating = ({\n\tvalue: controlledValue,\n\tonValueChange: controlledOnValueChange,\n\tdefaultValue = 0,\n\tonChange,\n\treadOnly = false,\n\tclassName,\n\tchildren,\n\t...props\n}: RatingProps) => {\n\tconst [hoverValue, setHoverValue] = useState<number | null>(null)\n\tconst [focusedStar, setFocusedStar] = useState<number | null>(null)\n\tconst containerRef = useRef<HTMLDivElement>(null)\n\tconst [value, onValueChange] = useControllableState({\n\t\tdefaultProp: defaultValue,\n\t\tprop: controlledValue,\n\t\tonChange: controlledOnValueChange,\n\t})\n\n\tconst handleValueChange = useCallback(\n\t\t(\n\t\t\tevent: MouseEvent<HTMLButtonElement> | KeyboardEvent<HTMLButtonElement>,\n\t\t\tnewValue: number,\n\t\t) => {\n\t\t\tif (!readOnly) {\n\t\t\t\tonChange?.(event, newValue)\n\t\t\t\tonValueChange?.(newValue)\n\t\t\t}\n\t\t},\n\t\t[readOnly, onChange, onValueChange],\n\t)\n\n\tconst handleKeyDown = useCallback(\n\t\t(event: KeyboardEvent<HTMLButtonElement>) => {\n\t\t\tif (readOnly) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tconst total = Children.count(children)\n\t\t\tlet newValue = focusedStar !== null ? focusedStar : (value ?? 0)\n\n\t\t\tswitch (event.key) {\n\t\t\t\tcase 'ArrowRight':\n\t\t\t\t\tif (event.shiftKey || event.metaKey) {\n\t\t\t\t\t\tnewValue = total\n\t\t\t\t\t} else {\n\t\t\t\t\t\tnewValue = Math.min(total, newValue + 1)\n\t\t\t\t\t}\n\t\t\t\t\tbreak\n\t\t\t\tcase 'ArrowLeft':\n\t\t\t\t\tif (event.shiftKey || event.metaKey) {\n\t\t\t\t\t\tnewValue = 1\n\t\t\t\t\t} else {\n\t\t\t\t\t\tnewValue = Math.max(1, newValue - 1)\n\t\t\t\t\t}\n\t\t\t\t\tbreak\n\t\t\t\tdefault:\n\t\t\t\t\treturn\n\t\t\t}\n\n\t\t\tevent.preventDefault()\n\t\t\tsetFocusedStar(newValue)\n\t\t\thandleValueChange(event, newValue)\n\t\t},\n\t\t[focusedStar, value, children, readOnly, handleValueChange],\n\t)\n\n\tuseEffect(() => {\n\t\tif (focusedStar !== null && containerRef.current) {\n\t\t\tconst buttons = containerRef.current.querySelectorAll('button')\n\t\t\tbuttons[focusedStar - 1]?.focus()\n\t\t}\n\t}, [focusedStar])\n\n\tconst contextValue: RatingContextValue = {\n\t\tvalue: value ?? 0,\n\t\treadOnly,\n\t\thoverValue,\n\t\tfocusedStar,\n\t\thandleValueChange,\n\t\thandleKeyDown,\n\t\tsetHoverValue,\n\t\tsetFocusedStar,\n\t}\n\n\treturn (\n\t\t<RatingContext.Provider value={contextValue}>\n\t\t\t<div\n\t\t\t\taria-label=\"Rating\"\n\t\t\t\tclassName={cn('inline-flex items-center gap-0.5', className)}\n\t\t\t\tonMouseLeave={() => setHoverValue(null)}\n\t\t\t\tref={containerRef}\n\t\t\t\trole=\"radiogroup\"\n\t\t\t\t{...props}\n\t\t\t>\n\t\t\t\t{Children.map(children, (child, index) => {\n\t\t\t\t\tif (!child) {\n\t\t\t\t\t\treturn null\n\t\t\t\t\t}\n\n\t\t\t\t\treturn cloneElement(child as ReactElement<RatingButtonProps>, {\n\t\t\t\t\t\tindex,\n\t\t\t\t\t})\n\t\t\t\t})}\n\t\t\t</div>\n\t\t</RatingContext.Provider>\n\t)\n}\n",
      "type": "registry:component"
    }
  ],
  "type": "registry:component"
}