/* ========================================================================= CISCO ARENA — Question Type Renderers 12 renderer components + registry. Each renderer receives standardized props: spec — type-specific data (options, pairs, steps, etc.) value — current answer state (lifted to parent) onChange — callback to update answer locked — true after submission (no interaction) graded — true when showing correct/incorrect feedback The registry maps type string → { Component, isValid, getDefaultValue } ========================================================================= */ const { useState: _uS, useEffect: _uE, useRef: _uR, useCallback: _uC } = React; /* ========================================================================= 01 · SINGLE SELECT — radio list, one of N ========================================================================= */ function SingleSelectRenderer({ spec, value, onChange, locked, graded }) { const correct = spec.correct; return (
{spec.options.map(o => { const isSel = value === o.id; const isRight = graded && o.id === correct; const isWrong = graded && isSel && o.id !== correct; const cls = `opt radio${isSel && !graded ? " on" : ""}${isRight ? " right" : ""}${isWrong ? " wrong" : ""}`; return (
!locked && onChange(o.id)} onKeyDown={e => { if (!locked && (e.key === 'Enter' || e.key === ' ')) { e.preventDefault(); onChange(o.id); } }} >
{isSel ? "●" : o.id}
{o.label} {o.sub && {o.id} · {o.sub}} {!o.sub && {o.id}}
); })}
); } /* ========================================================================= 02 · MULTI SELECT — checkbox list, k of N ========================================================================= */ function MultiSelectRenderer({ spec, value, onChange, locked, graded }) { // value = string[] of selected option ids const sel = Array.isArray(value) ? value : []; const correct = spec.correct || []; const max = spec.maxPick || spec.options.length; const toggle = (id) => { if (locked) return; if (sel.includes(id)) { onChange(sel.filter(x => x !== id)); } else if (sel.length < max) { onChange([...sel, id]); } }; return ( <>
Selection · min {spec.minPick || 1} · max {max} {sel.length} of {max} selected
{spec.options.map(o => { const isSel = sel.includes(o.id); const isRight = graded && correct.includes(o.id); const isWrong = graded && isSel && !correct.includes(o.id); const cls = `opt${isSel && !graded ? " on" : ""}${isRight ? " right" : ""}${isWrong ? " wrong" : ""}`; return (
toggle(o.id)} onKeyDown={e => { if (!locked && (e.key === 'Enter' || e.key === ' ')) { e.preventDefault(); toggle(o.id); } }} >
{isSel ? "✓" : o.id}
{o.label} {o.sub && {o.sub}}
); })}
); } /* ========================================================================= 03 · TRUE / FALSE — two large buttons ========================================================================= */ function TrueFalseRenderer({ spec, value, onChange, locked, graded }) { const correct = spec.correct; _uE(() => { if (locked) return; const handler = (e) => { if (e.key === "t" || e.key === "T") onChange(true); if (e.key === "f" || e.key === "F") onChange(false); }; window.addEventListener("keydown", handler); return () => window.removeEventListener("keydown", handler); }, [locked, onChange]); const cls = (val) => { const isSel = value === val; const isRight = graded && val === correct; const isWrong = graded && isSel && val !== correct; return `tf${isSel && !graded ? " on" : ""}${isRight ? " right" : ""}${isWrong ? " wrong" : ""}`; }; return (
!locked && onChange(true)} onKeyDown={e => { if (!locked && (e.key === 'Enter' || e.key === ' ')) { e.preventDefault(); onChange(true); } }} > TRUEPress T
!locked && onChange(false)} onKeyDown={e => { if (!locked && (e.key === 'Enter' || e.key === ' ')) { e.preventDefault(); onChange(false); } }} > FALSEPress F
); } /* ========================================================================= 04 · FREE TEXT / CLI — textarea input ========================================================================= */ function FreeTextRenderer({ spec, value, onChange, locked, graded }) { const maxLen = spec.maxLength || 800; const text = value || ""; return (