diff --git a/apps/web/src/app/create/worksheets/addition/components/DisplayOptionsPreview.tsx b/apps/web/src/app/create/worksheets/addition/components/DisplayOptionsPreview.tsx index 1e4d0380..c7f69603 100644 --- a/apps/web/src/app/create/worksheets/addition/components/DisplayOptionsPreview.tsx +++ b/apps/web/src/app/create/worksheets/addition/components/DisplayOptionsPreview.tsx @@ -9,6 +9,128 @@ interface DisplayOptionsPreviewProps { formState: WorksheetFormState } +interface MathSentenceProps { + operands: number[] + operator: string + onChange: (operands: number[]) => void + labels?: string[] +} + +/** + * Flexible math sentence component supporting operators with arity 1-3 + * Examples: + * Arity 1 (unary): [64] with "√" → "√64" + * Arity 2 (binary): [45, 27] with "+" → "45 + 27" + * Arity 3 (ternary): [5, 10, 15] with "between" → "5 < 10 < 15" + */ +function MathSentence({ operands, operator, onChange, labels }: MathSentenceProps) { + const handleOperandChange = (index: number, value: string) => { + const numValue = Number.parseInt(value, 10) + if (!Number.isNaN(numValue) && numValue >= 0 && numValue <= 99) { + const newOperands = [...operands] + newOperands[index] = numValue + onChange(newOperands) + } + } + + const renderInput = (value: number, index: number) => ( + handleOperandChange(index, e.target.value)} + aria-label={labels?.[index] || `operand ${index + 1}`} + className={css({ + width: '3.5em', + px: '1', + py: '0.5', + fontSize: 'sm', + fontWeight: 'medium', + textAlign: 'center', + border: '1px solid', + borderColor: 'transparent', + rounded: 'sm', + outline: 'none', + transition: 'border-color 0.2s', + _hover: { + borderColor: 'gray.300', + }, + _focus: { + borderColor: 'brand.500', + ring: '1px', + ringColor: 'brand.200', + }, + })} + /> + ) + + // Render based on arity + if (operands.length === 1) { + // Unary operator (prefix): √64 or ±5 + return ( +
+ {operator} + {renderInput(operands[0], 0)} +
+ ) + } + + if (operands.length === 2) { + // Binary operator (infix): 45 + 27 + return ( +
+ {renderInput(operands[0], 0)} + {operator} + {renderInput(operands[1], 1)} +
+ ) + } + + if (operands.length === 3) { + // Ternary operator: 5 < 10 < 15 or similar + return ( +
+ {renderInput(operands[0], 0)} + {operator} + {renderInput(operands[1], 1)} + {operator} + {renderInput(operands[2], 2)} +
+ ) + } + + return null +} + async function fetchExample(options: { showCarryBoxes: boolean showAnswerBoxes: boolean @@ -17,6 +139,8 @@ async function fetchExample(options: { showCellBorder: boolean showTenFrames: boolean showTenFramesForAll: boolean + addend1: number + addend2: number }): Promise { const response = await fetch('/api/create/worksheets/addition/example', { method: 'POST', @@ -36,6 +160,9 @@ async function fetchExample(options: { } export function DisplayOptionsPreview({ formState }: DisplayOptionsPreviewProps) { + // Local state for operands (not debounced - we want immediate feedback) + const [operands, setOperands] = useState([45, 27]) + // Debounce the display options to avoid hammering the server const [debouncedOptions, setDebouncedOptions] = useState({ showCarryBoxes: formState.showCarryBoxes ?? true, @@ -45,6 +172,8 @@ export function DisplayOptionsPreview({ formState }: DisplayOptionsPreviewProps) showCellBorder: formState.showCellBorder ?? true, showTenFrames: formState.showTenFrames ?? false, showTenFramesForAll: formState.showTenFramesForAll ?? false, + addend1: operands[0], + addend2: operands[1], }) useEffect(() => { @@ -57,6 +186,8 @@ export function DisplayOptionsPreview({ formState }: DisplayOptionsPreviewProps) showCellBorder: formState.showCellBorder ?? true, showTenFrames: formState.showTenFrames ?? false, showTenFramesForAll: formState.showTenFramesForAll ?? false, + addend1: operands[0], + addend2: operands[1], }) }, 300) // 300ms debounce @@ -69,6 +200,7 @@ export function DisplayOptionsPreview({ formState }: DisplayOptionsPreviewProps) formState.showCellBorder, formState.showTenFrames, formState.showTenFramesForAll, + operands, ]) const { data: svg, isLoading } = useQuery({ @@ -81,7 +213,7 @@ export function DisplayOptionsPreview({ formState }: DisplayOptionsPreviewProps)
- Preview +
+ Preview +
+
{isLoading ? (