📚 Update AbacusReact examples and documentation

Auto-generated fresh examples and balanced documentation from latest component changes.
Includes comprehensive usage patterns, API documentation, and educational examples.

Files updated:
- packages/abacus-react/README.md
- packages/abacus-react/src/AbacusReact.examples.stories.tsx

🤖 Generated with GitHub Actions

Co-Authored-By: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
This commit is contained in:
github-actions[bot] 2025-10-12 03:18:33 +00:00
parent 76163a0846
commit af209fe6ac
2 changed files with 361 additions and 632 deletions

View File

@ -1,26 +1,21 @@
# @soroban/abacus-react # @soroban/abacus-react
A comprehensive React component for rendering interactive Soroban (Japanese abacus) visualizations with advanced tutorial capabilities, directional gestures, and complete visual customization. A comprehensive React component for rendering interactive Soroban (Japanese abacus) visualizations with advanced customization and tutorial capabilities.
## Features ## Features
- 🎯 **Interactive beads** - Click to toggle or use directional drag gestures - 🎯 **Interactive beads** - Click to toggle or use directional gestures
- 🎨 **Complete visual customization** - Style every element individually with granular control - 🎨 **Complete visual customization** - Style every element individually
- 📱 **Responsive scaling** - Configurable scale factor for different display sizes - 📱 **Responsive scaling** - Configurable scale factor for different sizes
- 🌈 **Multiple color schemes** - Monochrome, place-value, alternating, heaven-earth - 🌈 **Multiple color schemes** - Monochrome, place-value, alternating, heaven-earth
- 🎭 **Flexible bead shapes** - Diamond, square, or circle beads - 🎭 **Flexible shapes** - Diamond, square, or circle beads
- ⚡ **React Spring animations** - Smooth bead movements and state transitions - ⚡ **React Spring animations** - Smooth bead movements and transitions
- 🔧 **Developer-friendly** - Comprehensive hooks, callbacks, and ref system - 🔧 **Developer-friendly** - Comprehensive hooks and callback system
- 🎓 **Tutorial system** - Built-in overlay system with tooltips and highlights - 🎓 **Tutorial system** - Built-in overlay and guidance capabilities
- 🧩 **Framework-free SVG** - Complete control over rendering and styling - 🧩 **Framework-free SVG** - Complete control over rendering
- 🏗️ **Type-safe APIs** - Full TypeScript support with branded types
- 📐 **Precise positioning** - Place-value based bead targeting system
- 🎮 **Directional gestures** - Natural drag interactions for bead manipulation
## Installation ## Installation
### From npm (recommended)
```bash ```bash
npm install @soroban/abacus-react npm install @soroban/abacus-react
# or # or
@ -29,33 +24,29 @@ pnpm add @soroban/abacus-react
yarn add @soroban/abacus-react yarn add @soroban/abacus-react
``` ```
### From GitHub Packages
```bash
# Configure npm to use GitHub Packages for @soroban scope
echo "@soroban:registry=https://npm.pkg.github.com" >> .npmrc
# Then install
npm install @soroban/abacus-react
```
The package is published to both npm and GitHub Packages simultaneously for redundancy and choice.
## Quick Start ## Quick Start
### Basic Usage ### Basic Usage
Simple static abacus display: Simple abacus showing a number
<img src="https://raw.githubusercontent.com/antialias/soroban-abacus-flashcards/main/packages/abacus-react/examples/basic-usage.svg" alt="Basic Usage">
```tsx ```tsx
import { AbacusReact } from "@soroban/abacus-react"; <AbacusReact
value={123}
<AbacusReact value={123} columns={3} showNumbers={true} scaleFactor={1.0} />; columns={3}
showNumbers={true}
scaleFactor={1.0}
/>
``` ```
### Interactive Mode ### Interactive Mode
Clickable abacus with animations and callbacks: Clickable abacus with animations
<img src="https://raw.githubusercontent.com/antialias/soroban-abacus-flashcards/main/packages/abacus-react/examples/interactive.svg" alt="Interactive Mode">
```tsx ```tsx
<AbacusReact <AbacusReact
@ -63,18 +54,19 @@ Clickable abacus with animations and callbacks:
columns={3} columns={3}
interactive={true} interactive={true}
animated={true} animated={true}
gestures={true}
showNumbers={true} showNumbers={true}
callbacks={{ callbacks={{
onValueChange: (newValue) => console.log("New value:", newValue), onValueChange: (newValue) => console.log('New value:', newValue),
onBeadClick: (event) => console.log("Bead clicked:", event), onBeadClick: (event) => console.log('Bead clicked:', event)
}} }}
/> />
``` ```
### Custom Styling ### Custom Styling
Personalized colors and visual themes: Personalized colors and highlights
<img src="https://raw.githubusercontent.com/antialias/soroban-abacus-flashcards/main/packages/abacus-react/examples/custom-styling.svg" alt="Custom Styling">
```tsx ```tsx
<AbacusReact <AbacusReact
@ -82,116 +74,72 @@ Personalized colors and visual themes:
columns={3} columns={3}
colorScheme="place-value" colorScheme="place-value"
beadShape="circle" beadShape="circle"
colorPalette="nature"
customStyles={{ customStyles={{
heavenBeads: { fill: "#2ecc71", stroke: "#27ae60" }, heavenBeads: { fill: '#ff6b35' },
earthBeads: { fill: "#3498db", stroke: "#2980b9" }, earthBeads: { fill: '#3498db' },
numerals: { color: "#2c3e50", fontWeight: "bold" }, numerals: { color: '#2c3e50', fontWeight: 'bold' }
reckoningBar: { stroke: "#34495e", strokeWidth: 3 },
}} }}
highlightBeads={[ highlightBeads={[
{ placeValue: 2, beadType: "heaven" }, // Hundreds place heaven bead { columnIndex: 1, beadType: 'heaven' }
{ placeValue: 0, beadType: "earth", position: 1 }, // Ones place, second earth bead
]} ]}
/> />
``` ```
### Tutorial System ### Tutorial System
Educational guidance with interactive overlays: Educational guidance with tooltips
<img src="https://raw.githubusercontent.com/antialias/soroban-abacus-flashcards/main/packages/abacus-react/examples/tutorial-mode.svg" alt="Tutorial System">
```tsx ```tsx
<AbacusReact <AbacusReact
value={42} value={42}
columns={2} columns={2}
interactive={true} interactive={true}
overlays={[ overlays={[{
{ id: 'tip',
id: "tutorial-tip", type: 'tooltip',
type: "tooltip", target: { type: 'bead', columnIndex: 0, beadType: 'earth', beadPosition: 1 },
target: { content: <div>Click this bead!</div>,
type: "bead", offset: { x: 0, y: -30 }
columnIndex: 0, }]}
beadType: "earth",
beadPosition: 1,
},
content: (
<div
style={{
background: "#333",
color: "white",
padding: "8px",
borderRadius: "4px",
fontSize: "14px",
}}
>
Click this bead to add 1!
</div>
),
offset: { x: 0, y: -30 },
},
]}
stepBeadHighlights={[
{
placeValue: 0,
beadType: "earth",
position: 1,
stepIndex: 0,
direction: "activate",
order: 1,
},
]}
showDirectionIndicators={true}
callbacks={{ callbacks={{
onBeadClick: (event) => { onBeadClick: (event) => {
if ( if (event.columnIndex === 0 && event.beadType === 'earth' && event.position === 1) {
event.placeValue === 0 && console.log('Correct!');
event.beadType === "earth" &&
event.position === 1
) {
console.log("Tutorial step completed!");
} }
}, }
}} }}
/> />
``` ```
## Core API ## Core API
### AbacusConfig Interface ### Basic Props
```tsx ```tsx
interface AbacusConfig { interface AbacusConfig {
// Display // Display
value?: number; // 0-99999, number to display value?: number; // 0-99999, number to display
columns?: number | "auto"; // Number of columns or auto-calculate columns?: number | 'auto'; // Number of columns or auto-calculate
showNumbers?: boolean; // Show place value numbers below showNumbers?: boolean; // Show place value numbers
scaleFactor?: number; // 0.5 - 3.0, size multiplier scaleFactor?: number; // 0.5 - 3.0, size multiplier
showEmptyColumns?: boolean; // Display columns with value 0
// Appearance // Appearance
beadShape?: "diamond" | "square" | "circle"; beadShape?: 'diamond' | 'square' | 'circle';
colorScheme?: "monochrome" | "place-value" | "alternating" | "heaven-earth"; colorScheme?: 'monochrome' | 'place-value' | 'alternating' | 'heaven-earth';
colorPalette?: "default" | "colorblind" | "mnemonic" | "grayscale" | "nature"; colorPalette?: 'default' | 'colorblind' | 'mnemonic' | 'grayscale' | 'nature';
hideInactiveBeads?: boolean; // Hide/show inactive beads hideInactiveBeads?: boolean; // Hide/show inactive beads
// Interaction // Interaction
interactive?: boolean; // Enable user interactions interactive?: boolean; // Enable user interactions
animated?: boolean; // Enable React Spring animations animated?: boolean; // Enable animations
gestures?: boolean; // Enable directional drag gestures gestures?: boolean; // Enable drag gestures
// Advanced
customStyles?: AbacusCustomStyles; // Granular styling control
callbacks?: AbacusCallbacks; // Event handlers
overlays?: AbacusOverlay[]; // Tutorial overlay system
highlightBeads?: BeadHighlight[]; // Highlight specific beads
stepBeadHighlights?: StepBeadHighlight[]; // Progressive tutorial highlighting
showDirectionIndicators?: boolean; // Show movement direction indicators
disabledBeads?: BeadHighlight[]; // Disable specific bead interactions
} }
``` ```
### Event System ### Event Callbacks
```tsx ```tsx
interface AbacusCallbacks { interface AbacusCallbacks {
@ -199,214 +147,109 @@ interface AbacusCallbacks {
onBeadClick?: (event: BeadClickEvent) => void; onBeadClick?: (event: BeadClickEvent) => void;
onBeadHover?: (event: BeadClickEvent) => void; onBeadHover?: (event: BeadClickEvent) => void;
onBeadLeave?: (event: BeadClickEvent) => void; onBeadLeave?: (event: BeadClickEvent) => void;
onColumnClick?: (columnIndex: number, event: React.MouseEvent) => void; onColumnClick?: (columnIndex: number) => void;
onNumeralClick?: ( onNumeralClick?: (columnIndex: number, value: number) => void;
columnIndex: number,
value: number,
event: React.MouseEvent,
) => void;
onBeadRef?: (bead: BeadConfig, element: SVGElement | null) => void; onBeadRef?: (bead: BeadConfig, element: SVGElement | null) => void;
} }
interface BeadClickEvent { interface BeadClickEvent {
bead: BeadConfig; // Complete bead configuration columnIndex: number; // 0, 1, 2...
columnIndex: number; // 0, 1, 2... (array index) beadType: 'heaven' | 'earth'; // Type of bead
placeValue: ValidPlaceValues; // 0=ones, 1=tens, 2=hundreds... position: number; // Position within type (0-3 for earth)
beadType: "heaven" | "earth"; // Type of bead active: boolean; // Current state
position: number; // Position within type (0-3 for earth) value: number; // Numeric value (1 or 5)
active: boolean; // Current activation state bead: BeadConfig; // Full bead configuration
value: number; // Numeric value (1 or 5)
event: React.MouseEvent; // Original mouse event
} }
``` ```
## Advanced Features ## Advanced Customization
### Place-Value Based Targeting ### Granular Styling
Target beads by mathematical place value instead of visual column position:
```tsx
// Target beads by place value (recommended)
const placeValueHighlights = [
{ placeValue: 0, beadType: "earth", position: 2 }, // Ones place, 3rd earth bead
{ placeValue: 1, beadType: "heaven" }, // Tens place, heaven bead
{ placeValue: 2, beadType: "earth", position: 0 }, // Hundreds place, 1st earth bead
];
// Legacy column-index targeting (still supported)
const columnHighlights = [
{ columnIndex: 2, beadType: "earth", position: 2 }, // Rightmost column
{ columnIndex: 1, beadType: "heaven" }, // Middle column
{ columnIndex: 0, beadType: "earth", position: 0 }, // Leftmost column
];
<AbacusReact highlightBeads={placeValueHighlights} />;
```
### Progressive Tutorial Steps
Create multi-step interactive tutorials:
```tsx
const tutorialSteps = [
{
placeValue: 0,
beadType: "earth",
position: 0,
stepIndex: 0,
direction: "activate",
order: 1,
},
{
placeValue: 0,
beadType: "earth",
position: 1,
stepIndex: 1,
direction: "activate",
order: 1,
},
{
placeValue: 1,
beadType: "heaven",
stepIndex: 2,
direction: "activate",
order: 1,
},
];
<AbacusReact
stepBeadHighlights={tutorialSteps}
currentStep={currentStepIndex}
showDirectionIndicators={true}
interactive={true}
/>;
```
### Granular Style Customization
Target any visual element with precise control: Target any visual element with precise control:
```tsx ```tsx
const advancedStyles = { const customStyles = {
// Global defaults // Global defaults
heavenBeads: { fill: "#e74c3c", stroke: "#c0392b" }, heavenBeads: { fill: '#ff6b35' },
earthBeads: { fill: "#3498db", stroke: "#2980b9" }, earthBeads: { fill: '#3498db' },
activeBeads: { opacity: 1.0 }, activeBeads: { opacity: 1.0 },
inactiveBeads: { opacity: 0.3 }, inactiveBeads: { opacity: 0.3 },
// Column-specific overrides (by array index) // Column-specific overrides
columns: { columns: {
0: { 0: { // Hundreds column
// Leftmost column (highest place value) heavenBeads: { fill: '#e74c3c' },
heavenBeads: { fill: "#f39c12" }, earthBeads: { fill: '#2ecc71' }
earthBeads: { fill: "#e67e22" }, }
backgroundGlow: { fill: "#fff3cd", opacity: 0.3 },
},
}, },
// Individual bead targeting (by array index) // Individual bead targeting
beads: { beads: {
1: { 1: { // Middle column
// Middle column heaven: { fill: '#f39c12' },
heaven: { fill: "#9b59b6" },
earth: { earth: {
0: { fill: "#1abc9c" }, // First earth bead 0: { fill: '#1abc9c' }, // First earth bead
1: { fill: "#16a085" }, // Second earth bead 3: { fill: '#e67e22' } // Fourth earth bead
2: { fill: "#17a2b8" }, // Third earth bead }
3: { fill: "#138496" }, // Fourth earth bead }
},
},
}, },
// UI elements // UI elements
reckoningBar: { stroke: "#34495e", strokeWidth: 3 }, reckoningBar: { stroke: '#34495e', strokeWidth: 3 },
columnPosts: { stroke: "#7f8c8d", strokeWidth: 2 }, columnPosts: { stroke: '#7f8c8d' },
numerals: { numerals: {
color: "#2c3e50", color: '#2c3e50',
fontSize: "16px", fontSize: '14px',
fontFamily: "monospace", fontFamily: 'monospace'
fontWeight: "bold", }
},
}; };
<AbacusReact customStyles={advancedStyles} />; <AbacusReact customStyles={customStyles} />
``` ```
### Overlay System ### Tutorial and Overlay System
Create rich interactive educational experiences: Create interactive educational experiences:
```tsx ```tsx
const educationalOverlays = [ const overlays = [
{ {
id: "value-explanation", id: 'welcome-tooltip',
type: "tooltip", type: 'tooltip',
target: { type: "bead", columnIndex: 0, beadType: "heaven" }, target: {
type: 'bead',
columnIndex: 0,
beadType: 'earth',
beadPosition: 0
},
content: ( content: (
<div className="tutorial-tooltip"> <div style={{
<h4>Heaven Bead</h4> background: '#333',
<p>Worth 5 in this place value</p> color: 'white',
<button onClick={() => nextStep()}>Got it!</button> padding: '8px',
borderRadius: '4px'
}}>
Click me to start!
</div> </div>
), ),
offset: { x: 0, y: -40 }, offset: { x: 0, y: -30 }
}, }
{
id: "direction-arrow",
type: "arrow",
target: {
type: "bead",
columnIndex: 1,
beadType: "earth",
beadPosition: 0,
},
content: <div className="arrow-down"></div>,
offset: { x: 0, y: -20 },
},
]; ];
<AbacusReact <AbacusReact
overlays={educationalOverlays} overlays={overlays}
interactive={true} highlightBeads={[
{ columnIndex: 0, beadType: 'earth', position: 0 }
]}
callbacks={{ callbacks={{
onBeadClick: handleTutorialProgression, onBeadClick: (event) => {
if (event.columnIndex === 0 && event.beadType === 'earth' && event.position === 0) {
console.log('Tutorial step completed!');
}
}
}} }}
/>; />
```
### Dimension Calculation Hook
Get exact sizing information for layout planning:
```tsx
import { useAbacusDimensions } from "@soroban/abacus-react";
function ResponsiveAbacusContainer() {
const dimensions = useAbacusDimensions(
5, // columns
1.2, // scale factor
true, // show numbers
);
return (
<div
style={{
width: dimensions.width,
height: dimensions.height,
border: "1px solid #ccc",
padding: "10px",
}}
>
<AbacusReact
columns={5}
scaleFactor={1.2}
showNumbers={true}
value={12345}
/>
</div>
);
}
``` ```
### Bead Reference System ### Bead Reference System
@ -414,7 +257,7 @@ function ResponsiveAbacusContainer() {
Access individual bead DOM elements for advanced positioning: Access individual bead DOM elements for advanced positioning:
```tsx ```tsx
function AdvancedPositioning() { function AdvancedExample() {
const beadRefs = useRef(new Map<string, SVGElement>()); const beadRefs = useRef(new Map<string, SVGElement>());
const handleBeadRef = (bead: BeadConfig, element: SVGElement | null) => { const handleBeadRef = (bead: BeadConfig, element: SVGElement | null) => {
@ -422,21 +265,100 @@ function AdvancedPositioning() {
if (element) { if (element) {
beadRefs.current.set(key, element); beadRefs.current.set(key, element);
// Position custom elements relative to beads // Now you can position tooltips, highlights, etc. precisely
const rect = element.getBoundingClientRect(); const rect = element.getBoundingClientRect();
console.log(`Bead at column ${bead.columnIndex} positioned at:`, rect); console.log(`Bead at column ${bead.columnIndex} is at:`, rect);
} }
}; };
return ( return (
<AbacusReact callbacks={{ onBeadRef: handleBeadRef }} interactive={true} /> <AbacusReact
callbacks={{ onBeadRef: handleBeadRef }}
// ... other props
/>
);
}
```
## Hooks
### 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 (
<div style={{ width: dimensions.width, height: dimensions.height }}>
<AbacusReact columns={3} scaleFactor={1.2} />
</div>
);
}
```
## Educational Use Cases
### Interactive Math Lessons
```tsx
function MathLesson() {
const [problem, setProblem] = useState({ a: 23, b: 45 });
const [step, setStep] = useState('show-first');
return (
<div>
<h3>Add {problem.a} + {problem.b}</h3>
<AbacusReact
value={step === 'show-first' ? problem.a : 0}
interactive={step === 'add-second'}
callbacks={{
onValueChange: (value) => {
if (value === problem.a + problem.b) {
celebrate();
}
}
}}
/>
</div>
);
}
```
### 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 (
<AbacusReact
interactive={true}
callbacks={{ onBeadClick: checkAnswer }}
customStyles={getAnswerHighlighting(answers)}
/>
); );
} }
``` ```
## TypeScript Support ## TypeScript Support
Full TypeScript definitions with branded types for enhanced type safety: Full TypeScript definitions included:
```tsx ```tsx
import { import {
@ -447,191 +369,16 @@ import {
AbacusCustomStyles, AbacusCustomStyles,
AbacusOverlay, AbacusOverlay,
AbacusCallbacks, AbacusCallbacks,
useAbacusDimensions, useAbacusDimensions
PlaceValueBead, } from '@soroban/abacus-react';
ColumnIndexBead,
StepBeadHighlight,
PlaceValue,
ColumnIndex,
ValidPlaceValues,
EarthBeadPosition,
} from "@soroban/abacus-react";
// Branded types prevent mixing place values and column indices // All interfaces fully typed for excellent developer experience
const placeValue: ValidPlaceValues = 2; // hundreds place
const earthPosition: EarthBeadPosition = 3; // fourth earth bead
// Type-safe bead specification
const bead: PlaceValueBead = {
placeValue: 1, // tens place
beadType: "earth",
position: 2, // third earth bead
};
``` ```
## Educational Use Cases
### Interactive Math Lessons
```tsx
function AdditionLesson() {
const [problem] = useState({ a: 23, b: 45 });
const [step, setStep] = useState("show-first");
const [userValue, setUserValue] = useState(0);
const checkAnswer = (newValue: number) => {
setUserValue(newValue);
if (newValue === problem.a + problem.b) {
setStep("completed");
showCelebration();
}
};
return (
<div className="math-lesson">
<h3>
Add {problem.a} + {problem.b}
</h3>
<AbacusReact
value={step === "show-first" ? problem.a : userValue}
columns={3}
interactive={step === "user-input"}
animated={true}
showNumbers={true}
callbacks={{ onValueChange: checkAnswer }}
highlightBeads={step === "hint" ? getHintBeads() : []}
/>
{step === "completed" && (
<div className="success">
🎉 Correct! {problem.a} + {problem.b} = {problem.a + problem.b}
</div>
)}
</div>
);
}
```
### Assessment and Quizzing
```tsx
function AbacusQuiz() {
const [answers, setAnswers] = useState<BeadClickEvent[]>([]);
const [feedback, setFeedback] = useState<string>("");
const validateAnswer = (event: BeadClickEvent) => {
const isCorrect = checkBeadClick(event, expectedAnswer);
setAnswers((prev) => [...prev, event]);
if (isCorrect) {
setFeedback("Correct! Well done.");
advanceToNextQuestion();
} else {
setFeedback("Try again. Remember: this bead represents...");
showHint(event);
}
};
return (
<div className="abacus-quiz">
<AbacusReact
value={currentQuestionValue}
interactive={true}
callbacks={{ onBeadClick: validateAnswer }}
customStyles={getAnswerHighlighting(answers)}
overlays={currentHints}
/>
<div className="feedback">{feedback}</div>
</div>
);
}
```
## Color Schemes and Accessibility
### Built-in Color Schemes
- **`monochrome`** - Single color for all beads
- **`place-value`** - Different colors for each place value column
- **`alternating`** - Alternating colors between columns
- **`heaven-earth`** - Different colors for heaven vs earth beads
### Accessibility Palettes
- **`colorblind`** - High contrast, colorblind-friendly palette
- **`grayscale`** - Monochrome grayscale for maximum compatibility
- **`mnemonic`** - Colors that aid memory and learning
- **`nature`** - Earth-tone palette for reduced eye strain
```tsx
<AbacusReact
colorScheme="place-value"
colorPalette="colorblind"
value={12345}
columns={5}
/>
```
## Publishing and Versioning
This package uses [semantic-release](https://semantic-release.gitbook.io/) for automated publishing. Versions are determined by conventional commit messages:
### Commit Message Format
Use these prefixes for commits that affect the `packages/abacus-react` directory:
```bash
# New features (minor version bump)
feat(abacus-react): add gesture recognition system
# Bug fixes (patch version bump)
fix(abacus-react): resolve animation timing issues
# Performance improvements (patch version bump)
perf(abacus-react): optimize bead rendering performance
# Breaking changes (major version bump)
feat(abacus-react)!: redesign callback API
# or
feat(abacus-react): change component interface
BREAKING CHANGE: callback functions now receive different parameters
```
### Release Process
1. **Automatic**: Releases happen automatically when changes are pushed to `main` branch
2. **Dual publishing**: Package is published to both npm and GitHub Packages simultaneously
3. **Manual testing**: Run `pnpm release:dry-run` to test release without publishing
4. **Version tags**: Releases are tagged as `abacus-react-v1.2.3` (separate from monorepo versions)
### Development Commands
```bash
# Build the package
pnpm build
# Run tests
pnpm test:run
# Run Storybook locally
pnpm storybook
# Test release process (dry run)
pnpm release:dry-run
```
## Live Documentation
- **Storybook**: [Component examples and documentation](https://antialias.github.io/soroban-abacus-flashcards/abacus-react/)
- **Source Code**: [GitHub Repository](https://github.com/antialias/soroban-abacus-flashcards/tree/main/packages/abacus-react)
## Contributing ## Contributing
Contributions welcome! Please see our [contributing guidelines](../../CONTRIBUTING.md) and feel free to submit issues or pull requests. Contributions welcome! Please see our contributing guidelines and feel free to submit issues or pull requests.
## License ## License
MIT License - see [LICENSE](../../LICENSE) file for details. MIT License - see LICENSE file for details.

View File

@ -1,134 +1,133 @@
import type { Meta, StoryObj } from "@storybook/react"; import type { Meta, StoryObj } from '@storybook/react';
import { AbacusReact } from "./AbacusReact"; import { AbacusReact } from './AbacusReact';
import React from "react"; import React from 'react';
const meta: Meta<typeof AbacusReact> = { const meta: Meta<typeof AbacusReact> = {
title: "Examples/AbacusReact", title: 'Examples/AbacusReact',
component: AbacusReact, component: AbacusReact,
parameters: { parameters: {
layout: "centered", layout: 'centered',
docs: { docs: {
description: { description: {
component: component: 'Interactive Soroban (Japanese abacus) component with comprehensive customization options.'
"Interactive Soroban (Japanese abacus) component with comprehensive customization options.", }
}, }
},
}, },
tags: ["autodocs"], tags: ['autodocs'],
}; };
export default meta; export default meta;
type Story = StoryObj<typeof meta>; type Story = StoryObj<typeof meta>;
export const basicUsage: Story = { export const basicUsage: Story = {
name: "Basic Usage", name: 'Basic Usage',
args: { args: {
value: 123, "value": 123,
columns: 3, "columns": 3,
showNumbers: true, "showNumbers": true,
scaleFactor: 1, "scaleFactor": 1,
animated: false, "animated": false
}, },
parameters: { parameters: {
docs: { docs: {
description: { description: {
story: "Simple abacus showing a number", story: 'Simple abacus showing a number'
}, }
}, }
}, }
}; };
export const interactive: Story = { export const interactive: Story = {
name: "Interactive Mode", name: 'Interactive Mode',
args: { args: {
value: 456, "value": 456,
columns: 3, "columns": 3,
interactive: true, "interactive": true,
animated: false, "animated": false,
showNumbers: true, "showNumbers": true
}, },
parameters: { parameters: {
docs: { docs: {
description: { description: {
story: "Clickable abacus with animations", story: 'Clickable abacus with animations'
}, }
}, }
}, }
}; };
export const customStyling: Story = { export const customStyling: Story = {
name: "Custom Styling", name: 'Custom Styling',
args: { args: {
value: 789, "value": 789,
columns: 3, "columns": 3,
colorScheme: "place-value", "colorScheme": "place-value",
beadShape: "circle", "beadShape": "circle",
animated: false, "animated": false,
customStyles: { "customStyles": {
heavenBeads: { "heavenBeads": {
fill: "#ff6b35", "fill": "#ff6b35"
},
earthBeads: {
fill: "#3498db",
},
numerals: {
color: "#2c3e50",
fontWeight: "bold",
},
}, },
highlightBeads: [ "earthBeads": {
{ "fill": "#3498db"
columnIndex: 1, },
beadType: "heaven", "numerals": {
}, "color": "#2c3e50",
], "fontWeight": "bold"
}
}, },
"highlightBeads": [
{
"columnIndex": 1,
"beadType": "heaven"
}
]
},
parameters: { parameters: {
docs: { docs: {
description: { description: {
story: "Personalized colors and highlights", story: 'Personalized colors and highlights'
}, }
}, }
}, }
}; };
export const tutorialMode: Story = { export const tutorialMode: Story = {
name: "Tutorial System", name: 'Tutorial System',
args: { args: {
value: 42, "value": 42,
columns: 2, "columns": 2,
interactive: true, "interactive": true,
animated: false, "animated": false,
showNumbers: true, "showNumbers": true
}, },
parameters: { parameters: {
docs: { docs: {
description: { description: {
story: "Educational guidance with tooltips", story: 'Educational guidance with tooltips'
}, }
}, }
}, }
}; };
// Advanced tutorial example (from our previous implementation) // Advanced tutorial example (from our previous implementation)
export const TutorialExample: Story = { export const TutorialExample: Story = {
name: "Interactive Tutorial", name: 'Interactive Tutorial',
render: () => { render: () => {
const [step, setStep] = React.useState(0); const [step, setStep] = React.useState(0);
const [feedbackMessage, setFeedbackMessage] = React.useState(""); const [feedbackMessage, setFeedbackMessage] = React.useState('');
const tutorialSteps = [ const tutorialSteps = [
{ {
instruction: instruction: "Click the orange highlighted bead in the ones column (leftmost)",
"Click the orange highlighted bead in the ones column (leftmost)", highlightBeads: [{ columnIndex: 0, beadType: 'earth', position: 2 }],
highlightBeads: [{ columnIndex: 4, beadType: "earth", position: 2 }], value: 7
value: 7,
}, },
{ {
instruction: "Click anywhere in the ones column (leftmost column)", instruction: "Click anywhere in the ones column (leftmost column)",
highlightColumns: [0], highlightColumns: [0],
value: 7, value: 7
}, }
]; ];
const currentStep = tutorialSteps[step]; const currentStep = tutorialSteps[step];
@ -138,8 +137,7 @@ export const TutorialExample: Story = {
if (step === 0) { if (step === 0) {
const target = currentStep.highlightBeads?.[0]; const target = currentStep.highlightBeads?.[0];
isCorrectTarget = isCorrectTarget = target &&
target &&
event.columnIndex === target.columnIndex && event.columnIndex === target.columnIndex &&
event.beadType === target.beadType && event.beadType === target.beadType &&
event.position === target.position; event.position === target.position;
@ -149,25 +147,23 @@ export const TutorialExample: Story = {
if (isCorrectTarget && step < tutorialSteps.length - 1) { if (isCorrectTarget && step < tutorialSteps.length - 1) {
setStep(step + 1); setStep(step + 1);
setFeedbackMessage("✅ Correct! Moving to next step..."); setFeedbackMessage('✅ Correct! Moving to next step...');
setTimeout(() => setFeedbackMessage(""), 2000); setTimeout(() => setFeedbackMessage(''), 2000);
} else if (!isCorrectTarget) { } else if (!isCorrectTarget) {
setFeedbackMessage("⚠️ Try clicking the highlighted area."); setFeedbackMessage('⚠️ Try clicking the highlighted area.');
setTimeout(() => setFeedbackMessage(""), 3000); setTimeout(() => setFeedbackMessage(''), 3000);
} }
}; };
const handleRestart = () => { const handleRestart = () => {
setStep(0); setStep(0);
setFeedbackMessage(""); setFeedbackMessage('');
}; };
return ( return (
<div style={{ position: "relative", padding: "20px" }}> <div style={{ position: 'relative', padding: '20px' }}>
<div style={{ marginBottom: "20px", textAlign: "center" }}> <div style={{ marginBottom: '20px', textAlign: 'center' }}>
<h3> <h3>Interactive Tutorial - Step {step + 1} of {tutorialSteps.length}</h3>
Interactive Tutorial - Step {step + 1} of {tutorialSteps.length}
</h3>
<p>{currentStep.instruction}</p> <p>{currentStep.instruction}</p>
</div> </div>
@ -178,105 +174,91 @@ export const TutorialExample: Story = {
animated={true} animated={true}
showNumbers={true} showNumbers={true}
scaleFactor={1.2} scaleFactor={1.2}
highlightColumns={currentStep.highlightColumns} highlightColumns={currentStep.highlightColumns}
highlightBeads={currentStep.highlightBeads} highlightBeads={currentStep.highlightBeads}
customStyles={{ customStyles={{
beads: { beads: {
0: { 0: {
earth: { earth: {
2: 2: step === 0 ? { fill: '#ff6b35', stroke: '#d63031', strokeWidth: 2 } : undefined
step === 0 }
? { fill: "#ff6b35", stroke: "#d63031", strokeWidth: 2 } }
: undefined, }
},
},
},
}} }}
overlays={
step === 0 overlays={step === 0 ? [{
? [ id: 'tutorial-tip',
{ type: 'tooltip',
id: "tutorial-tip", target: {
type: "tooltip", type: 'bead',
target: { columnIndex: 0,
type: "bead", beadType: 'earth',
columnIndex: 4, beadPosition: 2
beadType: "earth", },
beadPosition: 2, content: (
}, <div style={{
content: ( background: '#333',
<div color: 'white',
style={{ padding: '8px',
background: "#333", borderRadius: '4px',
color: "white", fontSize: '12px',
padding: "8px", maxWidth: '120px',
borderRadius: "4px", textAlign: 'center'
fontSize: "12px", }}>
maxWidth: "120px", Click this bead!
textAlign: "center", <div style={{
}} position: 'absolute',
> bottom: '-6px',
Click this bead! left: '50%',
<div transform: 'translateX(-50%)',
style={{ borderTop: '6px solid #333',
position: "absolute", borderLeft: '6px solid transparent',
bottom: "-6px", borderRight: '6px solid transparent'
left: "50%", }} />
transform: "translateX(-50%)", </div>
borderTop: "6px solid #333", ),
borderLeft: "6px solid transparent", offset: { x: 0, y: -50 }
borderRight: "6px solid transparent", }] : []}
}}
/>
</div>
),
offset: { x: 0, y: -50 },
},
]
: []
}
callbacks={{ callbacks={{
onBeadClick: handleBeadClick, onBeadClick: handleBeadClick
}} }}
/> />
<div style={{ marginTop: "20px", textAlign: "center" }}> <div style={{ marginTop: '20px', textAlign: 'center' }}>
<button <button onClick={handleRestart} style={{
onClick={handleRestart} padding: '8px 16px',
style={{ backgroundColor: '#007bff',
padding: "8px 16px", color: 'white',
backgroundColor: "#007bff", border: 'none',
color: "white", borderRadius: '4px',
border: "none", cursor: 'pointer'
borderRadius: "4px", }}>
cursor: "pointer",
}}
>
Restart Tutorial Restart Tutorial
</button> </button>
</div> </div>
{feedbackMessage && ( {feedbackMessage && (
<div <div style={{
style={{ position: 'absolute',
position: "absolute", right: '-300px',
right: "-300px", top: '50%',
top: "50%", transform: 'translateY(-50%)',
transform: "translateY(-50%)", background: '#f8f9fa',
background: "#f8f9fa", border: '1px solid #dee2e6',
border: "1px solid #dee2e6", borderRadius: '4px',
borderRadius: "4px", padding: '12px',
padding: "12px", fontSize: '14px',
fontSize: "14px", maxWidth: '250px',
maxWidth: "250px", boxShadow: '0 2px 4px rgba(0,0,0,0.1)',
boxShadow: "0 2px 4px rgba(0,0,0,0.1)", transition: 'all 0.3s ease'
transition: "all 0.3s ease", }}>
}}
>
{feedbackMessage} {feedbackMessage}
</div> </div>
)} )}
</div> </div>
); );
}, }
}; };