209 lines
7.6 KiB
TypeScript
209 lines
7.6 KiB
TypeScript
import { useState } from 'react'
|
||
import AudioPlayer from '@/components/AudioPlayer'
|
||
|
||
interface FlipMemoryTestProps {
|
||
word: string
|
||
definition: string
|
||
example: string
|
||
exampleTranslation: string
|
||
pronunciation?: string
|
||
synonyms?: string[]
|
||
difficultyLevel: string
|
||
onConfidenceSubmit: (level: number) => void
|
||
onReportError: () => void
|
||
disabled?: boolean
|
||
}
|
||
|
||
export const FlipMemoryTest: React.FC<FlipMemoryTestProps> = ({
|
||
word,
|
||
definition,
|
||
example,
|
||
exampleTranslation,
|
||
pronunciation,
|
||
synonyms = [],
|
||
difficultyLevel,
|
||
onConfidenceSubmit,
|
||
onReportError,
|
||
disabled = false
|
||
}) => {
|
||
const [isFlipped, setIsFlipped] = useState(false)
|
||
const [selectedConfidence, setSelectedConfidence] = useState<number | null>(null)
|
||
|
||
const handleFlip = () => {
|
||
if (!disabled) setIsFlipped(!isFlipped)
|
||
}
|
||
|
||
const handleConfidenceSelect = (level: number) => {
|
||
if (disabled) return
|
||
setSelectedConfidence(level)
|
||
onConfidenceSubmit(level)
|
||
}
|
||
|
||
const confidenceLabels = {
|
||
1: '完全不懂',
|
||
2: '模糊',
|
||
3: '一般',
|
||
4: '熟悉',
|
||
5: '非常熟悉'
|
||
}
|
||
|
||
return (
|
||
<div className="relative">
|
||
{/* 錯誤回報按鈕 */}
|
||
<div className="flex justify-end mb-2">
|
||
<button
|
||
onClick={onReportError}
|
||
className="px-3 py-2 rounded-md transition-colors text-gray-600 hover:text-gray-900"
|
||
>
|
||
🚩 回報錯誤
|
||
</button>
|
||
</div>
|
||
|
||
{/* 翻卡容器 */}
|
||
<div
|
||
className={`card-container ${disabled ? 'pointer-events-none opacity-75' : 'cursor-pointer'}`}
|
||
onClick={handleFlip}
|
||
style={{ perspective: '1000px', minHeight: '400px' }}
|
||
>
|
||
<div
|
||
className={`card transition-transform duration-600 ${isFlipped ? 'rotate-y-180' : ''}`}
|
||
style={{ transformStyle: 'preserve-3d' }}
|
||
>
|
||
{/* 正面 */}
|
||
<div
|
||
className="card-face card-front absolute w-full h-full"
|
||
style={{ backfaceVisibility: 'hidden' }}
|
||
>
|
||
<div className="bg-white rounded-xl shadow-lg p-8 h-full hover:shadow-xl transition-shadow">
|
||
<div className="flex justify-between items-start mb-6">
|
||
<h2 className="text-2xl font-bold text-gray-900">翻卡記憶</h2>
|
||
<span className="bg-blue-100 text-blue-800 text-xs px-2 py-1 rounded-full">
|
||
{difficultyLevel}
|
||
</span>
|
||
</div>
|
||
|
||
<div className="space-y-4">
|
||
<p className="text-lg text-gray-700 mb-6 text-left">
|
||
點擊卡片翻面,根據你對單字的熟悉程度進行自我評估:
|
||
</p>
|
||
|
||
<div className="flex-1 flex items-center justify-center mt-6">
|
||
<div className="bg-gray-50 rounded-lg p-8 w-full text-center">
|
||
<h3 className="text-4xl font-bold text-gray-900 mb-6">{word}</h3>
|
||
<div className="flex items-center justify-center gap-3">
|
||
{pronunciation && (
|
||
<span className="text-lg text-gray-500">{pronunciation}</span>
|
||
)}
|
||
<AudioPlayer text={word} />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 背面 */}
|
||
<div
|
||
className="card-face card-back absolute w-full h-full"
|
||
style={{ backfaceVisibility: 'hidden', transform: 'rotateY(180deg)' }}
|
||
>
|
||
<div className="bg-white rounded-xl shadow-lg p-8 h-full">
|
||
<div className="flex justify-between items-start mb-6">
|
||
<h2 className="text-2xl font-bold text-gray-900">翻卡記憶</h2>
|
||
<span className="bg-blue-100 text-blue-800 text-xs px-2 py-1 rounded-full">
|
||
{difficultyLevel}
|
||
</span>
|
||
</div>
|
||
|
||
<div className="space-y-4">
|
||
{/* 定義區塊 */}
|
||
<div className="bg-gray-50 rounded-lg p-4">
|
||
<h3 className="font-semibold text-gray-900 mb-2 text-left">定義</h3>
|
||
<p className="text-gray-700 text-left">{definition}</p>
|
||
</div>
|
||
|
||
{/* 例句區塊 */}
|
||
<div className="bg-gray-50 rounded-lg p-4">
|
||
<h3 className="font-semibold text-gray-900 mb-2 text-left">例句</h3>
|
||
<div className="relative">
|
||
<p className="text-gray-700 italic mb-2 text-left pr-12">"{example}"</p>
|
||
<div className="absolute bottom-0 right-0">
|
||
<AudioPlayer text={example} />
|
||
</div>
|
||
</div>
|
||
<p className="text-gray-600 text-sm text-left">"{exampleTranslation}"</p>
|
||
</div>
|
||
|
||
{/* 同義詞區塊 */}
|
||
{synonyms.length > 0 && (
|
||
<div className="bg-gray-50 rounded-lg p-4">
|
||
<h3 className="font-semibold text-gray-900 mb-2 text-left">同義詞</h3>
|
||
<div className="flex flex-wrap gap-2">
|
||
{synonyms.map((synonym, index) => (
|
||
<span
|
||
key={index}
|
||
className="bg-white text-gray-700 px-3 py-1 rounded-full text-sm border border-gray-200"
|
||
>
|
||
{synonym}
|
||
</span>
|
||
))}
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* 信心等級評估區 */}
|
||
<div className="bg-blue-50 rounded-lg p-4">
|
||
<h3 className="font-semibold text-blue-900 mb-3 text-left">你對這個單字的熟悉程度:</h3>
|
||
<div className="grid grid-cols-5 gap-2">
|
||
{[1, 2, 3, 4, 5].map(level => (
|
||
<button
|
||
key={level}
|
||
onClick={() => handleConfidenceSelect(level)}
|
||
disabled={disabled || selectedConfidence !== null}
|
||
className={`py-3 px-4 bg-white border border-blue-200 text-blue-700 rounded-lg transition-all text-center ${
|
||
selectedConfidence === level
|
||
? 'bg-blue-500 text-white border-blue-500'
|
||
: 'hover:bg-blue-100 hover:border-blue-300'
|
||
} ${disabled || selectedConfidence !== null ? 'opacity-50 cursor-not-allowed' : ''}`}
|
||
>
|
||
<div className="font-bold text-lg">{level}</div>
|
||
<div className="text-xs">
|
||
{confidenceLabels[level as keyof typeof confidenceLabels]}
|
||
</div>
|
||
</button>
|
||
))}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<style jsx>{`
|
||
.card-container {
|
||
perspective: 1000px;
|
||
transition: height 0.3s ease;
|
||
}
|
||
|
||
.card {
|
||
position: relative;
|
||
width: 100%;
|
||
height: 100%;
|
||
transform-style: preserve-3d;
|
||
}
|
||
|
||
.rotate-y-180 {
|
||
transform: rotateY(180deg);
|
||
}
|
||
|
||
.card-face {
|
||
position: absolute;
|
||
width: 100%;
|
||
height: 100%;
|
||
backface-visibility: hidden;
|
||
}
|
||
`}</style>
|
||
</div>
|
||
)
|
||
} |