From 82ba53da0ec6246f0dae96edd86b1a238e5e8fc3 Mon Sep 17 00:00:00 2001 From: LaurensBartelds Date: Tue, 21 Apr 2026 21:41:37 +0200 Subject: [PATCH] feat: add ShortAnswer and MathInput renderers --- .../src/components/questions/MathInput.tsx | 45 +++++++++++++++++++ .../src/components/questions/ShortAnswer.tsx | 30 +++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 frontend/src/components/questions/MathInput.tsx create mode 100644 frontend/src/components/questions/ShortAnswer.tsx diff --git a/frontend/src/components/questions/MathInput.tsx b/frontend/src/components/questions/MathInput.tsx new file mode 100644 index 0000000..ba2cedf --- /dev/null +++ b/frontend/src/components/questions/MathInput.tsx @@ -0,0 +1,45 @@ +import { useState, useEffect } from 'react' +import katex from 'katex' +import type { Question } from '../../types' + +interface Props { + q: Question + value: string | null | undefined + onChange: (val: string) => void + locked?: boolean +} + +// The KaTeX preview uses innerHTML: KaTeX controls all output, making this safe. +export default function MathInput({ value = '', onChange, locked }: Props) { + const [preview, setPreview] = useState('') + + useEffect(() => { + if (!value) { setPreview(''); return } + try { + setPreview(katex.renderToString(value, { throwOnError: false, displayMode: false })) + } catch { + setPreview('') + } + }, [value]) + + return ( +
+ onChange(e.target.value)} + placeholder="LaTeX, e.g. 2x\ln x + x" + disabled={locked} + autoComplete="off" + spellCheck={false} + /> +
+ Preview + {preview + ? + : Live LaTeX preview appears here} +
+
+ ) +} diff --git a/frontend/src/components/questions/ShortAnswer.tsx b/frontend/src/components/questions/ShortAnswer.tsx new file mode 100644 index 0000000..f57568b --- /dev/null +++ b/frontend/src/components/questions/ShortAnswer.tsx @@ -0,0 +1,30 @@ +import type { Question } from '../../types' + +interface Props { + q: Question + value: string | null | undefined + onChange: (val: string) => void + locked?: boolean +} + +export default function ShortAnswer({ q, value = '', onChange, locked }: Props) { + return ( +
+ onChange(e.target.value)} + placeholder="Type your answer…" + disabled={locked} + autoComplete="off" + spellCheck={q.type === 'translation'} + /> + {q.type === 'translation' && q.targetLanguage && ( +
+ Answer in {q.targetLanguage} +
+ )} +
+ ) +}