parent
8e4baa02b1
commit
dd71900f8b
2 changed files with 159 additions and 1 deletions
@ -0,0 +1,146 @@ |
||||
import { useState } from 'react'; |
||||
import { cn } from '../../lib/classname'; |
||||
|
||||
type StarValue = 0 | 0.5 | 1; |
||||
|
||||
type RatingProps = { |
||||
ratings?: number; |
||||
size?: number; |
||||
}; |
||||
|
||||
export function Rating(props: RatingProps) { |
||||
const { ratings = 0, size } = props; |
||||
|
||||
const [stars, setStars] = useState( |
||||
Array.from({ length: 5 }, (_, i) => { |
||||
const rating = Math.floor(ratings); |
||||
if (i < rating) { |
||||
return 1; |
||||
} |
||||
|
||||
if (i === rating && ratings % 1 !== 0) { |
||||
return 0.5; |
||||
} |
||||
|
||||
return 0; |
||||
}), |
||||
); |
||||
|
||||
const [dynamicStars, setDynamicStars] = useState(stars); |
||||
|
||||
return ( |
||||
<div className="mt-4 flex"> |
||||
{dynamicStars.map((star, counter) => ( |
||||
<RatingStar |
||||
key={`star-${counter}`} |
||||
value={star} |
||||
onValueChange={(value) => { |
||||
const newStars = [...stars]; |
||||
newStars.fill(1, 0, counter); |
||||
newStars[counter] = value; |
||||
newStars.fill(0, counter + 1); |
||||
|
||||
setDynamicStars(newStars); |
||||
}} |
||||
onClick={(value) => { |
||||
const newStars = [...stars]; |
||||
newStars.fill(1, 0, counter); |
||||
newStars[counter] = value; |
||||
newStars.fill(0, counter + 1); |
||||
|
||||
setStars(newStars); |
||||
setDynamicStars(newStars); |
||||
}} |
||||
onMouseLeave={() => { |
||||
setDynamicStars(stars); |
||||
}} |
||||
/> |
||||
))} |
||||
</div> |
||||
); |
||||
} |
||||
|
||||
type RatingStarProps = { |
||||
value: StarValue; |
||||
onValueChange?: (value: StarValue) => void; |
||||
onMouseLeave?: () => void; |
||||
onClick: (value: StarValue) => void; |
||||
startSize?: number; |
||||
}; |
||||
|
||||
export function RatingStar(props: RatingStarProps) { |
||||
const { value, onValueChange, onClick, onMouseLeave, startSize = 20 } = props; |
||||
|
||||
return ( |
||||
<span |
||||
onMouseMove={(e) => { |
||||
e.preventDefault(); |
||||
e.stopPropagation(); |
||||
|
||||
const rect = e.currentTarget.getBoundingClientRect(); |
||||
const mid = rect.width / 2; |
||||
const value = e.clientX < rect.left + mid ? 0.5 : 1; |
||||
onValueChange?.(value); |
||||
}} |
||||
onMouseEnter={(e) => { |
||||
e.preventDefault(); |
||||
e.stopPropagation(); |
||||
|
||||
const rect = e.currentTarget.getBoundingClientRect(); |
||||
const mid = rect.width / 2; |
||||
const value = e.clientX < rect.left + mid ? 0.5 : 1; |
||||
onValueChange?.(value); |
||||
}} |
||||
onMouseLeave={onMouseLeave} |
||||
onClick={() => { |
||||
onClick(value); |
||||
}} |
||||
className="relative block cursor-pointer" |
||||
style={{ |
||||
width: `${startSize}px`, |
||||
height: `${startSize}px`, |
||||
}} |
||||
> |
||||
<span |
||||
className={cn( |
||||
'absolute overflow-hidden', |
||||
value === 0.5 ? 'w-1/2' : 'w-0', |
||||
)} |
||||
> |
||||
<svg |
||||
xmlns="http://www.w3.org/2000/svg" |
||||
viewBox="0 0 24 24" |
||||
stroke="currentColor" |
||||
strokeWidth="2" |
||||
strokeLinecap="round" |
||||
strokeLinejoin="round" |
||||
className="fill-red" |
||||
style={{ |
||||
width: `${startSize}px`, |
||||
height: `${startSize}px`, |
||||
}} |
||||
> |
||||
<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2" /> |
||||
</svg> |
||||
</span> |
||||
|
||||
<span className="absolute"> |
||||
<svg |
||||
xmlns="http://www.w3.org/2000/svg" |
||||
viewBox="0 0 24 24" |
||||
stroke="currentColor" |
||||
strokeWidth="2" |
||||
strokeLinecap="round" |
||||
strokeLinejoin="round" |
||||
className={cn('', value === 1 ? 'fill-red' : 'fill-none')} |
||||
style={{ |
||||
width: `${startSize}px`, |
||||
height: `${startSize}px`, |
||||
}} |
||||
> |
||||
<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2" /> |
||||
</svg> |
||||
</span> |
||||
</span> |
||||
); |
||||
} |
Loading…
Reference in new issue