import { LucideProps, Star } from "lucide-react"; import React, { DetailedHTMLProps, ForwardRefExoticComponent, HTMLAttributes, RefAttributes, } from "react"; import { twMerge } from "tailwind-merge"; type StarProps = { total?: number; value?: number; size?: number; starProps?: LucideProps; allowRating?: boolean; setValueExternal?: React.Dispatch<React.SetStateAction<number>>; }; export type TWUI_STAR_RATING_PROPS = DetailedHTMLProps< HTMLAttributes<HTMLDivElement>, HTMLDivElement > & StarProps; let timeout: any; /** * # Star Rating Component * @className_wrapper twui-star-rating */ export default function StarRating({ total = 5, value = 0, size, starProps, allowRating, setValueExternal, ...props }: TWUI_STAR_RATING_PROPS) { const totalArray = Array(total).fill(null); const [finalValue, setFinalValue] = React.useState(value); const [selectedStarValue, setSelectedStarValue] = React.useState(value); const starClicked = React.useRef(false); const sectionHovered = React.useRef(false); React.useEffect(() => { window.clearTimeout(timeout); timeout = setTimeout(() => { setValueExternal?.(finalValue); }, 500); }, [selectedStarValue]); return ( <div {...props} className={twMerge( "flex flex-row items-center gap-0 -ml-[2px]", "twui-star-rating", props.className )} onMouseEnter={() => { sectionHovered.current = true; }} onMouseLeave={() => { sectionHovered.current = false; }} > {totalArray.map((_, index) => { return ( <StarComponent {...{ total, value, size, starProps, index, allowRating, finalValue, setFinalValue, starClicked, selectedStarValue, sectionHovered, setSelectedStarValue, }} key={index} /> ); })} </div> ); } function StarComponent({ value = 0, size = 20, starProps, index, allowRating, finalValue, setFinalValue, starClicked, sectionHovered, setSelectedStarValue, selectedStarValue, }: StarProps & { index: number; finalValue: number; setFinalValue: React.Dispatch<React.SetStateAction<number>>; setSelectedStarValue: React.Dispatch<React.SetStateAction<number>>; starClicked: React.MutableRefObject<boolean>; sectionHovered: React.MutableRefObject<boolean>; selectedStarValue: number; }) { const isActive = index < finalValue; return ( <div className={twMerge("p-[2px]", allowRating && "cursor-pointer")} onMouseEnter={() => { if (!allowRating) return; setFinalValue(index + 1); }} onMouseLeave={() => { if (!allowRating) return; setTimeout(() => { if (sectionHovered.current) { return; } if (!starClicked.current) { setFinalValue(0); } if (selectedStarValue) { setFinalValue(selectedStarValue); } }, 200); }} onClick={() => { if (!allowRating) return; starClicked.current = true; setSelectedStarValue(index + 1); }} > <Star size={size} className={twMerge( "text-slate-300 dark:text-white/20", isActive && "text-orange-500 dark:text-orange-400 fill-orange-500 dark:fill-orange-400", // allowRating && // "hover:text-orange-500 hover:dark:text-orange-400 hover:fill-orange-500 hover:dark:fill-orange-400", starProps?.className )} {...starProps} /> </div> ); }