Public Access
feat: add MultipleChoice and MultipleSelect renderers
This commit is contained in:
1 parent
b5e53c97e6
commit
535434421a
2 files changed
+80
No files matched your search
@@ -0,0 +1,40 @@
|
|||||||
|
import Tex from '../Tex'
|
||||||
|
import type { Question } from '../../types'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
q: Question
|
||||||
|
value: string | null | undefined
|
||||||
|
onChange: (val: string) => void
|
||||||
|
locked?: boolean
|
||||||
|
graded?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function MultipleChoice({ q, value, onChange, locked, graded }: Props) {
|
||||||
|
return (
|
||||||
|
<div className="choices" role="radiogroup">
|
||||||
|
{(q.choices ?? []).map((c, i) => {
|
||||||
|
const isSelected = value === c.id
|
||||||
|
let cls = 'choice'
|
||||||
|
if (isSelected) cls += ' selected'
|
||||||
|
if (graded) {
|
||||||
|
if (c.id === q.answer) cls += ' correct'
|
||||||
|
else if (isSelected) cls += ' wrong'
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={c.id}
|
||||||
|
className={cls}
|
||||||
|
onClick={() => !locked && onChange(c.id)}
|
||||||
|
disabled={locked}
|
||||||
|
role="radio"
|
||||||
|
aria-checked={isSelected}
|
||||||
|
>
|
||||||
|
<span className="choice-marker" />
|
||||||
|
<span className="choice-key">{String.fromCharCode(65 + i)}</span>
|
||||||
|
<Tex>{c.text}</Tex>
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import Tex from '../Tex'
|
||||||
|
import type { Question } from '../../types'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
q: Question
|
||||||
|
value: string[] | null | undefined
|
||||||
|
onChange: (val: string[]) => void
|
||||||
|
locked?: boolean
|
||||||
|
graded?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function MultipleSelect({ q, value, onChange, locked, graded }: Props) {
|
||||||
|
const current = value ?? []
|
||||||
|
const toggle = (id: string) => {
|
||||||
|
if (locked) return
|
||||||
|
onChange(current.includes(id) ? current.filter((v) => v !== id) : [...current, id])
|
||||||
|
}
|
||||||
|
const answerSet = new Set((q.answer as string[]) ?? [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="choices">
|
||||||
|
{(q.choices ?? []).map((c, i) => {
|
||||||
|
const isSelected = current.includes(c.id)
|
||||||
|
let cls = 'choice'
|
||||||
|
if (isSelected) cls += ' selected'
|
||||||
|
if (graded) {
|
||||||
|
if (answerSet.has(c.id)) cls += ' correct'
|
||||||
|
else if (isSelected) cls += ' wrong'
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<button key={c.id} className={cls} onClick={() => toggle(c.id)} disabled={locked}>
|
||||||
|
<span className="choice-marker square" />
|
||||||
|
<span className="choice-key">{String.fromCharCode(65 + i)}</span>
|
||||||
|
<Tex>{c.text}</Tex>
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
Reference in new issue
Block a user