Browse Source

feat(assessments): add native-like smooth horizontal slide transition to test questions

master
mortezaei 2 days ago
parent
commit
e6ef2291b1
  1. 158
      src/components/Componentes/test-questions-flow.tsx

158
src/components/Componentes/test-questions-flow.tsx

@ -198,11 +198,11 @@ export default function TestQuestionsFlow({
[currentQuestion.id]: value,
}));
// Auto advance to next question if not on last question
// Auto advance to next question if not on last question with slide animation
if (!isLastQuestion) {
setTimeout(() => {
setCurrentIndex((prev) => Math.min(prev + 1, totalQuestions - 1));
}, 250);
}, 200);
}
};
@ -296,84 +296,102 @@ export default function TestQuestionsFlow({
</div>
</div>
{/* Question Section */}
<div className="flex-1 flex flex-col justify-start overflow-y-auto pt-6 pb-4">
{/* Question Title */}
<h2
className="group-16 font-bold text-[#1B1B1B] leading-[1.45] text-center px-2 mb-8 flex items-center justify-center"
style={{ minHeight: "5.8em" }}
>
<span className="w-full">{currentQuestion.text}</span>
</h2>
{/* Question Slider Viewport */}
<div className="relative flex-1 w-full overflow-hidden min-h-0">
{questions.map((q, index) => {
const offset = index - currentIndex;
const isNearby = Math.abs(offset) <= 1;
if (!isNearby) return null;
{/* Answer Options Stack */}
{(() => {
const options = currentQuestion.options || [];
const qSelectedValue = answers[q.id];
const options = q.options || [];
return (
<div className="flex flex-col gap-3.5 pt-1">
{options.map((option) => {
const isSelected = selectedValue === option.value;
return (
<button
key={`${currentQuestion.id}-${String(option.value)}`}
type="button"
onClick={() => handleOptionSelect(option.value)}
className={[
"cursor-pointer rounded-[16px] font-semibold text-[15px] transition-all duration-200 active:scale-[0.99] flex items-center gap-3",
"w-full px-5 py-4 text-start leading-snug",
isSelected
? "bg-[#F0445B] text-white shadow-[0_8px_20px_rgba(240,68,91,0.25)]"
: "bg-[#FAFAFA] text-[#181818] hover:bg-white shadow-[0_2px_6px_rgba(0,0,0,0.03)] border border-[#F0EDED]",
].join(" ")}
>
<div
<div
key={q.id}
aria-hidden={offset !== 0}
className={[
"absolute inset-0 flex flex-col justify-start overflow-y-auto pt-6 pb-4 transition-transform duration-250 ease-[cubic-bezier(0.25,1,0.5,1)]",
offset === 0 ? "pointer-events-auto" : "pointer-events-none",
].join(" ")}
style={{
transform: `translate3d(${offset * 100}%, 0, 0)`,
}}
>
{/* Question Title */}
<h2
className="group-16 font-bold text-[#1B1B1B] leading-[1.45] text-center px-2 mb-8 flex items-center justify-center shrink-0"
style={{ minHeight: "5.8em" }}
>
<span className="w-full">{q.text}</span>
</h2>
{/* Answer Options Stack */}
<div className="flex flex-col gap-3.5 pt-1">
{options.map((option) => {
const isSelected = qSelectedValue === option.value;
return (
<button
key={`${q.id}-${String(option.value)}`}
type="button"
onClick={() => handleOptionSelect(option.value)}
className={[
"size-[18px] shrink-0 rounded-full border-[2px] transition-all flex items-center justify-center",
"cursor-pointer rounded-[16px] font-semibold text-[15px] transition-all duration-200 active:scale-[0.99] flex items-center gap-3",
"w-full px-5 py-4 text-start leading-snug",
isSelected
? "border-white bg-white text-[#F0445B]"
: "border-[#D0D5DD] bg-white text-transparent",
? "bg-[#F0445B] text-white shadow-[0_8px_20px_rgba(240,68,91,0.25)]"
: "bg-[#FAFAFA] text-[#181818] hover:bg-white shadow-[0_2px_6px_rgba(0,0,0,0.03)] border border-[#F0EDED]",
].join(" ")}
>
{isSelected && (
<div className="size-[8px] rounded-full bg-current" />
<div
className={[
"size-[18px] shrink-0 rounded-full border-[2px] transition-all flex items-center justify-center",
isSelected
? "border-white bg-white text-[#F0445B]"
: "border-[#D0D5DD] bg-white text-transparent",
].join(" ")}
>
{isSelected && (
<div className="size-[8px] rounded-full bg-current" />
)}
</div>
{option.label.includes(" - ") ? (
(() => {
const parts = option.label.split(" - ");
const title = formatOptionLabel(parts[0]);
const description = formatOptionLabel(
parts.slice(1).join(" - "),
);
return (
<span className="flex flex-col gap-1 text-start flex-1">
<span className="font-bold">{title}</span>
<ExplanationUiFont
textColor={
isSelected
? "text-white/85"
: "text-[#667085]"
}
>
{description}
</ExplanationUiFont>
</span>
);
})()
) : (
<span className="flex-1">
{formatOptionLabel(option.label)}
</span>
)}
</div>
{option.label.includes(" - ") ? (
(() => {
const parts = option.label.split(" - ");
const title = formatOptionLabel(parts[0]);
const description = formatOptionLabel(
parts.slice(1).join(" - "),
);
return (
<span className="flex flex-col gap-1 text-start flex-1">
<span className="font-bold">{title}</span>
<ExplanationUiFont
textColor={
isSelected
? "text-white/85"
: "text-[#667085]"
}
>
{description}
</ExplanationUiFont>
</span>
);
})()
) : (
<span className="flex-1">
{formatOptionLabel(option.label)}
</span>
)}
</button>
);
})}
</button>
);
})}
</div>
</div>
);
})()}
})}
</div>
{/* Bottom Actions Bar */}

Loading…
Cancel
Save