dramaling-vocab-learning/frontend/components/flashcards/FlashcardDetailHeader.tsx

92 lines
4.1 KiB
TypeScript

import React from 'react'
import type { Flashcard } from '@/lib/services/flashcards'
import { getPartOfSpeechDisplay, getCEFRColor } from '@/lib/utils/flashcardUtils'
interface FlashcardDetailHeaderProps {
flashcard: Flashcard
isPlayingWord: boolean
isPlayingExample: boolean
onToggleWordTTS: (text: string, lang?: string) => void
}
export const FlashcardDetailHeader: React.FC<FlashcardDetailHeaderProps> = ({
flashcard,
isPlayingWord,
isPlayingExample,
onToggleWordTTS
}) => {
return (
<div className="bg-gradient-to-br from-blue-50 to-indigo-50 p-6 border-b border-blue-200">
<div className="pr-16">
<h1 className="text-4xl font-bold text-gray-900 mb-3">{flashcard.word}</h1>
<div className="flex items-center gap-3">
<span className="text-sm bg-gray-100 text-gray-700 px-3 py-1 rounded-full">
{getPartOfSpeechDisplay(flashcard.partOfSpeech)}
</span>
<span className="text-lg text-gray-600">{flashcard.pronunciation}</span>
{/* TTS播放按鈕 - 藍底漸層設計 */}
<button
onClick={() => onToggleWordTTS(flashcard.word, 'en-US')}
disabled={isPlayingExample}
title={isPlayingWord ? "點擊停止播放" : "點擊聽詞彙發音"}
aria-label={isPlayingWord ? `停止播放詞彙:${flashcard.word}` : `播放詞彙發音:${flashcard.word}`}
className={`group relative w-10 h-10 rounded-full shadow-lg transform transition-all duration-200
${isPlayingWord
? 'bg-gradient-to-br from-green-500 to-green-600 shadow-green-200 scale-105'
: 'bg-gradient-to-br from-blue-500 to-blue-600 hover:shadow-xl hover:scale-105 shadow-blue-200'
} ${isPlayingExample ? 'cursor-not-allowed opacity-50' : 'cursor-pointer'}
`}
>
{/* 播放中波紋效果 */}
{isPlayingWord && (
<div className="absolute inset-0 bg-blue-400 rounded-full animate-ping opacity-40"></div>
)}
{/* 按鈕圖標 */}
<div className="relative z-10 flex items-center justify-center w-full h-full">
{isPlayingWord ? (
<svg className="w-5 h-5 text-white" fill="currentColor" viewBox="0 0 24 24">
<path d="M6 4h4v16H6V4zm8 0h4v16h-4V4z"/>
</svg>
) : (
<svg className="w-5 h-5 text-white group-hover:scale-110 transition-transform" fill="currentColor" viewBox="0 0 24 24">
<path d="M8 5v14l11-7z"/>
</svg>
)}
</div>
{/* 懸停提示光環 */}
{!isPlayingWord && !isPlayingExample && (
<div className="absolute inset-0 rounded-full bg-white opacity-0 group-hover:opacity-20 transition-opacity duration-200"></div>
)}
</button>
</div>
</div>
{/* 學習統計 */}
<div className="grid grid-cols-3 gap-4 text-center mt-4">
<div className="bg-white bg-opacity-60 rounded-lg p-3">
<div className="text-2xl font-bold text-gray-900">{flashcard.masteryLevel || 0}%</div>
<div className="text-sm text-gray-600"></div>
</div>
<div className="bg-white bg-opacity-60 rounded-lg p-3">
<div className="text-2xl font-bold text-gray-900">{flashcard.timesReviewed || 0}</div>
<div className="text-sm text-gray-600"></div>
</div>
<div className="bg-white bg-opacity-60 rounded-lg p-3">
<div className="text-2xl font-bold text-gray-900">
{(() => {
if (!flashcard.nextReviewDate) return 0
const reviewDate = new Date(flashcard.nextReviewDate)
if (isNaN(reviewDate.getTime())) return 0
const days = Math.ceil((reviewDate.getTime() - new Date().getTime()) / (1000 * 60 * 60 * 24))
return Math.max(0, days)
})()}
</div>
<div className="text-sm text-gray-600"></div>
</div>
</div>
</div>
)
}