dramaling-vocab-learning/frontend/components/word/WordPopup.tsx

135 lines
5.0 KiB
TypeScript

import React from 'react'
import { Modal } from '@/components/ui/Modal'
import { getCEFRColor, getPartOfSpeechDisplay } from '@/lib/utils/flashcardUtils'
import { BluePlayButton } from '@/components/shared/BluePlayButton'
import { useWordAnalysis } from '@/hooks/word/useWordAnalysis'
import type { WordAnalysis } from '@/lib/types/word'
interface WordPopupProps {
selectedWord: string | null
analysis: Record<string, WordAnalysis>
isOpen: boolean
onClose: () => void
onSaveWord?: (word: string, analysis: WordAnalysis) => Promise<{ success: boolean; error?: string }>
isSaving?: boolean
}
export const WordPopup: React.FC<WordPopupProps> = ({
selectedWord,
analysis,
isOpen,
onClose,
onSaveWord,
isSaving = false
}) => {
const { getWordProperty } = useWordAnalysis()
if (!selectedWord || !analysis?.[selectedWord]) {
return null
}
const wordAnalysis = analysis[selectedWord]
const handleSaveWord = async () => {
if (onSaveWord) {
await onSaveWord(selectedWord, wordAnalysis)
}
}
return (
<Modal isOpen={isOpen} onClose={onClose} size="md">
{/* Header */}
<div className="bg-gradient-to-br from-blue-50 to-indigo-50 p-5 border-b border-blue-200">
<div className="mb-3">
<h3 className="text-2xl font-bold text-gray-900">{getWordProperty(wordAnalysis, 'word')}</h3>
</div>
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<span className="text-sm bg-gray-100 text-gray-700 px-3 py-1 rounded-full">
{getPartOfSpeechDisplay(getWordProperty(wordAnalysis, 'partOfSpeech'))}
</span>
<div className="flex items-center gap-2">
<span className="text-base text-gray-600">
{getWordProperty(wordAnalysis, 'pronunciation')}
</span>
<BluePlayButton
text={getWordProperty(wordAnalysis, 'word') || selectedWord}
lang="en-US"
size="sm"
title="播放發音"
/>
</div>
</div>
<span className={`px-3 py-1 rounded-full text-sm font-medium border ${getCEFRColor(getWordProperty(wordAnalysis, 'cefr'))}`}>
{getWordProperty(wordAnalysis, 'cefr')}
</span>
</div>
</div>
{/* Content */}
<div className="p-4 space-y-4 max-h-96 overflow-y-auto">
{/* Translation */}
<div className="bg-green-50 rounded-lg p-3 border border-green-200">
<h4 className="font-semibold text-green-900 mb-2 text-left text-sm"></h4>
<p className="text-green-800 font-medium text-left">
{getWordProperty(wordAnalysis, 'translation')}
</p>
</div>
{/* Definition */}
<div className="bg-gray-50 rounded-lg p-3 border border-gray-200">
<h4 className="font-semibold text-gray-900 mb-2 text-left text-sm"></h4>
<p className="text-gray-700 text-left leading-relaxed">
{getWordProperty(wordAnalysis, 'definition')}
</p>
</div>
{/* Example */}
{getWordProperty(wordAnalysis, 'example') && (
<div className="bg-blue-50 rounded-lg p-3 border border-blue-200">
<h4 className="font-semibold text-blue-900 mb-2 text-left text-sm"></h4>
<div className="space-y-2">
<p className="text-blue-800 text-left italic">
"{getWordProperty(wordAnalysis, 'example')}"
</p>
<p className="text-blue-700 text-left">
{getWordProperty(wordAnalysis, 'exampleTranslation')}
</p>
</div>
</div>
)}
{/* Synonyms */}
{getWordProperty(wordAnalysis, 'synonyms')?.length > 0 && (
<div className="bg-purple-50 rounded-lg p-3 border border-purple-200">
<h4 className="font-semibold text-purple-900 mb-2 text-left text-sm"></h4>
<div className="flex flex-wrap gap-2">
{getWordProperty(wordAnalysis, 'synonyms')?.map((synonym: string, index: number) => (
<span
key={index}
className="bg-purple-100 text-purple-700 px-2 py-1 rounded-full text-xs font-medium"
>
{synonym}
</span>
))}
</div>
</div>
)}
</div>
{/* Save Button */}
{onSaveWord && (
<div className="p-4 pt-2">
<button
onClick={handleSaveWord}
disabled={isSaving}
className="w-full bg-primary text-white py-3 rounded-lg font-medium hover:bg-primary-hover transition-colors flex items-center justify-center gap-2 disabled:opacity-50"
>
<span className="font-medium">{isSaving ? '保存中...' : '保存到詞卡'}</span>
</button>
</div>
)}
</Modal>
)
}