120 lines
4.0 KiB
TypeScript
120 lines
4.0 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useRef } from "react";
|
|
|
|
import KatexRenderer from "@/components/math/KatexRenderer";
|
|
import type { VariableTermChoice } from "@/lib/engine/variable-types";
|
|
|
|
interface VariableChoiceAnswerFormProps {
|
|
choices: [VariableTermChoice[], VariableTermChoice[]];
|
|
selectedChoiceIds: [string | null, string | null];
|
|
activeTermIndex: 0 | 1;
|
|
pendingChoiceLabels: [string, string];
|
|
onSelectChoice: (termIndex: 0 | 1, choiceId: string) => void;
|
|
}
|
|
|
|
export default function VariableChoiceAnswerForm({
|
|
choices,
|
|
selectedChoiceIds,
|
|
activeTermIndex,
|
|
pendingChoiceLabels,
|
|
onSelectChoice,
|
|
}: VariableChoiceAnswerFormProps) {
|
|
const firstChoiceRef = useRef<HTMLButtonElement>(null);
|
|
const activeChoices = [...choices[activeTermIndex]].sort(
|
|
(a, b) => a.coefficient - b.coefficient,
|
|
);
|
|
|
|
useEffect(() => {
|
|
firstChoiceRef.current?.focus();
|
|
}, [activeTermIndex]);
|
|
|
|
function getSelectedChoice(index: 0 | 1) {
|
|
const selectedChoiceId = selectedChoiceIds[index];
|
|
|
|
if (!selectedChoiceId) return null;
|
|
|
|
return choices[index].find((choice) => choice.id === selectedChoiceId) ?? null;
|
|
}
|
|
|
|
return (
|
|
<div className="rounded-md bg-white p-6 shadow-sm ring-1 ring-slate-200">
|
|
<div className="flex flex-wrap items-center justify-between gap-3">
|
|
<div>
|
|
<p className="text-sm font-semibold text-slate-500">
|
|
{activeTermIndex === 0 ? "첫째 항" : "둘째 항"}을 고르세요.
|
|
</p>
|
|
</div>
|
|
<ol
|
|
className="flex min-w-40 items-center gap-1"
|
|
aria-label="입력 항 진행 상태"
|
|
>
|
|
{selectedChoiceIds.map((choiceId, index) => {
|
|
const selected = activeTermIndex === index;
|
|
const selectedChoice = getSelectedChoice(index as 0 | 1);
|
|
const completed = choiceId !== null && selectedChoice !== null;
|
|
|
|
return (
|
|
<li
|
|
key={index}
|
|
className="min-w-0 flex-1"
|
|
aria-label={`${index + 1}항 ${
|
|
completed ? "선택 완료" : "미선택"
|
|
}${selected ? ", 현재 선택 중" : ""}`}
|
|
>
|
|
<span
|
|
className={`block truncate text-center text-xs font-bold ${
|
|
selected || completed ? "text-emerald-800" : "text-slate-500"
|
|
}`}
|
|
>
|
|
{completed ? (
|
|
<KatexRenderer latex={selectedChoice.latex} />
|
|
) : (
|
|
<KatexRenderer latex={pendingChoiceLabels[index]} />
|
|
)}
|
|
</span>
|
|
<span
|
|
className={`mt-1 block h-1.5 rounded-full ${
|
|
completed
|
|
? "bg-emerald-700"
|
|
: selected
|
|
? "bg-emerald-300 ring-1 ring-emerald-700"
|
|
: "bg-slate-200"
|
|
}`}
|
|
/>
|
|
</li>
|
|
);
|
|
})}
|
|
</ol>
|
|
</div>
|
|
|
|
<div
|
|
className="mt-5 grid gap-3 sm:grid-cols-2"
|
|
role="group"
|
|
aria-label={`${activeTermIndex === 0 ? "첫째 항" : "둘째 항"} 보기 선택`}
|
|
>
|
|
{activeChoices.map((choice, index) => {
|
|
const selected = selectedChoiceIds[activeTermIndex] === choice.id;
|
|
|
|
return (
|
|
<button
|
|
key={choice.id}
|
|
ref={index === 0 ? firstChoiceRef : undefined}
|
|
type="button"
|
|
onClick={() => onSelectChoice(activeTermIndex, choice.id)}
|
|
aria-pressed={selected}
|
|
className={`flex h-20 items-center justify-center rounded-md text-3xl font-bold transition focus:outline-none focus:ring-2 focus:ring-emerald-500 focus:ring-offset-2 ${
|
|
selected
|
|
? "bg-emerald-700 text-white"
|
|
: "bg-slate-50 text-slate-950 ring-1 ring-slate-200 hover:bg-slate-100"
|
|
}`}
|
|
>
|
|
<KatexRenderer latex={choice.latex} />
|
|
</button>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|