📚 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:
parent
76163a0846
commit
af209fe6ac
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue