dramaling-vocab-learning/frontend/app/review-simple/components/SimpleFlipCard.tsx

208 lines
7.8 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 { useState, useRef, useEffect, useCallback } from 'react'
import { SimpleCard, CONFIDENCE_LEVELS } from '../data'
import { SimpleTestHeader } from './SimpleTestHeader'
interface SimpleFlipCardProps {
card: SimpleCard
onAnswer: (confidence: number) => void
}
export function SimpleFlipCard({ card, onAnswer }: SimpleFlipCardProps) {
const [isFlipped, setIsFlipped] = useState(false)
const [selectedConfidence, setSelectedConfidence] = useState<number | null>(null)
const [cardHeight, setCardHeight] = useState<number>(400)
const frontRef = useRef<HTMLDivElement>(null)
const backRef = useRef<HTMLDivElement>(null)
// 智能高度計算 (復用原有邏輯)
useEffect(() => {
const updateCardHeight = () => {
if (backRef.current) {
const backHeight = backRef.current.scrollHeight
// 響應式最小高度設定 (復用原有響應式邏輯)
const minHeightByScreen = window.innerWidth <= 480 ? 300 :
window.innerWidth <= 768 ? 350 : 400
const finalHeight = Math.max(minHeightByScreen, backHeight)
setCardHeight(finalHeight)
}
}
const timer = setTimeout(updateCardHeight, 100)
window.addEventListener('resize', updateCardHeight)
return () => {
clearTimeout(timer)
window.removeEventListener('resize', updateCardHeight)
}
}, [card.word, card.definition, card.example])
const handleFlip = useCallback(() => {
setIsFlipped(!isFlipped)
}, [isFlipped])
const handleConfidenceSelect = useCallback((level: number) => {
setSelectedConfidence(level)
}, [])
const handleSubmit = () => {
if (selectedConfidence) {
onAnswer(selectedConfidence)
// 重置狀態為下一張卡片準備
setIsFlipped(false)
setSelectedConfidence(null)
}
}
const hasAnswered = selectedConfidence !== null
return (
<div className="w-full">
{/* 完全復用原FlipMemoryTest的精確設計 */}
<div
className={`relative w-full cursor-pointer`}
onClick={handleFlip}
style={{
perspective: '1000px',
height: `${cardHeight}px`,
minHeight: '400px',
transition: 'height 0.4s cubic-bezier(0.4, 0, 0.2, 1)'
}}
>
<div
style={{
position: 'relative',
width: '100%',
height: '100%',
transformStyle: 'preserve-3d',
transition: 'transform 0.6s cubic-bezier(0.4, 0, 0.2, 1)',
transform: isFlipped ? 'rotateY(180deg)' : 'rotateY(0deg)'
}}
>
{/* 正面 - 完全復用原設計 */}
<div
ref={frontRef}
className="absolute w-full h-full bg-white rounded-xl shadow-lg hover:shadow-xl"
style={{ backfaceVisibility: 'hidden' }}
>
<div className="p-8 h-full">
<SimpleTestHeader
title="翻卡記憶"
cefr="A1"
/>
<div className="space-y-4">
<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">{card.word}</h3>
<div className="flex items-center justify-center gap-3">
{card.pronunciation && (
<span className="text-lg text-gray-500">{card.pronunciation}</span>
)}
</div>
</div>
</div>
<p className="text-lg text-gray-700 mb-6 text-right">
</p>
</div>
</div>
</div>
{/* 背面 - 完全復用原設計 */}
<div
ref={backRef}
className="absolute w-full h-full bg-white rounded-xl shadow-lg"
style={{
backfaceVisibility: 'hidden',
transform: 'rotateY(180deg)'
}}
>
<div className="p-8 h-full">
<SimpleTestHeader
title="翻卡記憶"
cefr="A1"
/>
<div className="space-y-4 pb-6">
{/* 定義區塊 - 完全復用原樣式 */}
<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">{card.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">"{card.example}"</p>
</div>
<p className="text-gray-600 text-sm text-left">"{card.translation}"</p>
</div>
</div>
</div>
</div>
</div>
</div>
{/* 信心等級評估區 - 完全復用原ConfidenceButtons設計 */}
{isFlipped && (
<div className="mt-6">
<div className="space-y-3">
<h3 className="text-lg font-semibold text-gray-900 mb-4 text-left">
</h3>
<div className="grid grid-cols-5 gap-3">
{[
{ level: 1, label: '完全不懂', color: 'bg-red-100 text-red-700 border-red-200 hover:bg-red-200' },
{ level: 2, label: '模糊', color: 'bg-orange-100 text-orange-700 border-orange-200 hover:bg-orange-200' },
{ level: 3, label: '一般', color: 'bg-yellow-100 text-yellow-700 border-yellow-200 hover:bg-yellow-200' },
{ level: 4, label: '熟悉', color: 'bg-blue-100 text-blue-700 border-blue-200 hover:bg-blue-200' },
{ level: 5, label: '非常熟悉', color: 'bg-green-100 text-green-700 border-green-200 hover:bg-green-200' }
].map(({ level, label, color }) => {
const isSelected = selectedConfidence === level
return (
<button
key={level}
onClick={(e) => {
e.stopPropagation()
handleConfidenceSelect(level)
}}
className={`
px-3 py-2 rounded-lg border-2 text-center font-medium transition-all duration-200
${isSelected
? 'ring-2 ring-blue-400 ring-opacity-75 transform scale-105'
: 'cursor-pointer active:scale-95'
}
${color}
`}
>
<div className="flex items-center justify-center h-8">
<span className="text-sm">{label}</span>
</div>
</button>
)
})}
</div>
{/* 提交按鈕 - 選擇後顯示 */}
{hasAnswered && (
<button
onClick={(e) => {
e.stopPropagation()
handleSubmit()
}}
className="w-full bg-blue-600 text-white py-3 px-6 rounded-lg font-semibold hover:bg-blue-700 transition-colors mt-4"
>
</button>
)}
</div>
</div>
)}
</div>
)
}