feat: 統一所有選擇題組件的選項布局和圖片功能

## 主要改動

### 響應式選項布局統一
- VocabChoiceTest: 改為2x2網格布局,支援響應式設計
- VocabListeningTest: 添加響應式斷點 (grid-cols-1 sm:grid-cols-2)
- SentenceListeningTest: 改為響應式2x2網格,移除選項標籤

### 圖片功能完善
- SentenceListeningTest: 新增exampleImage和onImageClick支援
- 添加完整的圖片顯示區塊和點擊處理
- review-design頁面: 為SentenceListeningTest傳遞圖片屬性

### 視覺一致性提升
- 所有選擇題組件採用相同的按鈕樣式和網格布局
- 統一文字置中對齊和font-medium字重
- 手機版自動切換為單列布局,提升觸控體驗
- 桌面版使用2x2網格,充分利用屏幕空間

### 響應式設計
- 小屏幕 (< 640px): 選項垂直單列排列
- 中等以上屏幕 (≥ 640px): 選項2x2網格排列
- 保持所有組件一致的響應式行為

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
鄭沛軒 2025-09-28 00:18:10 +08:00
parent b913d13543
commit 5a9e7f727c
4 changed files with 30 additions and 10 deletions

View File

@ -231,8 +231,10 @@ export default function ReviewTestsPage() {
exampleTranslation={mockCardData.exampleTranslation} exampleTranslation={mockCardData.exampleTranslation}
difficultyLevel={mockCardData.difficultyLevel} difficultyLevel={mockCardData.difficultyLevel}
options={vocabChoiceOptions} options={vocabChoiceOptions}
exampleImage={mockCardData.exampleImage}
onAnswer={handleAnswer} onAnswer={handleAnswer}
onReportError={handleReportError} onReportError={handleReportError}
onImageClick={handleImageClick}
/> />
)} )}

View File

@ -7,8 +7,10 @@ interface SentenceListeningTestProps {
exampleTranslation: string exampleTranslation: string
difficultyLevel: string difficultyLevel: string
options: string[] options: string[]
exampleImage?: string
onAnswer: (answer: string) => void onAnswer: (answer: string) => void
onReportError: () => void onReportError: () => void
onImageClick?: (image: string) => void
disabled?: boolean disabled?: boolean
} }
@ -18,8 +20,10 @@ export const SentenceListeningTest: React.FC<SentenceListeningTestProps> = ({
exampleTranslation, exampleTranslation,
difficultyLevel, difficultyLevel,
options, options,
exampleImage,
onAnswer, onAnswer,
onReportError, onReportError,
onImageClick,
disabled = false disabled = false
}) => { }) => {
const [selectedAnswer, setSelectedAnswer] = useState<string | null>(null) const [selectedAnswer, setSelectedAnswer] = useState<string | null>(null)
@ -70,14 +74,29 @@ export const SentenceListeningTest: React.FC<SentenceListeningTestProps> = ({
</div> </div>
</div> </div>
{/* 選項區域 - 垂直列表布局 */} {/* 圖片區(如果有) */}
<div className="grid grid-cols-1 gap-3 mb-6"> {exampleImage && (
<div className="mb-6">
<div className="bg-gray-50 rounded-lg p-4">
<h3 className="font-semibold text-gray-900 mb-2 text-left"></h3>
<img
src={exampleImage}
alt="Example illustration"
className="w-full max-w-md mx-auto rounded-lg cursor-pointer"
onClick={() => onImageClick?.(exampleImage)}
/>
</div>
</div>
)}
{/* 選項區域 - 響應式網格布局 */}
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3 mb-6">
{options.map((sentence, idx) => ( {options.map((sentence, idx) => (
<button <button
key={idx} key={idx}
onClick={() => handleAnswerSelect(sentence)} onClick={() => handleAnswerSelect(sentence)}
disabled={disabled || showResult} disabled={disabled || showResult}
className={`w-full p-4 text-left rounded-lg border-2 transition-all ${ className={`p-4 text-center rounded-lg border-2 transition-all ${
showResult showResult
? sentence === example ? sentence === example
? 'border-green-500 bg-green-50 text-green-700' ? 'border-green-500 bg-green-50 text-green-700'
@ -87,8 +106,7 @@ export const SentenceListeningTest: React.FC<SentenceListeningTestProps> = ({
: 'border-gray-200 hover:border-blue-300 hover:bg-blue-50' : 'border-gray-200 hover:border-blue-300 hover:bg-blue-50'
}`} }`}
> >
<div className="text-sm text-gray-600 mb-1"> {String.fromCharCode(65 + idx)}:</div> <div className="text-lg font-medium">{sentence}</div>
<div className="text-base">{sentence}</div>
</button> </button>
))} ))}
</div> </div>

View File

@ -72,14 +72,14 @@ export const VocabChoiceTest: React.FC<VocabChoiceTestProps> = ({
</div> </div>
</div> </div>
{/* 選項區域 */} {/* 選項區域 - 響應式網格布局 */}
<div className="space-y-3 mb-6"> <div className="grid grid-cols-1 sm:grid-cols-2 gap-3 mb-6">
{options.map((option, idx) => ( {options.map((option, idx) => (
<button <button
key={idx} key={idx}
onClick={() => handleAnswerSelect(option)} onClick={() => handleAnswerSelect(option)}
disabled={disabled || showResult} disabled={disabled || showResult}
className={`w-full p-4 text-left rounded-lg border-2 transition-all ${ className={`p-4 text-center rounded-lg border-2 transition-all ${
showResult showResult
? option === word ? option === word
? 'border-green-500 bg-green-50 text-green-700' ? 'border-green-500 bg-green-50 text-green-700'
@ -89,7 +89,7 @@ export const VocabChoiceTest: React.FC<VocabChoiceTestProps> = ({
: 'border-gray-200 hover:border-blue-300 hover:bg-blue-50' : 'border-gray-200 hover:border-blue-300 hover:bg-blue-50'
}`} }`}
> >
{option} <div className="text-lg font-medium">{option}</div>
</button> </button>
))} ))}
</div> </div>

View File

@ -72,7 +72,7 @@ export const VocabListeningTest: React.FC<VocabListeningTestProps> = ({
</div> </div>
{/* 選項區域 - 2x2網格布局 */} {/* 選項區域 - 2x2網格布局 */}
<div className="grid grid-cols-2 gap-3 mb-6"> <div className="grid grid-cols-1 sm:grid-cols-2 gap-3 mb-6">
{options.map((option) => ( {options.map((option) => (
<button <button
key={option} key={option}