Compare commits
8 Commits
abacus-rea
...
abacus-rea
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f01ef9479d | ||
|
|
fcbf0f5421 | ||
|
|
a79a97a5b9 | ||
|
|
de89dcddb3 | ||
|
|
195aff161b | ||
|
|
8ef57ccec5 | ||
|
|
bc7ca12158 | ||
|
|
8b8dfeefbd |
96
apps/web/scripts/validateTypstRefactoring.ts
Normal file
96
apps/web/scripts/validateTypstRefactoring.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
#!/usr/bin/env tsx
|
||||
/**
|
||||
* Validation script for typstHelpers refactoring
|
||||
*
|
||||
* Generates sample worksheets and verifies that the refactored code
|
||||
* produces identical Typst output to ensure no regressions.
|
||||
*/
|
||||
|
||||
import { generateSubtractionProblemStackFunction } from '../src/app/create/worksheets/addition/typstHelpers'
|
||||
import { generateTypstHelpers } from '../src/app/create/worksheets/addition/typstHelpers'
|
||||
import { generatePlaceValueColors } from '../src/app/create/worksheets/addition/typstHelpers'
|
||||
|
||||
console.log('🔍 Validating typstHelpers refactoring...\n')
|
||||
|
||||
// Test 1: Check that functions are exported and callable
|
||||
console.log('✓ Test 1: Functions are exported')
|
||||
console.log(` - generateSubtractionProblemStackFunction: ${typeof generateSubtractionProblemStackFunction}`)
|
||||
console.log(` - generateTypstHelpers: ${typeof generateTypstHelpers}`)
|
||||
console.log(` - generatePlaceValueColors: ${typeof generatePlaceValueColors}`)
|
||||
|
||||
if (typeof generateSubtractionProblemStackFunction !== 'function') {
|
||||
console.error('❌ generateSubtractionProblemStackFunction is not a function!')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
// Test 2: Generate sample Typst code
|
||||
console.log('\n✓ Test 2: Generate sample Typst code')
|
||||
const cellSize = 0.55
|
||||
const maxDigits = 3
|
||||
|
||||
const helpers = generateTypstHelpers(cellSize)
|
||||
console.log(` - Helper functions: ${helpers.length} characters`)
|
||||
|
||||
const colors = generatePlaceValueColors()
|
||||
console.log(` - Color definitions: ${colors.length} characters`)
|
||||
|
||||
const problemStack = generateSubtractionProblemStackFunction(cellSize, maxDigits)
|
||||
console.log(` - Problem stack function: ${problemStack.length} characters`)
|
||||
|
||||
// Test 3: Verify key features are present
|
||||
console.log('\n✓ Test 3: Verify key features in generated Typst')
|
||||
|
||||
const checks = [
|
||||
{ name: 'Borrow boxes row', pattern: /Borrow boxes row/ },
|
||||
{ name: 'Minuend row', pattern: /Minuend row/ },
|
||||
{ name: 'Subtrahend row', pattern: /Subtrahend row/ },
|
||||
{ name: 'Answer boxes', pattern: /Answer boxes/ },
|
||||
{ name: 'Ten-frames', pattern: /Ten-frames row/ },
|
||||
{ name: 'Borrowing hints', pattern: /show-borrowing-hints/ },
|
||||
{ name: 'Arrow rendering', pattern: /path\(/ },
|
||||
{ name: 'Place value colors', pattern: /place-colors/ },
|
||||
{ name: 'Scratch work boxes', pattern: /dotted.*paint: gray/ },
|
||||
]
|
||||
|
||||
let allPassed = true
|
||||
for (const check of checks) {
|
||||
const found = check.pattern.test(problemStack)
|
||||
if (found) {
|
||||
console.log(` ✓ ${check.name}`)
|
||||
} else {
|
||||
console.log(` ❌ ${check.name} - NOT FOUND`)
|
||||
allPassed = false
|
||||
}
|
||||
}
|
||||
|
||||
// Test 4: Verify structure
|
||||
console.log('\n✓ Test 4: Verify Typst structure')
|
||||
const structureChecks = [
|
||||
{ name: 'Function definition', pattern: /#let subtraction-problem-stack\(/ },
|
||||
{ name: 'Grid structure', pattern: /grid\(/ },
|
||||
{ name: 'Stack structure', pattern: /stack\(/ },
|
||||
{ name: 'Problem number display', pattern: /problem-number-display/ },
|
||||
]
|
||||
|
||||
for (const check of structureChecks) {
|
||||
const found = check.pattern.test(problemStack)
|
||||
if (found) {
|
||||
console.log(` ✓ ${check.name}`)
|
||||
} else {
|
||||
console.log(` ❌ ${check.name} - NOT FOUND`)
|
||||
allPassed = false
|
||||
}
|
||||
}
|
||||
|
||||
// Summary
|
||||
console.log('\n' + '='.repeat(60))
|
||||
if (allPassed) {
|
||||
console.log('✅ All validation checks passed!')
|
||||
console.log('\nThe refactored code generates valid Typst output with all')
|
||||
console.log('expected features present.')
|
||||
process.exit(0)
|
||||
} else {
|
||||
console.log('❌ Some validation checks failed!')
|
||||
console.log('\nPlease review the output above for details.')
|
||||
process.exit(1)
|
||||
}
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
generateProblemStackFunction,
|
||||
generateSubtractionProblemStackFunction,
|
||||
generateTypstHelpers,
|
||||
generatePlaceValueColors,
|
||||
} from '@/app/create/worksheets/addition/typstHelpers'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
@@ -90,6 +91,8 @@ function generateExampleTypst(config: ExampleRequest): string {
|
||||
#let show-ten-frames = ${showTenFrames ? 'true' : 'false'}
|
||||
#let show-ten-frames-for-all = ${showTenFramesForAll ? 'true' : 'false'}
|
||||
|
||||
${generatePlaceValueColors()}
|
||||
|
||||
${generateTypstHelpers(cellSize)}
|
||||
|
||||
${generateProblemStackFunction(cellSize, 3)}
|
||||
@@ -132,6 +135,8 @@ ${generateProblemStackFunction(cellSize, 3)}
|
||||
#let show-borrow-notation = ${showBorrowNotation ? 'true' : 'false'}
|
||||
#let show-borrowing-hints = ${showBorrowingHints ? 'true' : 'false'}
|
||||
|
||||
${generatePlaceValueColors()}
|
||||
|
||||
${generateTypstHelpers(cellSize)}
|
||||
|
||||
${generateSubtractionProblemStackFunction(cellSize, 3)}
|
||||
|
||||
@@ -3,9 +3,13 @@
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { execSync } from 'child_process'
|
||||
import { validateWorksheetConfig } from '@/app/create/worksheets/addition/validation'
|
||||
import { generateProblems } from '@/app/create/worksheets/addition/problemGenerator'
|
||||
import {
|
||||
generateProblems,
|
||||
generateSubtractionProblems,
|
||||
generateMixedProblems,
|
||||
} from '@/app/create/worksheets/addition/problemGenerator'
|
||||
import { generateTypstSource } from '@/app/create/worksheets/addition/typstGenerator'
|
||||
import type { WorksheetFormState } from '@/app/create/worksheets/addition/types'
|
||||
import type { WorksheetFormState, WorksheetProblem } from '@/app/create/worksheets/addition/types'
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
@@ -22,14 +26,37 @@ export async function POST(request: NextRequest) {
|
||||
|
||||
const config = validation.config
|
||||
|
||||
// Generate problems
|
||||
const problems = generateProblems(
|
||||
config.total,
|
||||
config.pAnyStart,
|
||||
config.pAllStart,
|
||||
config.interpolate,
|
||||
config.seed
|
||||
)
|
||||
// Generate problems based on operator type
|
||||
let problems: WorksheetProblem[]
|
||||
if (config.operator === 'addition') {
|
||||
problems = generateProblems(
|
||||
config.total,
|
||||
config.pAnyStart,
|
||||
config.pAllStart,
|
||||
config.interpolate,
|
||||
config.seed,
|
||||
config.digitRange
|
||||
)
|
||||
} else if (config.operator === 'subtraction') {
|
||||
problems = generateSubtractionProblems(
|
||||
config.total,
|
||||
config.digitRange,
|
||||
config.pAnyStart,
|
||||
config.pAllStart,
|
||||
config.interpolate,
|
||||
config.seed
|
||||
)
|
||||
} else {
|
||||
// mixed
|
||||
problems = generateMixedProblems(
|
||||
config.total,
|
||||
config.digitRange,
|
||||
config.pAnyStart,
|
||||
config.pAllStart,
|
||||
config.interpolate,
|
||||
config.seed
|
||||
)
|
||||
}
|
||||
|
||||
// Generate Typst sources (one per page)
|
||||
const typstSources = generateTypstSource(config, problems)
|
||||
|
||||
@@ -0,0 +1,349 @@
|
||||
# Subtraction Scaffolding Analysis & Smart Difficulty Integration
|
||||
|
||||
## Current State
|
||||
|
||||
### Subtraction-Specific Scaffolding Options
|
||||
|
||||
We have **two new subtraction-specific scaffolding options**:
|
||||
|
||||
1. **`showBorrowNotation`** (Manual mode only, line 332)
|
||||
- Shows dotted scratch boxes to the left of minuend digits that need borrowing
|
||||
- Visual space for students to write modified digit values (e.g., "12" when borrowing from tens to ones)
|
||||
- Background color comes from the place value being borrowed FROM
|
||||
|
||||
2. **`showBorrowingHints`** (Manual mode only, line 333)
|
||||
- Shows visual hints with arrows pointing to where students should write borrowed values
|
||||
- Displays "n-1" hints showing what to write in the borrow-from place
|
||||
- Includes curved arrows with arrowheads for clear visual guidance
|
||||
|
||||
### Current Integration Status
|
||||
|
||||
**✅ Works in Manual Mode:**
|
||||
- Both options available in manual mode schema (config-schemas.ts:332-333)
|
||||
- Both options properly passed to Typst rendering (typstGenerator.ts:114-115, 225-226)
|
||||
- Defaults: `showBorrowNotation: true`, `showBorrowingHints: false`
|
||||
|
||||
**❌ NOT Available in Smart Mode:**
|
||||
- Smart mode explicitly sets both to `false` (typstGenerator.ts:88-89)
|
||||
- Comments say: "Smart mode doesn't have borrow notation (yet)"
|
||||
- No conditional rules for these options in `DisplayRules` interface
|
||||
|
||||
## Gaps & Issues
|
||||
|
||||
### 1. **No Smart Mode Integration** ⚠️ CRITICAL
|
||||
|
||||
The subtraction scaffolding is **completely absent from smart difficulty mode**. This means:
|
||||
|
||||
- ❌ Smart mode worksheets never show borrow notation boxes
|
||||
- ❌ Smart mode worksheets never show borrowing hints
|
||||
- ❌ No way to progressively fade these scaffolds based on problem difficulty
|
||||
- ❌ Subtraction problems in smart mode have LESS scaffolding than addition problems
|
||||
|
||||
**Impact:** Smart mode is less useful for subtraction than for addition. Teachers using smart mode for subtraction get NO subtraction-specific scaffolding, making it harder for students to learn borrowing.
|
||||
|
||||
### 2. **Missing Display Rules**
|
||||
|
||||
The `DisplayRules` interface (displayRules.ts:14-21) only includes:
|
||||
- `carryBoxes` (addition-focused)
|
||||
- `answerBoxes`
|
||||
- `placeValueColors`
|
||||
- `tenFrames` (works for both, but addition-named)
|
||||
- `problemNumbers`
|
||||
- `cellBorders`
|
||||
|
||||
**Missing:**
|
||||
- `borrowNotation` - Conditional rules for scratch boxes
|
||||
- `borrowingHints` - Conditional rules for visual hints
|
||||
|
||||
### 3. **Problem Analysis is Good** ✅
|
||||
|
||||
`SubtractionProblemMeta` (problemAnalysis.ts:84-95) properly tracks:
|
||||
- `requiresBorrowing: boolean`
|
||||
- `borrowCount: number`
|
||||
- `borrowPlaces: PlaceValue[]`
|
||||
|
||||
This gives us the data we need to make smart decisions about when to show scaffolding.
|
||||
|
||||
### 4. **Rule Evaluation Works** ✅
|
||||
|
||||
The `evaluateRule()` function (displayRules.ts:36-57) already handles both addition and subtraction:
|
||||
- Line 45-48: Maps `requiresRegrouping` (addition) OR `requiresBorrowing` (subtraction)
|
||||
- Line 50-52: Maps `regroupCount` (addition) OR `borrowCount` (subtraction)
|
||||
|
||||
So the **infrastructure is ready** - we just need to add the rules.
|
||||
|
||||
## Recommendations
|
||||
|
||||
### Phase 1: Add Display Rules for Subtraction Scaffolding
|
||||
|
||||
**1. Extend `DisplayRules` interface:**
|
||||
|
||||
```typescript
|
||||
// displayRules.ts
|
||||
export interface DisplayRules {
|
||||
carryBoxes: RuleMode
|
||||
answerBoxes: RuleMode
|
||||
placeValueColors: RuleMode
|
||||
tenFrames: RuleMode
|
||||
problemNumbers: RuleMode
|
||||
cellBorders: RuleMode
|
||||
borrowNotation: RuleMode // NEW: Scratch boxes for borrowing work
|
||||
borrowingHints: RuleMode // NEW: Visual hints (arrows, "n-1")
|
||||
}
|
||||
```
|
||||
|
||||
**2. Update `ResolvedDisplayOptions`:**
|
||||
|
||||
```typescript
|
||||
// displayRules.ts
|
||||
export interface ResolvedDisplayOptions {
|
||||
showCarryBoxes: boolean
|
||||
showAnswerBoxes: boolean
|
||||
showPlaceValueColors: boolean
|
||||
showTenFrames: boolean
|
||||
showProblemNumbers: boolean
|
||||
showCellBorder: boolean
|
||||
showBorrowNotation: boolean // NEW
|
||||
showBorrowingHints: boolean // NEW
|
||||
}
|
||||
```
|
||||
|
||||
**3. Update `resolveDisplayForProblem()`:**
|
||||
|
||||
```typescript
|
||||
// displayRules.ts (line 70-77)
|
||||
const resolved = {
|
||||
showCarryBoxes: evaluateRule(rules.carryBoxes, problem),
|
||||
showAnswerBoxes: evaluateRule(rules.answerBoxes, problem),
|
||||
showPlaceValueColors: evaluateRule(rules.placeValueColors, problem),
|
||||
showTenFrames: evaluateRule(rules.tenFrames, problem),
|
||||
showProblemNumbers: evaluateRule(rules.problemNumbers, problem),
|
||||
showCellBorder: evaluateRule(rules.cellBorders, problem),
|
||||
showBorrowNotation: evaluateRule(rules.borrowNotation, problem), // NEW
|
||||
showBorrowingHints: evaluateRule(rules.borrowingHints, problem), // NEW
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 2: Update Config Schemas
|
||||
|
||||
**1. Add to Smart Mode schema:**
|
||||
|
||||
```typescript
|
||||
// config-schemas.ts (additionConfigV4SmartSchema, line 271-314)
|
||||
displayRules: z.object({
|
||||
carryBoxes: z.enum([...]),
|
||||
answerBoxes: z.enum([...]),
|
||||
placeValueColors: z.enum([...]),
|
||||
tenFrames: z.enum([...]),
|
||||
problemNumbers: z.enum([...]),
|
||||
cellBorders: z.enum([...]),
|
||||
borrowNotation: z.enum([ // NEW
|
||||
'always',
|
||||
'never',
|
||||
'whenRegrouping', // When any borrowing needed
|
||||
'whenMultipleRegroups', // When 2+ borrows
|
||||
'when3PlusDigits',
|
||||
]),
|
||||
borrowingHints: z.enum([ // NEW
|
||||
'always',
|
||||
'never',
|
||||
'whenRegrouping',
|
||||
'whenMultipleRegroups',
|
||||
'when3PlusDigits',
|
||||
]),
|
||||
}),
|
||||
```
|
||||
|
||||
**2. Update default config:**
|
||||
|
||||
```typescript
|
||||
// config-schemas.ts (defaultAdditionConfig, line 375-382)
|
||||
displayRules: {
|
||||
carryBoxes: 'whenRegrouping',
|
||||
answerBoxes: 'always',
|
||||
placeValueColors: 'always',
|
||||
tenFrames: 'whenRegrouping',
|
||||
problemNumbers: 'always',
|
||||
cellBorders: 'always',
|
||||
borrowNotation: 'whenRegrouping', // NEW: Show when borrowing needed
|
||||
borrowingHints: 'never', // NEW: Advanced feature, default off
|
||||
},
|
||||
```
|
||||
|
||||
### Phase 3: Update Scaffolding Progression
|
||||
|
||||
Add subtraction scaffolding to the pedagogical progression:
|
||||
|
||||
```typescript
|
||||
// difficultyProfiles.ts (SCAFFOLDING_PROGRESSION)
|
||||
export const SCAFFOLDING_PROGRESSION: DisplayRules[] = [
|
||||
// Level 0: Maximum scaffolding
|
||||
{
|
||||
carryBoxes: 'always',
|
||||
answerBoxes: 'always',
|
||||
placeValueColors: 'always',
|
||||
tenFrames: 'always',
|
||||
problemNumbers: 'always',
|
||||
cellBorders: 'always',
|
||||
borrowNotation: 'always', // NEW: Always show scratch boxes
|
||||
borrowingHints: 'always', // NEW: Always show hints
|
||||
},
|
||||
|
||||
// Level 1: Carry/borrow boxes become conditional
|
||||
{
|
||||
carryBoxes: 'whenRegrouping',
|
||||
borrowNotation: 'whenRegrouping', // NEW: Only when borrowing
|
||||
borrowingHints: 'always', // Still show hints
|
||||
// ... rest
|
||||
},
|
||||
|
||||
// Level 2: Hints become conditional
|
||||
{
|
||||
carryBoxes: 'whenRegrouping',
|
||||
borrowNotation: 'whenRegrouping',
|
||||
borrowingHints: 'whenRegrouping', // NEW: Only when borrowing
|
||||
// ... rest
|
||||
},
|
||||
|
||||
// Level 3-5: Keep both at whenRegrouping
|
||||
// ... (intermediate levels)
|
||||
|
||||
// Level 6: Hints become more conditional
|
||||
{
|
||||
borrowNotation: 'whenRegrouping',
|
||||
borrowingHints: 'whenMultipleRegroups', // NEW: Only complex problems
|
||||
// ... rest
|
||||
},
|
||||
|
||||
// Level 7+: Remove hints, keep notation
|
||||
{
|
||||
borrowNotation: 'whenRegrouping',
|
||||
borrowingHints: 'never', // NEW: No hints
|
||||
// ... rest
|
||||
},
|
||||
|
||||
// Level 10+: Remove all subtraction scaffolding
|
||||
{
|
||||
borrowNotation: 'never',
|
||||
borrowingHints: 'never',
|
||||
// ... rest
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
### Phase 4: Update Typst Generator
|
||||
|
||||
**Remove hardcoded false values:**
|
||||
|
||||
```typescript
|
||||
// typstGenerator.ts (line 88-89)
|
||||
// BEFORE:
|
||||
showBorrowNotation: false, // Smart mode doesn't have borrow notation (yet)
|
||||
showBorrowingHints: false, // Smart mode doesn't have borrowing hints (yet)
|
||||
|
||||
// AFTER:
|
||||
showBorrowNotation: displayOptions.showBorrowNotation, // Use resolved value
|
||||
showBorrowingHints: displayOptions.showBorrowingHints, // Use resolved value
|
||||
```
|
||||
|
||||
### Phase 5: Update UI Components
|
||||
|
||||
**1. Add controls in ConfigPanel** (if using smart mode):
|
||||
- Add "Borrow Notation" dropdown (always/never/whenBorrowing/etc.)
|
||||
- Add "Borrowing Hints" dropdown (always/never/whenBorrowing/etc.)
|
||||
- Only show when `operator` is 'subtraction' or 'mixed'
|
||||
|
||||
**2. Add preview in DisplayOptionsPreview:**
|
||||
- Show subtraction example with borrow notation enabled
|
||||
- Show subtraction example with borrowing hints enabled
|
||||
|
||||
## Pedagogical Rationale
|
||||
|
||||
### Why This Progression Makes Sense
|
||||
|
||||
1. **Early Learners (Levels 0-2):**
|
||||
- Show ALL scaffolding including hints with arrows
|
||||
- Students need maximum support to understand borrowing concept
|
||||
- Visual hints show "where to write what"
|
||||
|
||||
2. **Intermediate (Levels 3-6):**
|
||||
- Fade hints to only show when borrowing happens
|
||||
- Keep scratch boxes for all borrowing problems
|
||||
- Students understand concept but need workspace
|
||||
|
||||
3. **Advanced (Levels 7-9):**
|
||||
- Remove hints entirely (students know the pattern)
|
||||
- Keep scratch boxes for multi-borrow problems
|
||||
- Only show aids for complex problems
|
||||
|
||||
4. **Mastery (Level 10+):**
|
||||
- No subtraction-specific scaffolding
|
||||
- Students work problems independently
|
||||
- Standard worksheet format
|
||||
|
||||
### Parallel with Addition
|
||||
|
||||
This mirrors the addition progression:
|
||||
- Carry boxes fade from "always" → "whenRegrouping" → "whenMultipleRegroups" → "never"
|
||||
- Borrow notation should follow the same path
|
||||
- Borrowing hints are MORE specific than carry boxes (like ten-frames), so fade faster
|
||||
|
||||
## Implementation Priority
|
||||
|
||||
**High Priority:**
|
||||
1. ✅ Add `borrowNotation` and `borrowingHints` to `DisplayRules` interface
|
||||
2. ✅ Update schemas to include these rules in smart mode
|
||||
3. ✅ Remove hardcoded `false` values in typstGenerator
|
||||
4. ✅ Add to default config with sensible defaults
|
||||
|
||||
**Medium Priority:**
|
||||
5. ✅ Update scaffolding progression
|
||||
6. ✅ Add to difficulty profiles (earlyLearner, intermediate, etc.)
|
||||
|
||||
**Lower Priority:**
|
||||
7. ⚠️ Update UI components (ConfigPanel, DisplayOptionsPreview)
|
||||
8. ⚠️ Update documentation/help text
|
||||
|
||||
## Migration Strategy
|
||||
|
||||
**Good news:** This is backward compatible!
|
||||
|
||||
- **Manual mode** already has these options, no migration needed
|
||||
- **Smart mode V4** doesn't have these options yet, so adding them is purely additive
|
||||
- **Default values** will make existing configs work without changes:
|
||||
- `borrowNotation: 'whenRegrouping'` - reasonable default
|
||||
- `borrowingHints: 'never'` - conservative default (advanced feature)
|
||||
|
||||
**No schema version bump needed** - V4 smart mode can be extended with optional fields.
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
After implementation:
|
||||
|
||||
- [ ] Manual mode subtraction worksheets still show borrow notation
|
||||
- [ ] Manual mode can toggle borrowing hints on/off
|
||||
- [ ] Smart mode subtraction worksheets show borrow notation based on rules
|
||||
- [ ] Smart mode subtraction worksheets show hints based on rules
|
||||
- [ ] Addition worksheets unaffected (no regression)
|
||||
- [ ] Mixed worksheets apply correct rules per problem
|
||||
- [ ] Early learner profile shows max scaffolding for subtraction
|
||||
- [ ] Advanced profile shows minimal scaffolding for subtraction
|
||||
- [ ] Preview correctly shows/hides features based on rules
|
||||
- [ ] Saved configs load correctly with new fields
|
||||
|
||||
## Summary
|
||||
|
||||
**Current Status:** Subtraction scaffolding exists but is **manual-only**. Smart mode ignores these features entirely.
|
||||
|
||||
**Key Problem:** Smart difficulty mode is less effective for subtraction than addition because it lacks subtraction-specific scaffolding rules.
|
||||
|
||||
**Solution:** Extend the existing display rules system to include `borrowNotation` and `borrowingHints` as conditional options, following the same pedagogical progression as addition scaffolding.
|
||||
|
||||
**Effort:** Medium (2-3 hours)
|
||||
- Schema updates: 30 min
|
||||
- Display rules updates: 30 min
|
||||
- Scaffolding progression: 1 hour
|
||||
- UI updates: 1-2 hours
|
||||
- Testing: 1 hour
|
||||
|
||||
**Impact:** High - Makes smart mode equally effective for subtraction as it is for addition.
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
generateTypstHelpers,
|
||||
generateProblemStackFunction,
|
||||
generateSubtractionProblemStackFunction,
|
||||
generatePlaceValueColors,
|
||||
} from './typstHelpers'
|
||||
import { analyzeProblem, analyzeSubtractionProblem } from './problemAnalysis'
|
||||
import { resolveDisplayForProblem } from './displayRules'
|
||||
@@ -185,6 +186,8 @@ function generatePageTypst(
|
||||
: 'false'
|
||||
}
|
||||
|
||||
${generatePlaceValueColors()}
|
||||
|
||||
${generateTypstHelpers(cellSize)}
|
||||
|
||||
${generateProblemStackFunction(cellSize, maxDigits)}
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
// NOTE: This file now re-exports from the modular typstHelpers/ directory
|
||||
// for backward compatibility. New code should import from typstHelpers/ directly.
|
||||
|
||||
// Import types for internal use
|
||||
import type { DisplayOptions } from './typstHelpers/shared/types'
|
||||
|
||||
// Re-export everything from modular structure
|
||||
export type { DisplayOptions, CellDimensions } from './typstHelpers/shared/types'
|
||||
export { generateTypstHelpers } from './typstHelpers/shared/helpers'
|
||||
|
||||
@@ -1,3 +1,30 @@
|
||||
## [2.13.2](https://github.com/antialias/soroban-abacus-flashcards/compare/abacus-react-v2.13.1...abacus-react-v2.13.2) (2025-11-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **abacus-react:** remove duplicate numeral rendering and fix dark mode colors ([fcbf0f5](https://github.com/antialias/soroban-abacus-flashcards/commit/fcbf0f5421a790784dc7cc286f9082c6d46746ee))
|
||||
|
||||
## [2.13.1](https://github.com/antialias/soroban-abacus-flashcards/compare/abacus-react-v2.13.0...abacus-react-v2.13.1) (2025-11-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **abacus-react:** showNumbers prop was hardcoded to false, breaking numeral display ([de89dcd](https://github.com/antialias/soroban-abacus-flashcards/commit/de89dcddb3e6b5021cc0f90c68c4109f8f6c8907))
|
||||
|
||||
# [2.13.0](https://github.com/antialias/soroban-abacus-flashcards/compare/abacus-react-v2.12.0...abacus-react-v2.13.0) (2025-11-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add missing color definitions to example route ([bc7ca12](https://github.com/antialias/soroban-abacus-flashcards/commit/bc7ca12158a03c3e0bfe87f34b1c8ad399e27007))
|
||||
* PDF generation now respects operator and digitRange settings ([8b8dfee](https://github.com/antialias/soroban-abacus-flashcards/commit/8b8dfeefbdf2f75300b20ddf731677a627d50438))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **abacus-react:** add comprehensive Storybook stories for automatic theme detection ([8ef57cc](https://github.com/antialias/soroban-abacus-flashcards/commit/8ef57ccec5debaa0ffa1c0e36005bd478cde60f1))
|
||||
|
||||
# [2.12.0](https://github.com/antialias/soroban-abacus-flashcards/compare/abacus-react-v2.11.0...abacus-react-v2.12.0) (2025-11-08)
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,628 @@
|
||||
/**
|
||||
* Automatic Theme Detection
|
||||
* Features: useSystemTheme hook, automatic numeral color adjustment, manual overrides
|
||||
*/
|
||||
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import AbacusReact from "./AbacusReact";
|
||||
import { useSystemTheme, ABACUS_THEMES } from "./index";
|
||||
|
||||
const meta = {
|
||||
title: "AbacusReact/Theme Detection",
|
||||
component: AbacusReact,
|
||||
parameters: {
|
||||
layout: "centered",
|
||||
},
|
||||
tags: ["autodocs"],
|
||||
} satisfies Meta<typeof AbacusReact>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
// ============================================================================
|
||||
// AUTOMATIC THEME DETECTION
|
||||
// ============================================================================
|
||||
|
||||
function AutomaticThemeDemo() {
|
||||
const [pageTheme, setPageTheme] = useState<"light" | "dark">("light");
|
||||
|
||||
// Apply theme to document root (simulating parent app's theme system)
|
||||
useEffect(() => {
|
||||
document.documentElement.setAttribute("data-theme", pageTheme);
|
||||
}, [pageTheme]);
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
padding: "40px",
|
||||
minWidth: "600px",
|
||||
background: pageTheme === "dark" ? "#1a1a1a" : "#f5f5f5",
|
||||
borderRadius: "8px",
|
||||
transition: "background 0.3s ease",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
marginBottom: "30px",
|
||||
color: pageTheme === "dark" ? "white" : "black",
|
||||
}}
|
||||
>
|
||||
<h3 style={{ marginTop: 0 }}>Automatic Theme Detection</h3>
|
||||
<p>Numerals automatically adjust for optimal visibility</p>
|
||||
|
||||
<div style={{ marginTop: "20px" }}>
|
||||
<label>
|
||||
<input
|
||||
type="radio"
|
||||
name="theme"
|
||||
checked={pageTheme === "light"}
|
||||
onChange={() => setPageTheme("light")}
|
||||
/>{" "}
|
||||
Light Mode
|
||||
</label>
|
||||
{" "}
|
||||
<label>
|
||||
<input
|
||||
type="radio"
|
||||
name="theme"
|
||||
checked={pageTheme === "dark"}
|
||||
onChange={() => setPageTheme("dark")}
|
||||
/>{" "}
|
||||
Dark Mode
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: "20px" }}>
|
||||
<AbacusReact
|
||||
value={12345}
|
||||
columns={5}
|
||||
showNumbers={true}
|
||||
customStyles={
|
||||
pageTheme === "dark" ? ABACUS_THEMES.dark : ABACUS_THEMES.light
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
marginTop: "30px",
|
||||
padding: "15px",
|
||||
background: pageTheme === "dark" ? "#2a2a2a" : "#e5e5e5",
|
||||
borderRadius: "6px",
|
||||
color: pageTheme === "dark" ? "#ccc" : "#666",
|
||||
fontSize: "14px",
|
||||
}}
|
||||
>
|
||||
<strong>What's happening:</strong>
|
||||
<ul style={{ marginTop: "10px", marginBottom: 0 }}>
|
||||
<li>
|
||||
Page background changes with theme (dark = black, light = white)
|
||||
</li>
|
||||
<li>
|
||||
Abacus frame adapts (translucent white in dark, solid white in
|
||||
light)
|
||||
</li>
|
||||
<li>
|
||||
✨ Numerals stay dark (readable) regardless of page theme
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const AutomaticThemeDetection: Story = {
|
||||
render: () => <AutomaticThemeDemo />,
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// useSystemTheme HOOK DEMO
|
||||
// ============================================================================
|
||||
|
||||
function SystemThemeHookDemo() {
|
||||
const systemTheme = useSystemTheme(); // Use the hook directly
|
||||
const [pageTheme, setPageTheme] = useState<"light" | "dark">("light");
|
||||
|
||||
// Apply theme to document root
|
||||
useEffect(() => {
|
||||
document.documentElement.setAttribute("data-theme", pageTheme);
|
||||
}, [pageTheme]);
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
padding: "40px",
|
||||
minWidth: "600px",
|
||||
background: pageTheme === "dark" ? "#1a1a1a" : "#f5f5f5",
|
||||
borderRadius: "8px",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
marginBottom: "30px",
|
||||
color: pageTheme === "dark" ? "white" : "black",
|
||||
}}
|
||||
>
|
||||
<h3 style={{ marginTop: 0 }}>useSystemTheme Hook</h3>
|
||||
<p>Detect and respond to page theme changes</p>
|
||||
|
||||
<div style={{ marginTop: "20px" }}>
|
||||
<label>
|
||||
<input
|
||||
type="radio"
|
||||
name="theme"
|
||||
checked={pageTheme === "light"}
|
||||
onChange={() => setPageTheme("light")}
|
||||
/>{" "}
|
||||
Light Mode
|
||||
</label>
|
||||
{" "}
|
||||
<label>
|
||||
<input
|
||||
type="radio"
|
||||
name="theme"
|
||||
checked={pageTheme === "dark"}
|
||||
onChange={() => setPageTheme("dark")}
|
||||
/>{" "}
|
||||
Dark Mode
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
marginTop: "20px",
|
||||
padding: "15px",
|
||||
background: pageTheme === "dark" ? "#2a2a2a" : "#e5e5e5",
|
||||
borderRadius: "6px",
|
||||
}}
|
||||
>
|
||||
<code>
|
||||
const systemTheme = useSystemTheme(); // "{systemTheme}"
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<AbacusReact
|
||||
value={789}
|
||||
columns={3}
|
||||
showNumbers={true}
|
||||
customStyles={
|
||||
systemTheme === "dark" ? ABACUS_THEMES.dark : ABACUS_THEMES.light
|
||||
}
|
||||
/>
|
||||
|
||||
<div
|
||||
style={{
|
||||
marginTop: "30px",
|
||||
padding: "15px",
|
||||
background: pageTheme === "dark" ? "#2a2a2a" : "#e5e5e5",
|
||||
borderRadius: "6px",
|
||||
color: pageTheme === "dark" ? "#ccc" : "#666",
|
||||
fontSize: "14px",
|
||||
}}
|
||||
>
|
||||
<strong>Hook detects:</strong>
|
||||
<ul style={{ marginTop: "10px", marginBottom: 0 }}>
|
||||
<li>
|
||||
<code>data-theme="light"</code> or <code>data-theme="dark"</code>{" "}
|
||||
attribute on <code><html></code>
|
||||
</li>
|
||||
<li>
|
||||
<code>.light</code> or <code>.dark</code> class on{" "}
|
||||
<code><html></code>
|
||||
</li>
|
||||
<li>Updates automatically using MutationObserver</li>
|
||||
<li>SSR-safe with default fallback</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const UseSystemThemeHook: Story = {
|
||||
render: () => <SystemThemeHookDemo />,
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// MANUAL OVERRIDE
|
||||
// ============================================================================
|
||||
|
||||
function ManualOverrideDemo() {
|
||||
const [pageTheme, setPageTheme] = useState<"light" | "dark">("light");
|
||||
|
||||
// Apply theme to document root
|
||||
useEffect(() => {
|
||||
document.documentElement.setAttribute("data-theme", pageTheme);
|
||||
}, [pageTheme]);
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
padding: "40px",
|
||||
minWidth: "600px",
|
||||
background: pageTheme === "dark" ? "#1a1a1a" : "#f5f5f5",
|
||||
borderRadius: "8px",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
marginBottom: "30px",
|
||||
color: pageTheme === "dark" ? "white" : "black",
|
||||
}}
|
||||
>
|
||||
<h3 style={{ marginTop: 0 }}>Manual Color Override</h3>
|
||||
<p>Custom numeral colors override automatic detection</p>
|
||||
|
||||
<div style={{ marginTop: "20px" }}>
|
||||
<label>
|
||||
<input
|
||||
type="radio"
|
||||
name="theme"
|
||||
checked={pageTheme === "light"}
|
||||
onChange={() => setPageTheme("light")}
|
||||
/>{" "}
|
||||
Light Mode
|
||||
</label>
|
||||
{" "}
|
||||
<label>
|
||||
<input
|
||||
type="radio"
|
||||
name="theme"
|
||||
checked={pageTheme === "dark"}
|
||||
onChange={() => setPageTheme("dark")}
|
||||
/>{" "}
|
||||
Dark Mode
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: "40px" }}>
|
||||
<h4
|
||||
style={{
|
||||
color: pageTheme === "dark" ? "white" : "black",
|
||||
marginTop: 0,
|
||||
}}
|
||||
>
|
||||
Default (automatic):
|
||||
</h4>
|
||||
<AbacusReact
|
||||
value={456}
|
||||
columns={3}
|
||||
showNumbers={true}
|
||||
customStyles={
|
||||
pageTheme === "dark" ? ABACUS_THEMES.dark : ABACUS_THEMES.light
|
||||
}
|
||||
/>
|
||||
<p
|
||||
style={{
|
||||
marginTop: "10px",
|
||||
fontSize: "14px",
|
||||
color: pageTheme === "dark" ? "#ccc" : "#666",
|
||||
}}
|
||||
>
|
||||
Numerals use automatic color (dark text on light frame)
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: "40px" }}>
|
||||
<h4
|
||||
style={{
|
||||
color: pageTheme === "dark" ? "white" : "black",
|
||||
marginTop: 0,
|
||||
}}
|
||||
>
|
||||
Custom red numerals:
|
||||
</h4>
|
||||
<AbacusReact
|
||||
value={456}
|
||||
columns={3}
|
||||
showNumbers={true}
|
||||
customStyles={{
|
||||
...(pageTheme === "dark" ? ABACUS_THEMES.dark : ABACUS_THEMES.light),
|
||||
numerals: {
|
||||
color: "#ef4444", // Red
|
||||
fontWeight: "700",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<p
|
||||
style={{
|
||||
marginTop: "10px",
|
||||
fontSize: "14px",
|
||||
color: pageTheme === "dark" ? "#ccc" : "#666",
|
||||
}}
|
||||
>
|
||||
Custom color overrides automatic detection
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4
|
||||
style={{
|
||||
color: pageTheme === "dark" ? "white" : "black",
|
||||
marginTop: 0,
|
||||
}}
|
||||
>
|
||||
Custom blue numerals:
|
||||
</h4>
|
||||
<AbacusReact
|
||||
value={456}
|
||||
columns={3}
|
||||
showNumbers={true}
|
||||
customStyles={{
|
||||
...(pageTheme === "dark" ? ABACUS_THEMES.dark : ABACUS_THEMES.light),
|
||||
numerals: {
|
||||
color: "#3b82f6", // Blue
|
||||
fontWeight: "600",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<p
|
||||
style={{
|
||||
marginTop: "10px",
|
||||
fontSize: "14px",
|
||||
color: pageTheme === "dark" ? "#ccc" : "#666",
|
||||
}}
|
||||
>
|
||||
Any color can be used for special effects
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const ManualColorOverride: Story = {
|
||||
render: () => <ManualOverrideDemo />,
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// COMPARISON: WITH vs WITHOUT NUMERALS
|
||||
// ============================================================================
|
||||
|
||||
function NumeralsComparisonDemo() {
|
||||
const [pageTheme, setPageTheme] = useState<"light" | "dark">("dark");
|
||||
|
||||
useEffect(() => {
|
||||
document.documentElement.setAttribute("data-theme", pageTheme);
|
||||
}, [pageTheme]);
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
padding: "40px",
|
||||
minWidth: "700px",
|
||||
background: pageTheme === "dark" ? "#1a1a1a" : "#f5f5f5",
|
||||
borderRadius: "8px",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
marginBottom: "30px",
|
||||
color: pageTheme === "dark" ? "white" : "black",
|
||||
}}
|
||||
>
|
||||
<h3 style={{ marginTop: 0 }}>Numerals On vs Off</h3>
|
||||
<p>Compare visibility with and without numeral labels</p>
|
||||
|
||||
<div style={{ marginTop: "20px" }}>
|
||||
<label>
|
||||
<input
|
||||
type="radio"
|
||||
name="theme"
|
||||
checked={pageTheme === "light"}
|
||||
onChange={() => setPageTheme("light")}
|
||||
/>{" "}
|
||||
Light Mode
|
||||
</label>
|
||||
{" "}
|
||||
<label>
|
||||
<input
|
||||
type="radio"
|
||||
name="theme"
|
||||
checked={pageTheme === "dark"}
|
||||
onChange={() => setPageTheme("dark")}
|
||||
/>{" "}
|
||||
Dark Mode
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: "1fr 1fr",
|
||||
gap: "30px",
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<h4
|
||||
style={{
|
||||
color: pageTheme === "dark" ? "white" : "black",
|
||||
marginTop: 0,
|
||||
}}
|
||||
>
|
||||
Without numerals:
|
||||
</h4>
|
||||
<AbacusReact
|
||||
value={8765}
|
||||
columns={4}
|
||||
showNumbers={false}
|
||||
customStyles={
|
||||
pageTheme === "dark" ? ABACUS_THEMES.dark : ABACUS_THEMES.light
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4
|
||||
style={{
|
||||
color: pageTheme === "dark" ? "white" : "black",
|
||||
marginTop: 0,
|
||||
}}
|
||||
>
|
||||
With numerals:
|
||||
</h4>
|
||||
<AbacusReact
|
||||
value={8765}
|
||||
columns={4}
|
||||
showNumbers={true}
|
||||
customStyles={
|
||||
pageTheme === "dark" ? ABACUS_THEMES.dark : ABACUS_THEMES.light
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
marginTop: "30px",
|
||||
padding: "15px",
|
||||
background: pageTheme === "dark" ? "#2a2a2a" : "#e5e5e5",
|
||||
borderRadius: "6px",
|
||||
color: pageTheme === "dark" ? "#ccc" : "#666",
|
||||
fontSize: "14px",
|
||||
}}
|
||||
>
|
||||
<strong>Note:</strong> Numerals remain visible in both light and dark
|
||||
modes thanks to automatic theme detection. They always use dark color
|
||||
since the abacus frame is light/translucent.
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const NumeralsComparison: Story = {
|
||||
render: () => <NumeralsComparisonDemo />,
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// REAL-WORLD EXAMPLE: EDUCATIONAL APP
|
||||
// ============================================================================
|
||||
|
||||
function EducationalAppDemo() {
|
||||
const [pageTheme, setPageTheme] = useState<"light" | "dark">("light");
|
||||
const [currentValue, setCurrentValue] = useState(234);
|
||||
|
||||
useEffect(() => {
|
||||
document.documentElement.setAttribute("data-theme", pageTheme);
|
||||
}, [pageTheme]);
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
padding: "40px",
|
||||
minWidth: "700px",
|
||||
background: pageTheme === "dark" ? "#0f172a" : "#ffffff",
|
||||
borderRadius: "8px",
|
||||
boxShadow: "0 4px 6px rgba(0,0,0,0.1)",
|
||||
}}
|
||||
>
|
||||
{/* App Header */}
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
marginBottom: "30px",
|
||||
paddingBottom: "20px",
|
||||
borderBottom: `2px solid ${pageTheme === "dark" ? "#334155" : "#e5e7eb"}`,
|
||||
}}
|
||||
>
|
||||
<h2
|
||||
style={{
|
||||
margin: 0,
|
||||
color: pageTheme === "dark" ? "white" : "black",
|
||||
}}
|
||||
>
|
||||
Math Learning App
|
||||
</h2>
|
||||
<button
|
||||
onClick={() => setPageTheme(pageTheme === "dark" ? "light" : "dark")}
|
||||
style={{
|
||||
padding: "8px 16px",
|
||||
borderRadius: "6px",
|
||||
border: "none",
|
||||
background: pageTheme === "dark" ? "#475569" : "#e5e7eb",
|
||||
color: pageTheme === "dark" ? "white" : "black",
|
||||
cursor: "pointer",
|
||||
fontSize: "14px",
|
||||
}}
|
||||
>
|
||||
{pageTheme === "dark" ? "☀️ Light" : "🌙 Dark"}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Lesson Content */}
|
||||
<div style={{ color: pageTheme === "dark" ? "#e2e8f0" : "#374151" }}>
|
||||
<h3 style={{ marginTop: 0 }}>Today's Lesson: Place Value</h3>
|
||||
<p>Learn to represent numbers on a soroban abacus!</p>
|
||||
</div>
|
||||
|
||||
{/* Interactive Abacus */}
|
||||
<div
|
||||
style={{
|
||||
marginTop: "30px",
|
||||
padding: "30px",
|
||||
background: pageTheme === "dark" ? "#1e293b" : "#f9fafb",
|
||||
borderRadius: "8px",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
marginBottom: "20px",
|
||||
color: pageTheme === "dark" ? "white" : "black",
|
||||
}}
|
||||
>
|
||||
<label>
|
||||
Current Number: {currentValue}
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="999"
|
||||
value={currentValue}
|
||||
onChange={(e) => setCurrentValue(Number(e.target.value))}
|
||||
style={{ marginLeft: "15px", width: "200px" }}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<AbacusReact
|
||||
value={currentValue}
|
||||
columns={3}
|
||||
showNumbers={true}
|
||||
columnLabels={["ones", "tens", "hundreds"]}
|
||||
customStyles={
|
||||
pageTheme === "dark" ? ABACUS_THEMES.dark : ABACUS_THEMES.light
|
||||
}
|
||||
interactive={true}
|
||||
onValueChange={setCurrentValue}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Info Box */}
|
||||
<div
|
||||
style={{
|
||||
marginTop: "30px",
|
||||
padding: "20px",
|
||||
background: pageTheme === "dark" ? "#064e3b" : "#d1fae5",
|
||||
borderRadius: "8px",
|
||||
color: pageTheme === "dark" ? "#6ee7b7" : "#065f46",
|
||||
}}
|
||||
>
|
||||
<strong>✨ Theme-Aware Design:</strong>
|
||||
<ul style={{ marginTop: "10px", marginBottom: 0 }}>
|
||||
<li>Entire app responds to light/dark mode toggle</li>
|
||||
<li>Abacus frame adapts to page background</li>
|
||||
<li>Numerals always remain readable</li>
|
||||
<li>No manual color configuration needed!</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const EducationalAppExample: Story = {
|
||||
render: () => <EducationalAppDemo />,
|
||||
};
|
||||
@@ -2312,7 +2312,7 @@ export const AbacusReact: React.FC<AbacusConfig> = ({
|
||||
colorPalette={finalConfig.colorPalette}
|
||||
hideInactiveBeads={finalConfig.hideInactiveBeads}
|
||||
frameVisible={finalConfig.frameVisible}
|
||||
showNumbers={false}
|
||||
showNumbers={finalConfig.showNumbers}
|
||||
customStyles={themeAwareCustomStyles}
|
||||
interactive={finalConfig.interactive}
|
||||
highlightColumns={highlightColumns}
|
||||
@@ -2431,6 +2431,7 @@ export const AbacusReact: React.FC<AbacusConfig> = ({
|
||||
fontFamily: "monospace",
|
||||
fontWeight: "bold",
|
||||
fontSize: `${Math.max(8, 14 * finalConfig.scaleFactor)}px`,
|
||||
color: themeAwareCustomStyles?.numerals?.color || "rgba(0, 0, 0, 0.8)",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -431,7 +431,9 @@ export function AbacusSVGRenderer({
|
||||
})}
|
||||
|
||||
{/* Column numbers */}
|
||||
{showNumbers &&
|
||||
{/* NumberFlow numerals are now rendered in AbacusReact.tsx, not here */}
|
||||
{/* Keeping this code commented for reference - SVG text numerals replaced by NumberFlow */}
|
||||
{false && showNumbers &&
|
||||
beadConfigs.map((_, colIndex) => {
|
||||
const placeValue = columns - 1 - colIndex;
|
||||
const columnState = state[placeValue] || {
|
||||
|
||||
Reference in New Issue
Block a user