Rating
별점을 매길 수 있습니다.
Example
It can only be viewed in a mobile.
Steps
Prerequisite
Copy Code
components/Rating/index.tsx
'use client'
import { useCallback, useEffect, useId, useMemo } from 'react'
import { cn } from 'utils'
export interface Props {
value: number
onChange: (value: number) => void
readOnly?: boolean
disabled?: boolean
count?: number
half?: boolean
}
function Rating({
value,
onChange,
readOnly,
disabled,
count = 5,
half
}: Props) {
const id = useId()
const ACTIVE_COLOR: string = useMemo(() => '#fbbf24 ', [])
const INACTIVE_COLOR: string = useMemo(() => '#d1d5db', [])
const onMouseLeave = useCallback(() => {
for (let i = 0; i < value; i++) {
const second = document.getElementById(`${id}-rating-second-${i}`)
if (second) second.style.color = ACTIVE_COLOR
}
for (let i = Math.floor(value); i < count; i++) {
const second = document.getElementById(`${id}-rating-second-${i}`)
if (second) second.style.color = INACTIVE_COLOR
}
if (half) {
const first = document.getElementById(`${id}-rating-first-${value}`)
const second = document.getElementById(
`${id}-rating-second-${Math.floor(value)}`
)
if (first) first.style.color = ACTIVE_COLOR
if (second) second.style.color = INACTIVE_COLOR
for (let i = 0; i < value; i++) {
const first = document.getElementById(`${id}-rating-first-${i}`)
if (first) first.style.color = ACTIVE_COLOR
}
for (let i = Math.ceil(value); i < count; i++) {
const first = document.getElementById(`${id}-rating-first-${i}`)
if (first) first.style.color = INACTIVE_COLOR
}
}
}, [value, half, ACTIVE_COLOR, INACTIVE_COLOR, count, id])
const onMouseEnter = useCallback(
(index: number) => {
for (let i = 0; i <= index; i++) {
const second = document.getElementById(`${id}-rating-second-${i}`)
if (second) second.style.color = ACTIVE_COLOR
if (half) {
for (let j = 0; j <= index; j++) {
const first = document.getElementById(`${id}-rating-first-${j}`)
if (first) first.style.color = ACTIVE_COLOR
}
}
}
for (let i = index + 1; i < count; i++) {
const second = document.getElementById(`${id}-rating-second-${i}`)
if (second) second.style.color = INACTIVE_COLOR
if (half) {
for (let j = index + 1; j < count; j++) {
const first = document.getElementById(`${id}-rating-first-${j}`)
if (first) first.style.color = INACTIVE_COLOR
}
}
}
},
[ACTIVE_COLOR, INACTIVE_COLOR, count, half, id]
)
const onHalfMouseEnter = useCallback(
(index: number) => {
for (let i = 0; i < index; i++) {
const first = document.getElementById(`${id}-rating-first-${i}`)
const second = document.getElementById(`${id}-rating-second-${i}`)
if (first) first.style.color = ACTIVE_COLOR
if (second) second.style.color = ACTIVE_COLOR
}
const first = document.getElementById(`${id}-rating-first-${index}`)
if (first) first.style.color = ACTIVE_COLOR
const second = document.getElementById(`${id}-rating-second-${index}`)
if (second) second.style.color = INACTIVE_COLOR
for (let i = index + 1; i < count; i++) {
const first = document.getElementById(`${id}-rating-first-${i}`)
const second = document.getElementById(`${id}-rating-second-${i}`)
if (first) first.style.color = INACTIVE_COLOR
if (second) second.style.color = INACTIVE_COLOR
}
},
[ACTIVE_COLOR, INACTIVE_COLOR, count, id]
)
useEffect(() => {
if (!value) return
for (let i = 0; i < value; i++) {
const second = document.getElementById(`${id}-rating-second-${i}`)
if (second) second.style.color = ACTIVE_COLOR
}
if (half) {
for (let i = 0; i < Math.ceil(value); i++) {
const first = document.getElementById(`${id}-rating-first-${i}`)
if (first) first.style.color = ACTIVE_COLOR
}
}
}, [ACTIVE_COLOR, half, id, value])
return (
<ul
role="radiogroup"
className={cn('flex select-none', {
'cursor-not-allowed opacity-70': disabled
})}
>
{Array.from({ length: count }).map((_, key) => (
<li
key={key}
aria-checked={value > key ? 'true' : 'false'}
aria-posinset={key + 1}
aria-setsize={count}
tabIndex={disabled ? -1 : 0}
role="radio"
className={cn('text-xl text-neutral-300', {
'cursor-pointer duration-150 hover:scale-125':
!disabled && !readOnly,
relative: half
})}
onMouseLeave={disabled || readOnly ? undefined : onMouseLeave}
>
{half && (
<span
id={`${id}-rating-first-${key}`}
onMouseEnter={
disabled || readOnly ? undefined : () => onHalfMouseEnter(key)
}
onClick={
disabled || readOnly ? undefined : () => onChange(key + 0.5)
}
className="absolute left-0 top-0 h-full w-1/2 overflow-hidden duration-150"
>
★
</span>
)}
<span
id={`${id}-rating-second-${key}`}
onMouseEnter={
disabled || readOnly ? undefined : () => onMouseEnter(key)
}
onClick={disabled || readOnly ? undefined : () => onChange(key + 1)}
className="duration-150"
>
★
</span>
</li>
))}
</ul>
)
}
export default Rating
Usage
<Rating
value={value}
onChange={(value) => setState({ value })}
count={10}
readOnly
disabled
half
/>
Props
Name | Type | Default |
---|---|---|
value | number | |
onChange | (value: number) => void | |
readOnly | boolean | |
disabled | boolean | |
count | number | 5 |
half | boolean |