165 lines
4.5 KiB
TypeScript
165 lines
4.5 KiB
TypeScript
|
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>
|
||
|
);
|
||
|
}
|