feat: 发布 v0.3.0 移动端答题优化
This commit is contained in:
@@ -1,5 +1,15 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import {
|
||||
ChevronDown,
|
||||
ListChecks,
|
||||
PanelRightClose,
|
||||
PanelRightOpen,
|
||||
} from 'lucide-react';
|
||||
import { useScopedI18n } from '@/locales/client';
|
||||
import { Option } from '@/types';
|
||||
|
||||
interface Question {
|
||||
id: number;
|
||||
content: string;
|
||||
@@ -26,68 +36,89 @@ export function ProgressPanel({
|
||||
completionPercentage,
|
||||
}: ProgressPanelProps) {
|
||||
const t = useScopedI18n('component.questionnaire.test.public.progressPanel');
|
||||
const [showMobileQuestions, setShowMobileQuestions] = useState(false);
|
||||
|
||||
const questionGrid = (
|
||||
<div className="grid grid-cols-5 gap-2 overflow-y-auto sm:grid-cols-8 lg:mb-4 lg:max-h-[400px] lg:grid-cols-5 lg:gap-1">
|
||||
{questions.map((_, index) => {
|
||||
const questionNumber = index + 1;
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
key={questionNumber}
|
||||
className={`flex h-11 min-w-11 items-center justify-center rounded-md text-sm lg:h-9 lg:w-9 lg:min-w-0 lg:text-xs
|
||||
${answers[questionNumber] ? 'border-2 border-green-600 bg-green-50' : 'border bg-white'}
|
||||
${questionNumber === activePanelQuestion ? 'border-green-700 bg-green-200' : ''}
|
||||
active:bg-gray-100 lg:hover:bg-gray-100`}
|
||||
onClick={() => {
|
||||
goToQuestion(questionNumber);
|
||||
setShowMobileQuestions(false);
|
||||
}}
|
||||
title={t('jumpToQuestion', { questionNumber })}
|
||||
>
|
||||
{questionNumber}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="mb-4 lg:hidden">
|
||||
<button
|
||||
type="button"
|
||||
className="flex min-h-12 w-full items-center justify-between rounded-md border bg-muted/30 px-4 text-base"
|
||||
aria-expanded={showMobileQuestions}
|
||||
onClick={() => setShowMobileQuestions((current) => !current)}
|
||||
>
|
||||
<span className="flex items-center gap-2">
|
||||
<ListChecks className="h-5 w-5" />
|
||||
题目进度 {Object.keys(answers).length} / {questions.length}
|
||||
</span>
|
||||
<ChevronDown
|
||||
className={`h-5 w-5 transition-transform ${showMobileQuestions ? 'rotate-180' : ''}`}
|
||||
/>
|
||||
</button>
|
||||
{showMobileQuestions && (
|
||||
<div className="mt-3 border bg-muted/20 p-3">{questionGrid}</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<button
|
||||
className="fixed right-4 top-10 bg-blue-500 hover:bg-blue-600 text-white rounded-full p-2 z-40 transition-all duration-300"
|
||||
type="button"
|
||||
className="fixed right-4 top-16 z-40 hidden h-11 w-11 items-center justify-center rounded-full bg-blue-600 text-white transition-colors hover:bg-blue-700 lg:flex"
|
||||
onClick={toggleProgressPanel}
|
||||
aria-label={showProgressPanel ? t('hideNav') : t('showNav')}
|
||||
title={showProgressPanel ? t('hideNav') : t('showNav')}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={`h-6 w-6 transition-transform duration-300 ${showProgressPanel ? 'rotate-180' : ''
|
||||
}`}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M9 5l7 7-7 7"
|
||||
/>
|
||||
</svg>
|
||||
{showProgressPanel ? (
|
||||
<PanelRightClose className="h-5 w-5" />
|
||||
) : (
|
||||
<PanelRightOpen className="h-5 w-5" />
|
||||
)}
|
||||
</button>
|
||||
|
||||
<div
|
||||
className={`fixed right-4 top-20 w-64 bg-white rounded-lg shadow-lg p-4 transition-transform duration-300 transform ${showProgressPanel ? 'translate-x-0' : 'translate-x-full'
|
||||
}`}
|
||||
className={`fixed right-4 top-28 z-30 hidden w-64 rounded-lg border bg-white p-4 shadow-lg transition-transform duration-300 lg:block ${
|
||||
showProgressPanel
|
||||
? 'translate-x-0'
|
||||
: 'translate-x-[calc(100%+2rem)]'
|
||||
}`}
|
||||
>
|
||||
<div className="mb-4">
|
||||
<div className="text-sm font-medium mb-2">
|
||||
<div className="mb-2 text-sm font-medium">
|
||||
{t('completionProgress')}
|
||||
{Math.round(completionPercentage)}%
|
||||
</div>
|
||||
<div className="w-full bg-gray-200 rounded-full h-2">
|
||||
<div className="h-2 w-full rounded-full bg-gray-200">
|
||||
<div
|
||||
className="bg-blue-500 rounded-full h-2 transition-all duration-300"
|
||||
className="h-2 rounded-full bg-blue-500 transition-all duration-300"
|
||||
style={{ width: `${completionPercentage}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-5 gap-1 mb-4 max-h-[400px] overflow-y-auto">
|
||||
{questions.map((_, i) => (
|
||||
<button
|
||||
key={i}
|
||||
className={`w-9 h-9 flex items-center justify-center rounded-md text-xs
|
||||
${answers[i + 1]
|
||||
? 'bg-green-100 border-green-500 border-2'
|
||||
: 'border'
|
||||
}
|
||||
${i + 1 === activePanelQuestion
|
||||
? 'bg-green-300 border-green-600'
|
||||
: ''
|
||||
}
|
||||
hover:bg-gray-100 transition-colors duration-200`}
|
||||
onClick={() => goToQuestion(i + 1)}
|
||||
title={t('jumpToQuestion', { questionNumber: i + 1 })}
|
||||
>
|
||||
{i + 1}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
{questionGrid}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user