2026-03-18 15:18:47 +08:00
|
|
|
import { useEffect } from 'react';
|
2026-03-07 05:42:32 +00:00
|
|
|
import type { DifficultyRating } from '../types/vocabulary';
|
|
|
|
|
|
|
|
|
|
interface RatingButtonsProps {
|
|
|
|
|
onRate: (rating: DifficultyRating) => void;
|
|
|
|
|
disabled?: boolean;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-18 15:18:47 +08:00
|
|
|
interface ButtonConfig {
|
|
|
|
|
rating: DifficultyRating;
|
|
|
|
|
label: string;
|
|
|
|
|
sublabel: string;
|
|
|
|
|
shortcut: string;
|
|
|
|
|
color: string;
|
|
|
|
|
shadow: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const BUTTONS: ButtonConfig[] = [
|
|
|
|
|
{
|
|
|
|
|
rating: 'again',
|
|
|
|
|
label: '重来',
|
|
|
|
|
sublabel: '< 1 分钟',
|
|
|
|
|
shortcut: '1',
|
|
|
|
|
color: 'from-rose-400 to-red-500',
|
|
|
|
|
shadow: 'shadow-rose-200'
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
rating: 'hard',
|
|
|
|
|
label: '困难',
|
|
|
|
|
sublabel: '~ 2 天',
|
|
|
|
|
shortcut: '2',
|
|
|
|
|
color: 'from-orange-400 to-amber-500',
|
|
|
|
|
shadow: 'shadow-orange-200'
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
rating: 'good',
|
|
|
|
|
label: '良好',
|
|
|
|
|
sublabel: '~ 4 天',
|
|
|
|
|
shortcut: '3',
|
|
|
|
|
color: 'from-blue-400 to-indigo-500',
|
|
|
|
|
shadow: 'shadow-blue-200'
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
rating: 'easy',
|
|
|
|
|
label: '简单',
|
|
|
|
|
sublabel: '~ 7 天',
|
|
|
|
|
shortcut: '4',
|
|
|
|
|
color: 'from-emerald-400 to-teal-500',
|
|
|
|
|
shadow: 'shadow-emerald-200'
|
|
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
|
2026-03-07 05:42:32 +00:00
|
|
|
export function RatingButtons({ onRate, disabled }: RatingButtonsProps) {
|
2026-03-18 15:18:47 +08:00
|
|
|
// 键盘快捷键支持
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const handleKeyDown = (e: KeyboardEvent) => {
|
|
|
|
|
if (disabled) return;
|
|
|
|
|
|
|
|
|
|
const keyMap: Record<string, DifficultyRating> = {
|
|
|
|
|
'1': 'again',
|
|
|
|
|
'2': 'hard',
|
|
|
|
|
'3': 'good',
|
|
|
|
|
'4': 'easy',
|
|
|
|
|
'Enter': 'good',
|
|
|
|
|
' ': 'good',
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const rating = keyMap[e.key];
|
|
|
|
|
if (rating) {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
onRate(rating);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
window.addEventListener('keydown', handleKeyDown);
|
|
|
|
|
return () => window.removeEventListener('keydown', handleKeyDown);
|
|
|
|
|
}, [onRate, disabled]);
|
2026-03-07 05:42:32 +00:00
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="grid grid-cols-4 gap-3 mt-8">
|
2026-03-18 15:18:47 +08:00
|
|
|
{BUTTONS.map(({ rating, label, sublabel, shortcut, color, shadow }) => (
|
2026-03-07 05:42:32 +00:00
|
|
|
<button
|
|
|
|
|
key={rating}
|
|
|
|
|
onClick={() => onRate(rating)}
|
|
|
|
|
disabled={disabled}
|
|
|
|
|
className={`group relative overflow-hidden bg-gradient-to-b ${color} ${shadow} shadow-lg hover:shadow-xl text-white rounded-2xl py-4 px-2 transition-all duration-200 hover:-translate-y-1 disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:translate-y-0`}
|
2026-03-18 15:18:47 +08:00
|
|
|
aria-label={`${label} (${shortcut})`}
|
2026-03-07 05:42:32 +00:00
|
|
|
>
|
|
|
|
|
<div className="relative z-10 flex flex-col items-center">
|
|
|
|
|
<span className="text-lg font-bold">{label}</span>
|
|
|
|
|
<span className="text-xs opacity-80 mt-1">{sublabel}</span>
|
2026-03-18 15:18:47 +08:00
|
|
|
<span className="absolute top-1 right-2 text-xs opacity-50 font-mono">{shortcut}</span>
|
2026-03-07 05:42:32 +00:00
|
|
|
</div>
|
|
|
|
|
<div className="absolute inset-0 bg-white/20 opacity-0 group-hover:opacity-100 transition-opacity" />
|
|
|
|
|
</button>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|