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:
parent
b913d13543
commit
5a9e7f727c
|
|
@ -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}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue