# @soroban/abacus-react A comprehensive React component for rendering interactive Soroban (Japanese abacus) visualizations with advanced customization and tutorial capabilities. ## Features - 🎯 **Interactive beads** - Click to toggle or use directional gestures - 🎨 **Complete visual customization** - Style every element individually - πŸ“± **Responsive scaling** - Configurable scale factor for different sizes - 🌈 **Multiple color schemes** - Monochrome, place-value, alternating, heaven-earth - 🎭 **Flexible shapes** - Diamond, square, or circle beads - ⚑ **React Spring animations** - Smooth bead movements and transitions - πŸ”§ **Developer-friendly** - Comprehensive hooks and callback system - πŸŽ“ **Tutorial system** - Built-in overlay and guidance capabilities - 🧩 **Framework-free SVG** - Complete control over rendering - ✨ **3D Enhancement** - Three levels of progressive 3D effects for immersive visuals - πŸš€ **Server Component support** - AbacusStatic works in React Server Components (Next.js App Router) ## Installation ```bash npm install @soroban/abacus-react # or pnpm add @soroban/abacus-react # or yarn add @soroban/abacus-react ``` ## Quick Start ### Basic Usage Simple abacus showing a number Basic Usage ```tsx ``` ### Interactive Mode Clickable abacus with animations Interactive Mode ```tsx console.log("New value:", newValue), onBeadClick: (event) => console.log("Bead clicked:", event), }} /> ``` ### Custom Styling Personalized colors and highlights Custom Styling ```tsx ``` ### Theme Presets Use pre-defined themes for quick styling: ```tsx import { AbacusReact, ABACUS_THEMES } from '@soroban/abacus-react'; // Available themes: 'light', 'dark', 'trophy', 'translucent', 'solid', 'traditional' ``` **Available Themes:** - `light` - Solid white frame with subtle gray accents (best for light backgrounds) - `dark` - Translucent white with subtle glow (best for dark backgrounds) - `trophy` - Golden frame with warm tones (best for achievements/rewards) - `translucent` - Nearly invisible frame (best for inline/minimal UI) - `solid` - Black frame (best for high contrast/educational contexts) - `traditional` - Brown wooden appearance (best for traditional soroban aesthetic) ### Static Display (Server Components) For static, non-interactive displays that work in React Server Components: ```tsx // IMPORTANT: Use /static import path for RSC compatibility! import { AbacusStatic } from "@soroban/abacus-react/static"; // βœ… Works in React Server Components - no "use client" needed! // βœ… No JavaScript sent to client // βœ… Perfect for SSG, SSR, and static previews ; ``` **Import paths:** - `@soroban/abacus-react` - Full package (client components with hooks/animations) - `@soroban/abacus-react/static` - Server-compatible components only (no client code) **Guaranteed Visual Consistency:** Both `AbacusStatic` and `AbacusReact` share the same underlying layout engine. **Same props = same exact SVG output.** This ensures: - Static previews match interactive versions pixel-perfect - Server-rendered abaci look identical to client-rendered ones - PDF generation produces accurate representations - No visual discrepancies between environments **Architecture: How We Guarantee Consistency** The package uses a shared rendering architecture with dependency injection: ``` β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Shared Utilities (AbacusUtils.ts) β”‚ β”‚ β€’ calculateStandardDimensions() - Single β”‚ β”‚ source of truth for all layout dimensionsβ”‚ β”‚ β€’ calculateBeadPosition() - Exact bead β”‚ β”‚ positioning using shared formulas β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” ↓ ↓ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ AbacusStatic β”‚ β”‚ AbacusReact β”‚ β”‚ (Server/Static) β”‚ β”‚ (Interactive) β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ↓ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ AbacusSVGRenderer β”‚ β”‚ β€’ Pure SVG structure β”‚ β”‚ β€’ Dependency injection β”‚ β”‚ β€’ Bead component prop β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ↓ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” ↓ ↓ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ AbacusStatic β”‚ β”‚ AbacusAnimated β”‚ β”‚ Bead β”‚ β”‚ Bead β”‚ β”‚ (Simple SVG) β”‚ β”‚ (react-spring) β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` **Key Components:** 1. **`calculateStandardDimensions()`** - Returns complete layout dimensions (bar position, bead sizes, gaps, etc.) 2. **`calculateBeadPosition()`** - Calculates exact x,y coordinates for any bead 3. **`AbacusSVGRenderer`** - Shared SVG rendering component that accepts a bead component via dependency injection 4. **`AbacusStaticBead`** - Simple SVG shapes for static display (no hooks, RSC-compatible) 5. **`AbacusAnimatedBead`** - Client component with react-spring animations and gesture handling This architecture eliminates code duplication (~560 lines removed in the refactor) while guaranteeing pixel-perfect consistency. **When to use `AbacusStatic` vs `AbacusReact`:** | Feature | AbacusStatic | AbacusReact | | ----------------------- | --------------------------------------------- | ----------------------------------- | | React Server Components | βœ… Yes | ❌ No (requires "use client") | | Client-side JavaScript | ❌ None | βœ… Yes | | User interaction | ❌ No | βœ… Click/drag beads | | Animations | ❌ No | βœ… Smooth transitions | | Sound effects | ❌ No | βœ… Optional sounds | | 3D effects | ❌ No | βœ… Yes | | **Visual output** | **βœ… Identical** | **βœ… Identical** | | Bundle size | πŸ“¦ Minimal | πŸ“¦ Full-featured | | Use cases | Preview cards, thumbnails, static pages, PDFs | Interactive tutorials, games, tools | ```tsx // Example: Server Component with static abacus cards // app/flashcards/page.tsx import { AbacusStatic } from "@soroban/abacus-react/static"; export default function FlashcardsPage() { const numbers = [1, 5, 10, 25, 50, 100]; return (
{numbers.map((num) => (

{num}

))}
); } ``` ### Compact/Inline Display Create mini abacus displays for inline use: ```tsx // Compact mode - automatically hides frame and optimizes spacing // Or manually control frame visibility ``` ### Tutorial System Educational guidance with tooltips and column highlighting Tutorial System ```tsx Click this bead in the tens column!, offset: { x: 0, y: -30 }, }, ]} callbacks={{ onBeadClick: (event) => { if ( event.columnIndex === 1 && event.beadType === "earth" && event.position === 1 ) { console.log("Correct! You clicked the tens column."); } }, }} /> ``` **Column Highlighting:** - `highlightColumns` - Array of column indices to highlight (e.g., `[0, 2]` highlights first and third columns) - `columnLabels` - Optional labels displayed above each column (indexed left to right) ## 3D Enhancement Make the abacus feel tangible and satisfying with three progressive levels of 3D effects. ### Subtle Mode Light depth shadows and perspective for subtle dimensionality. ```tsx ``` ### Realistic Mode Material-based rendering with lighting effects and textures. ```tsx ``` **Materials:** - `glossy` - High shine with strong highlights - `satin` - Balanced shine (default) - `matte` - Subtle shading, no shine **Lighting:** - `top-down` - Balanced directional light from above - `ambient` - Soft light from all directions - `dramatic` - Strong directional light for high contrast ### Delightful Mode Maximum satisfaction with enhanced physics and interactive effects. ```tsx ``` **Physics Options:** - `hoverParallax` - Beads near mouse cursor lift up with depth perception All 3D modes work with existing configurations and preserve exact geometry. ## Core API ### Basic Props ```tsx interface AbacusConfig { // Display value?: number; // 0-99999, number to display columns?: number | "auto"; // Number of columns or auto-calculate showNumbers?: boolean; // Show place value numbers scaleFactor?: number; // 0.5 - 3.0, size multiplier // Appearance beadShape?: "diamond" | "square" | "circle"; colorScheme?: "monochrome" | "place-value" | "alternating" | "heaven-earth"; colorPalette?: "default" | "colorblind" | "mnemonic" | "grayscale" | "nature"; hideInactiveBeads?: boolean; // Hide/show inactive beads // Layout & Frame frameVisible?: boolean; // Show/hide column posts and reckoning bar compact?: boolean; // Compact layout (implies frameVisible=false) // Interaction interactive?: boolean; // Enable user interactions animated?: boolean; // Enable animations gestures?: boolean; // Enable drag gestures // Tutorial Features highlightColumns?: number[]; // Highlight specific columns by index columnLabels?: string[]; // Optional labels for columns } ``` ### Event Callbacks ```tsx interface AbacusCallbacks { onValueChange?: (newValue: number) => void; onBeadClick?: (event: BeadClickEvent) => void; onBeadHover?: (event: BeadClickEvent) => void; onBeadLeave?: (event: BeadClickEvent) => void; onColumnClick?: (columnIndex: number) => void; onNumeralClick?: (columnIndex: number, value: number) => void; onBeadRef?: (bead: BeadConfig, element: SVGElement | null) => void; } interface BeadClickEvent { columnIndex: number; // 0, 1, 2... beadType: "heaven" | "earth"; // Type of bead position: number; // Position within type (0-3 for earth) active: boolean; // Current state value: number; // Numeric value (1 or 5) bead: BeadConfig; // Full bead configuration } ``` ## Advanced Customization ### Granular Styling Target any visual element with precise control: ```tsx const customStyles = { // Global defaults heavenBeads: { fill: "#ff6b35" }, earthBeads: { fill: "#3498db" }, activeBeads: { opacity: 1.0 }, inactiveBeads: { opacity: 0.3 }, // Column-specific overrides columns: { 0: { // Hundreds column heavenBeads: { fill: "#e74c3c" }, earthBeads: { fill: "#2ecc71" }, }, }, // Individual bead targeting beads: { 1: { // Middle column heaven: { fill: "#f39c12" }, earth: { 0: { fill: "#1abc9c" }, // First earth bead 3: { fill: "#e67e22" }, // Fourth earth bead }, }, }, // UI elements reckoningBar: { stroke: "#34495e", strokeWidth: 3 }, columnPosts: { stroke: "#7f8c8d" }, numerals: { color: "#2c3e50", fontSize: "14px", fontFamily: "monospace", }, }; ; ``` ### Tutorial and Overlay System Create interactive educational experiences: ```tsx const overlays = [ { id: "welcome-tooltip", type: "tooltip", target: { type: "bead", columnIndex: 0, beadType: "earth", beadPosition: 0, }, content: (
Click me to start!
), offset: { x: 0, y: -30 }, }, ]; { if ( event.columnIndex === 0 && event.beadType === "earth" && event.position === 0 ) { console.log("Tutorial step completed!"); } }, }} />; ``` ### Bead Reference System Access individual bead DOM elements for advanced positioning: ```tsx function AdvancedExample() { const beadRefs = useRef(new Map()); const handleBeadRef = (bead: BeadConfig, element: SVGElement | null) => { const key = `${bead.columnIndex}-${bead.type}-${bead.position}`; if (element) { beadRefs.current.set(key, element); // Now you can position tooltips, highlights, etc. precisely const rect = element.getBoundingClientRect(); console.log(`Bead at column ${bead.columnIndex} is at:`, rect); } }; return ( ); } ``` ## Hooks ### useAbacusDiff Calculate bead differences between values for tutorials and animations: ```tsx import { useAbacusDiff } from "@soroban/abacus-react"; function Tutorial() { const [currentValue, setCurrentValue] = useState(5); const targetValue = 15; // Get diff information: which beads need to move const diff = useAbacusDiff(currentValue, targetValue); return (

{diff.summary}

{/* "add heaven bead in tens column, then..." */}

Changes needed: {diff.changes.length}

); } ``` **Returns:** - `changes` - Array of bead movements with direction and order - `highlights` - Bead highlight data for stepBeadHighlights prop - `hasChanges` - Boolean indicating if any changes needed - `summary` - Human-readable description of changes (e.g., "add heaven bead in ones column") ### useAbacusState Convert numbers to abacus bead states: ```tsx import { useAbacusState } from "@soroban/abacus-react"; function BeadAnalyzer() { const value = 123; const state = useAbacusState(value); // Check bead positions const onesHasHeaven = state[0].heavenActive; // false (3 < 5) const tensEarthCount = state[1].earthActive; // 2 (20 = 2 tens) return (
Ones column heaven bead: {onesHasHeaven ? "active" : "inactive"}
); } ``` ### useAbacusDimensions Get exact sizing information for layout planning: ```tsx import { useAbacusDimensions } from "@soroban/abacus-react"; function MyComponent() { const dimensions = useAbacusDimensions(3, 1.2); // 3 columns, 1.2x scale return (
); } ``` ## Utility Functions Low-level functions for working with abacus states and calculations: ### numberToAbacusState Convert a number to bead positions: ```tsx import { numberToAbacusState } from "@soroban/abacus-react"; const state = numberToAbacusState(123, 5); // 5 columns // Returns: { // 0: { heavenActive: false, earthActive: 3 }, // ones = 3 // 1: { heavenActive: false, earthActive: 2 }, // tens = 2 // 2: { heavenActive: true, earthActive: 0 }, // hundreds = 1 // ... // } ``` ### abacusStateToNumber Convert bead positions back to a number: ```tsx import { abacusStateToNumber } from "@soroban/abacus-react"; const state = { 0: { heavenActive: false, earthActive: 3 }, 1: { heavenActive: false, earthActive: 2 }, 2: { heavenActive: true, earthActive: 0 }, }; const value = abacusStateToNumber(state); // 123 ``` ### calculateBeadDiff Calculate the exact bead movements needed between two states: ```tsx import { calculateBeadDiff, numberToAbacusState } from "@soroban/abacus-react"; const fromState = numberToAbacusState(5); const toState = numberToAbacusState(15); const diff = calculateBeadDiff(fromState, toState); console.log(diff.summary); // "add heaven bead in tens column" console.log(diff.changes); // Detailed array of movements with order ``` ### calculateBeadDiffFromValues Convenience wrapper for calculating diff from numbers: ```tsx import { calculateBeadDiffFromValues } from "@soroban/abacus-react"; const diff = calculateBeadDiffFromValues(42, 57); // Equivalent to: calculateBeadDiff(numberToAbacusState(42), numberToAbacusState(57)) ``` ### validateAbacusValue Check if a value is within the supported range: ```tsx import { validateAbacusValue } from "@soroban/abacus-react"; const result = validateAbacusValue(123456, 5); // 5 columns max console.log(result.isValid); // false console.log(result.error); // "Value exceeds maximum for 5 columns (max: 99999)" ``` ### areStatesEqual Compare two abacus states: ```tsx import { areStatesEqual, numberToAbacusState } from "@soroban/abacus-react"; const state1 = numberToAbacusState(123); const state2 = numberToAbacusState(123); const isEqual = areStatesEqual(state1, state2); // true ``` ### calculateStandardDimensions **⚑ Core Architecture Function** - Calculate complete layout dimensions for consistent rendering. This is the **single source of truth** for all layout dimensions, used internally by both `AbacusStatic` and `AbacusReact` to guarantee pixel-perfect consistency. ```tsx import { calculateStandardDimensions } from "@soroban/abacus-react"; const dimensions = calculateStandardDimensions({ columns: 3, scaleFactor: 1.5, showNumbers: true, columnLabels: ["ones", "tens", "hundreds"], }); // Returns complete layout info: // { // width, height, // SVG canvas size // beadSize, // 12 * scaleFactor (standard bead size) // rodSpacing, // 25 * scaleFactor (column spacing) // rodWidth, // 3 * scaleFactor // barThickness, // 2 * scaleFactor // barY, // Reckoning bar Y position (30 * scaleFactor + labels) // heavenY, earthY, // Inactive bead rest positions // activeGap, // 1 * scaleFactor (gap to bar when active) // inactiveGap, // 8 * scaleFactor (gap between active/inactive) // adjacentSpacing, // 0.5 * scaleFactor (spacing between adjacent beads) // padding, labelHeight, numbersHeight, totalColumns // } ``` **Why this matters:** Same input parameters = same exact layout dimensions = pixel-perfect visual consistency across static and interactive displays. ### calculateBeadPosition **⚑ Core Architecture Function** - Calculate exact x,y coordinates for any bead. Used internally by `AbacusSVGRenderer` to position all beads consistently in both static and interactive modes. ```tsx import { calculateBeadPosition, calculateStandardDimensions, } from "@soroban/abacus-react"; const dimensions = calculateStandardDimensions({ columns: 3, scaleFactor: 1 }); const bead = { type: "heaven", active: true, position: 0, placeValue: 1, // tens column }; const position = calculateBeadPosition(bead, dimensions); // Returns: { x: 25, y: 29 } // exact pixel coordinates ``` Useful for custom rendering or positioning tooltips/overlays relative to specific beads. ## Educational Use Cases ### Interactive Math Lessons ```tsx function MathLesson() { const [problem, setProblem] = useState({ a: 23, b: 45 }); const [step, setStep] = useState("show-first"); return (

Add {problem.a} + {problem.b}

{ if (value === problem.a + problem.b) { celebrate(); } }, }} />
); } ``` ### Assessment Tools ```tsx function AbacusQuiz() { const [answers, setAnswers] = useState([]); const checkAnswer = (event: BeadClickEvent) => { const isCorrect = validateBeadClick(event, expectedAnswer); recordAnswer(event, isCorrect); if (isCorrect) { showSuccessFeedback(); } else { showHint(event); } }; return ( ); } ``` ## TypeScript Support Full TypeScript definitions included: ```tsx import { // Components AbacusReact, // Hooks useAbacusDiff, useAbacusState, useAbacusDimensions, // Utility Functions numberToAbacusState, abacusStateToNumber, calculateBeadDiff, calculateBeadDiffFromValues, validateAbacusValue, areStatesEqual, calculateStandardDimensions, // NEW: Shared layout calculator calculateBeadPosition, // NEW: Bead position calculator // Theme Presets ABACUS_THEMES, // Types AbacusConfig, BeadConfig, BeadClickEvent, AbacusCustomStyles, AbacusOverlay, AbacusCallbacks, AbacusState, BeadState, BeadDiffResult, BeadDiffOutput, AbacusThemeName, AbacusLayoutDimensions, // NEW: Complete layout dimensions type BeadPositionConfig, // NEW: Bead config for position calculation } from "@soroban/abacus-react"; // All interfaces fully typed for excellent developer experience ``` ## Contributing Contributions welcome! Please see our contributing guidelines and feel free to submit issues or pull requests. ## License MIT License - see LICENSE file for details.