@math-scan/exercise-renderer
v1.0.1
Published
A flexible and scalable React package for rendering interactive math exercises
Maintainers
Readme
@mathscan/exercise-renderer
A flexible and scalable React package for rendering interactive math exercises with TypeScript and Zod validation.
Features
- 🎯 Multiple Widget Types: Numeric input, fractions, divisions, number lines, and more
- 🎨 Beautiful UI: Pre-styled with Tailwind CSS
- 🔄 Flexible Modes: Support for both typing and drag-and-drop interactions
- ✅ Built-in Grading: Automatic answer validation with customizable grading logic
- 📝 Template-Based: Parse math expressions from simple template strings
- 🔒 Type-Safe: Full TypeScript support with Zod schemas
- 🎭 Extensible: Easy to add custom widgets and grading logic
- 🌍 RTL Support: Built-in support for right-to-left languages
Installation
npm install @mathscan/exercise-renderer react react-dom
# or
yarn add @mathscan/exercise-renderer react react-dom
# or
pnpm add @mathscan/exercise-renderer react react-domNote: This package requires Tailwind CSS to be configured in your project.
Quick Start
import { ExerciseRenderer, gradeExercise } from '@mathscan/exercise-renderer';
import { useState } from 'react';
function App() {
const [answers, setAnswers] = useState({});
const exercise = {
type: "doc",
content: [
{
type: "paragraph",
attrs: { align: "center" },
content: [
{
type: "widget",
attrs: {
_uid: "ex1",
id: "numeric_input",
label: "Simple Addition",
description: "Basic math problem",
content: {
template: "15 + 5 = ?",
answers: ["20"],
options: {
mode: "type"
}
}
}
}
]
}
]
};
const handleAnswerChange = (key: string, value: string) => {
setAnswers(prev => ({ ...prev, [key]: value }));
};
const handleCheck = () => {
const widget = exercise.content[0].content[0];
const isCorrect = gradeExercise(widget, answers);
alert(isCorrect ? 'Correct!' : 'Try again!');
};
return (
<div className="p-8">
<ExerciseRenderer
exercise={exercise}
answers={answers}
onAnswerChange={handleAnswerChange}
/>
<button
onClick={handleCheck}
className="mt-4 px-6 py-2 bg-purple-600 text-white rounded-lg"
>
Check Answer
</button>
</div>
);
}Widget Types
1. Numeric Input
Simple numeric input with support for single or multiple blanks.
{
type: "widget",
attrs: {
id: "numeric_input",
content: {
template: "? + ? = 10",
answers: ["5", "5"], // or multiple solutions: [["1","9"], ["2","8"], ...]
options: {
mode: "type", // or "drag"
pool: ["1", "2", "3", "4", "5"], // for drag mode
}
}
}
}2. Fraction Input
Handle fraction expressions with automatic equivalence checking.
{
type: "widget",
attrs: {
id: "fraction_input",
content: {
template: "2 × 1/4 = ?/?",
answers: ["2/4", "1/2"],
options: {
acceptEquivalent: true // Accept equivalent fractions
}
}
}
}3. Vertical Operations
Display operations vertically for traditional arithmetic format.
{
type: "widget",
attrs: {
id: "numeric_input",
content: {
template: "15 + 225 + 5 = ???",
answers: ["245"],
options: {
mode: "type",
verticalDisplay: true
}
}
}
}4. Division with Remainder
Handle division problems with quotient and remainder.
{
type: "widget",
attrs: {
id: "division_input",
content: {
template: "629 ÷ 4 = ? R ?",
answers: ["157", "1"],
options: {
labels: ["Quotient", "Remainder"]
}
}
}
}5. Number Line
Interactive number line with positionable inputs.
{
type: "widget",
attrs: {
id: "number_line",
content: {
from: 0,
to: 1,
ticks: [
{ position: 0, label: "?", input: true },
{ position: 0.5, label: "?", input: true },
{ position: 1, label: "?", input: true }
],
answers: ["0", "0.5", "1"],
options: {
mode: "drag",
pool: ["0", "0.25", "0.5", "0.75", "1"]
}
}
}
}Template Syntax
The template parser supports:
- Numbers:
15,3.14,-5 - Operators:
+,-,×,÷,= - Single input:
? - Multi-digit input:
??,??? - Fractions:
1/2,?/4,?/? - Remainder:
R(with spaces around it)
Grading
import { gradeExercise, gradeWithFeedback } from '@mathscan/exercise-renderer';
// Simple grading
const isCorrect = gradeExercise(widget, userAnswers);
// Detailed feedback
const result = gradeWithFeedback(widget, userAnswers);
console.log(result.message); // "✓ Correct! Well done!" or "✗ Try again!"Advanced Usage
Custom Hooks
import { useExerciseState } from '@mathscan/exercise-renderer';
function CustomExercise() {
const {
answers,
usedItems,
validation,
handleAnswerChange,
handleUsedItemsChange,
resetExercise,
setValidationResult
} = useExerciseState();
// Use the state and handlers
}Validation with Zod
import { ExerciseSchema } from '@mathscan/exercise-renderer';
const result = ExerciseSchema.safeParse(exerciseData);
if (result.success) {
// Exercise data is valid
const exercise = result.data;
} else {
console.error(result.error);
}Custom Widget Components
import { NumericInputWidget } from '@mathscan/exercise-renderer';
function CustomWidget() {
return (
<NumericInputWidget
template="? + ? = 10"
mode="type"
answers={{}}
onAnswerChange={(key, value) => console.log(key, value)}
/>
);
}Styling
This package uses Tailwind CSS. Make sure Tailwind is configured in your project:
// tailwind.config.js
module.exports = {
content: [
'./src/**/*.{js,jsx,ts,tsx}',
'./node_modules/@mathscan/exercise-renderer/**/*.{js,jsx,ts,tsx}',
],
// ... rest of config
};The components don't include background colors by default, allowing you to customize the appearance in your app.
TypeScript Support
Full TypeScript support with exported types:
import type {
Exercise,
Widget,
AnswerState,
ValidationResult,
NumericInputContent,
FractionInputContent,
} from '@mathscan/exercise-renderer';API Reference
Components
ExerciseRenderer- Main component for rendering exercisesWidgetRenderer- Renders individual widgetsNumericInputWidget- Numeric input widgetVerticalOperationWidget- Vertical arithmetic operationsNumberLineWidget- Interactive number lineInputBox- Shared input componentDragPool- Drag-and-drop pool componentFractionDisplay- Fraction display/input component
Functions
parseTemplate(template)- Parse template strings into tokensparseVerticalOperation(template)- Parse vertical operation templatesgradeExercise(widget, answers)- Grade user answersgradeWithFeedback(widget, answers)- Grade with detailed feedbackareFractionsEquivalent(f1, f2)- Check fraction equivalence
Hooks
useExerciseState()- Manage exercise stateuseDragAndDrop()- Manage drag-and-drop interactions
License
MIT
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
