visual-novel-web/src/components/ui/rate.tsx

97 lines
2.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client';
import { useControllableValue } from 'ahooks';
import React, { useState } from 'react';
import { cn } from '@/lib';
import Image from 'next/image';
type RateProps = {
readonly?: boolean;
// 0 - 10
value?: number;
onChange?: (value: number) => void;
} & React.HTMLAttributes<HTMLDivElement>;
export default function Rate(props: RateProps) {
const { readonly = false, ...others } = props;
const [value = 0, onChange] = useControllableValue(props);
const [hoveredValue, setHoveredValue] = useState(0);
const readonlyRender = () => {
// value 范围 0-10每颗星代表 2 分
const fullCounts = Math.floor(value / 2); // 满星数量
const halfCount = value % 2; // 半星数量0 或 1
const emptyCount = 5 - fullCounts - halfCount; // 空星数量
const starDoms = [
// 满星
...Array.from({ length: fullCounts }, (_, i) => (
<Image
key={`full-${i}`}
src={'/component/star_full.svg'}
width={20}
height={20}
alt="full star"
/>
)),
// 半星
...Array.from({ length: halfCount }, (_, i) => (
<Image
key={`half-${i}`}
src={'/component/star_half.svg'}
width={20}
height={20}
alt="half star"
/>
)),
// 空星
];
return (
<>
{starDoms}
<span className="font-normal" style={{ color: 'rgba(255, 225, 0, 1)' }}>
{value}
</span>
</>
);
};
const editRender = () => {
const finalValue = hoveredValue || value;
// 将 0-10 的值转换为 0-5 的星级
const starValue = finalValue / 2;
return Array.from({ length: 5 }, (_, index) => {
let src = '/component/star_empty.svg';
// 判断当前星星应该显示什么状态
if (index < Math.floor(starValue)) {
// 满星
src = '/component/star_full.svg';
}
return (
<Image
key={index}
src={src}
width={20}
height={20}
alt="star"
className="cursor-pointer"
onClick={() => onChange?.((index + 1) * 2)}
onMouseEnter={() => setHoveredValue((index + 1) * 2)}
/>
);
});
};
const render = readonly ? readonlyRender : editRender;
return (
<div
{...others}
onMouseLeave={() => setHoveredValue(0)}
className={cn('flex items-center gap-1', props.className)}
>
{render()}
</div>
);
}