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}
difficultyLevel={mockCardData.difficultyLevel}
options={vocabChoiceOptions}
exampleImage={mockCardData.exampleImage}
onAnswer={handleAnswer}
onReportError={handleReportError}
onImageClick={handleImageClick}
/>
)}

View File

@ -7,8 +7,10 @@ interface SentenceListeningTestProps {
exampleTranslation: string
difficultyLevel: string
options: string[]
exampleImage?: string
onAnswer: (answer: string) => void
onReportError: () => void
onImageClick?: (image: string) => void
disabled?: boolean
}
@ -18,8 +20,10 @@ export const SentenceListeningTest: React.FC<SentenceListeningTestProps> = ({
exampleTranslation,
difficultyLevel,
options,
exampleImage,
onAnswer,
onReportError,
onImageClick,
disabled = false
}) => {
const [selectedAnswer, setSelectedAnswer] = useState<string | null>(null)
@ -70,14 +74,29 @@ export const SentenceListeningTest: React.FC<SentenceListeningTestProps> = ({
</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) => (
<button
key={idx}
onClick={() => handleAnswerSelect(sentence)}
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
? sentence === example
? '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'
}`}
>
<div className="text-sm text-gray-600 mb-1"> {String.fromCharCode(65 + idx)}:</div>
<div className="text-base">{sentence}</div>
<div className="text-lg font-medium">{sentence}</div>
</button>
))}
</div>

View File

@ -72,14 +72,14 @@ export const VocabChoiceTest: React.FC<VocabChoiceTestProps> = ({
</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) => (
<button
key={idx}
onClick={() => handleAnswerSelect(option)}
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
? option === word
? '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'
}`}
>
{option}
<div className="text-lg font-medium">{option}</div>
</button>
))}
</div>

View File

@ -72,7 +72,7 @@ export const VocabListeningTest: React.FC<VocabListeningTestProps> = ({
</div>
{/* 選項區域 - 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) => (
<button
key={option}