|
|
@ -198,11 +198,11 @@ export default function TestQuestionsFlow({ |
|
|
[currentQuestion.id]: value, |
|
|
[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) { |
|
|
if (!isLastQuestion) { |
|
|
setTimeout(() => { |
|
|
setTimeout(() => { |
|
|
setCurrentIndex((prev) => Math.min(prev + 1, totalQuestions - 1)); |
|
|
setCurrentIndex((prev) => Math.min(prev + 1, totalQuestions - 1)); |
|
|
}, 250); |
|
|
|
|
|
|
|
|
}, 200); |
|
|
} |
|
|
} |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
@ -296,84 +296,102 @@ export default function TestQuestionsFlow({ |
|
|
</div> |
|
|
</div> |
|
|
</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 ( |
|
|
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={[ |
|
|
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 |
|
|
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(" ")} |
|
|
].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> |
|
|
); |
|
|
); |
|
|
})()} |
|
|
|
|
|
|
|
|
})} |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
{/* Bottom Actions Bar */} |
|
|
{/* Bottom Actions Bar */} |
|
|
|