234 lines
5.3 KiB
TypeScript
234 lines
5.3 KiB
TypeScript
import React, { ReactNode } from 'react'
|
||
import { BaseTestComponent, BaseTestComponentProps } from './BaseTestComponent'
|
||
|
||
/**
|
||
* 測驗容器元件 - 提供統一的測驗布局結構
|
||
* 整合導航控制和進度顯示
|
||
*/
|
||
|
||
export interface TestContainerProps extends Omit<BaseTestComponentProps, 'children'> {
|
||
// 測驗內容區域
|
||
contentArea: ReactNode
|
||
|
||
// 答題區域
|
||
answerArea?: ReactNode
|
||
|
||
// 結果顯示區域
|
||
resultArea?: ReactNode
|
||
|
||
// 導航控制(預留給未來的NavigationController)
|
||
navigationArea?: ReactNode
|
||
|
||
// 布局配置
|
||
layout?: 'standard' | 'split' | 'fullscreen'
|
||
|
||
// 自定義樣式
|
||
contentClassName?: string
|
||
answerClassName?: string
|
||
resultClassName?: string
|
||
}
|
||
|
||
export const TestContainer: React.FC<TestContainerProps> = ({
|
||
contentArea,
|
||
answerArea,
|
||
resultArea,
|
||
navigationArea,
|
||
layout = 'standard',
|
||
contentClassName = '',
|
||
answerClassName = '',
|
||
resultClassName = '',
|
||
...baseProps
|
||
}) => {
|
||
const getLayoutClasses = () => {
|
||
switch (layout) {
|
||
case 'split':
|
||
return 'lg:grid lg:grid-cols-2 lg:gap-8'
|
||
case 'fullscreen':
|
||
return 'min-h-screen flex flex-col'
|
||
default:
|
||
return 'space-y-6'
|
||
}
|
||
}
|
||
|
||
const renderContent = () => (
|
||
<div className={getLayoutClasses()}>
|
||
{/* 內容展示區域 */}
|
||
<div className={`test-content-area ${contentClassName}`}>
|
||
{contentArea}
|
||
</div>
|
||
|
||
{/* 答題互動區域 */}
|
||
{answerArea && (
|
||
<div className={`test-answer-area ${answerClassName}`}>
|
||
{answerArea}
|
||
</div>
|
||
)}
|
||
|
||
{/* 結果展示區域 */}
|
||
{resultArea && (
|
||
<div className={`test-result-area mt-6 ${resultClassName}`}>
|
||
{resultArea}
|
||
</div>
|
||
)}
|
||
|
||
{/* 導航控制區域 */}
|
||
{navigationArea && (
|
||
<div className="test-navigation-area mt-8">
|
||
{navigationArea}
|
||
</div>
|
||
)}
|
||
</div>
|
||
)
|
||
|
||
return (
|
||
<BaseTestComponent {...baseProps}>
|
||
{renderContent()}
|
||
</BaseTestComponent>
|
||
)
|
||
}
|
||
|
||
/**
|
||
* 專用於不同測驗類型的容器變體
|
||
*/
|
||
|
||
// 選擇題容器
|
||
export interface ChoiceTestContainerProps extends Omit<TestContainerProps, 'layout'> {
|
||
questionArea: ReactNode
|
||
optionsArea: ReactNode
|
||
}
|
||
|
||
export const ChoiceTestContainer: React.FC<ChoiceTestContainerProps> = ({
|
||
questionArea,
|
||
optionsArea,
|
||
resultArea,
|
||
navigationArea,
|
||
...props
|
||
}) => {
|
||
return (
|
||
<TestContainer
|
||
{...props}
|
||
layout="standard"
|
||
contentArea={
|
||
<div className="space-y-6">
|
||
<div className="question-area">
|
||
{questionArea}
|
||
</div>
|
||
<div className="options-area">
|
||
{optionsArea}
|
||
</div>
|
||
</div>
|
||
}
|
||
resultArea={resultArea}
|
||
navigationArea={navigationArea}
|
||
/>
|
||
)
|
||
}
|
||
|
||
// 填空題容器
|
||
export interface FillTestContainerProps extends Omit<TestContainerProps, 'layout'> {
|
||
sentenceArea: ReactNode
|
||
inputArea: ReactNode
|
||
}
|
||
|
||
export const FillTestContainer: React.FC<FillTestContainerProps> = ({
|
||
sentenceArea,
|
||
inputArea,
|
||
resultArea,
|
||
navigationArea,
|
||
...props
|
||
}) => {
|
||
return (
|
||
<TestContainer
|
||
{...props}
|
||
layout="standard"
|
||
contentArea={sentenceArea}
|
||
answerArea={inputArea}
|
||
resultArea={resultArea}
|
||
navigationArea={navigationArea}
|
||
/>
|
||
)
|
||
}
|
||
|
||
// 聽力測驗容器
|
||
export interface ListeningTestContainerProps extends Omit<TestContainerProps, 'layout'> {
|
||
audioArea: ReactNode
|
||
questionArea: ReactNode
|
||
answerArea: ReactNode
|
||
}
|
||
|
||
export const ListeningTestContainer: React.FC<ListeningTestContainerProps> = ({
|
||
audioArea,
|
||
questionArea,
|
||
answerArea,
|
||
resultArea,
|
||
navigationArea,
|
||
...props
|
||
}) => {
|
||
return (
|
||
<TestContainer
|
||
{...props}
|
||
layout="standard"
|
||
contentArea={
|
||
<div className="space-y-6">
|
||
<div className="audio-area text-center">
|
||
{audioArea}
|
||
</div>
|
||
<div className="question-area">
|
||
{questionArea}
|
||
</div>
|
||
</div>
|
||
}
|
||
answerArea={answerArea}
|
||
resultArea={resultArea}
|
||
navigationArea={navigationArea}
|
||
/>
|
||
)
|
||
}
|
||
|
||
// 口說測驗容器
|
||
export interface SpeakingTestContainerProps extends Omit<TestContainerProps, 'layout'> {
|
||
promptArea: ReactNode
|
||
recordingArea: ReactNode
|
||
}
|
||
|
||
export const SpeakingTestContainer: React.FC<SpeakingTestContainerProps> = ({
|
||
promptArea,
|
||
recordingArea,
|
||
resultArea,
|
||
navigationArea,
|
||
...props
|
||
}) => {
|
||
return (
|
||
<TestContainer
|
||
{...props}
|
||
layout="standard"
|
||
contentArea={promptArea}
|
||
answerArea={recordingArea}
|
||
resultArea={resultArea}
|
||
navigationArea={navigationArea}
|
||
/>
|
||
)
|
||
}
|
||
|
||
// 翻卡測驗容器
|
||
export interface FlipTestContainerProps extends Omit<TestContainerProps, 'layout'> {
|
||
cardArea: ReactNode
|
||
confidenceArea: ReactNode
|
||
}
|
||
|
||
export const FlipTestContainer: React.FC<FlipTestContainerProps> = ({
|
||
cardArea,
|
||
confidenceArea,
|
||
navigationArea,
|
||
...props
|
||
}) => {
|
||
return (
|
||
<TestContainer
|
||
{...props}
|
||
layout="standard"
|
||
contentArea={cardArea}
|
||
answerArea={confidenceArea}
|
||
navigationArea={navigationArea}
|
||
/>
|
||
)
|
||
} |