feat: integrate NumberFlow for smooth animated number display

- Replace react-spring value animation with NumberFlow component
- Add smooth digit-by-digit rolling animations for value changes
- Maintain consistent styling with inherit properties
- Improve user experience with professional number transitions

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Thomas Hallock
2025-09-15 18:11:29 -05:00
parent 1a26d8c4e1
commit e330d3539d
2 changed files with 24 additions and 32 deletions

View File

@@ -2,6 +2,7 @@
import { useState, useCallback, useMemo, useEffect, useRef } from 'react'
import { useSpring, animated } from '@react-spring/web'
import NumberFlow from '@number-flow/react'
import { css } from '../../styled-system/css'
import { TypstSoroban } from './TypstSoroban'
@@ -27,11 +28,7 @@ export function InteractiveAbacus({
const [previousValue, setPreviousValue] = useState(initialValue)
const svgRef = useRef<HTMLDivElement>(null)
// Animated value display
const valueSpring = useSpring({
value: currentValue,
config: { tension: 300, friction: 26 }
})
// Remove the old spring animation since we're using NumberFlow now
// Container animation for feedback
const containerSpring = useSpring({
@@ -249,7 +246,7 @@ export function InteractiveAbacus({
{/* Value Display */}
{showValue && (
<animated.div
<div
className={css({
fontSize: '3xl',
fontWeight: 'bold',
@@ -265,8 +262,15 @@ export function InteractiveAbacus({
boxShadow: '0 4px 12px rgba(59, 130, 246, 0.15)'
})}
>
{valueSpring.value.to(val => Math.round(val))}
</animated.div>
<NumberFlow
value={currentValue}
style={{
fontSize: 'inherit',
fontWeight: 'inherit',
color: 'inherit'
}}
/>
</div>
)}
{/* Controls */}

View File

@@ -855,18 +855,6 @@
}
@media (max-width: 768px) {
.\[\@media_\(max-width\:_768px\)\]\:gap_10px {
gap: 10px
}
}
@media (max-width: 480px) {
.\[\@media_\(max-width\:_480px\)\]\:gap_8px {
gap: 8px
}
}
@media (max-width: 768px) {
.\[\@media_\(max-width\:_768px\)\]\:h_130px {
height: 130px
}
@@ -899,18 +887,6 @@
min-width: 90px
}
}
@media (max-width: 768px) {
.\[\@media_\(max-width\:_768px\)\]\:fs_40px {
font-size: 40px
}
}
@media (max-width: 480px) {
.\[\@media_\(max-width\:_480px\)\]\:fs_32px {
font-size: 32px
}
}
@media screen and (min-width: 48em) {
.md\:px_4 {
@@ -933,6 +909,9 @@
}
@media (max-width: 768px) {
.\[\@media_\(max-width\:_768px\)\]\:gap_10px {
gap: 10px
}
.\[\@media_\(max-width\:_768px\)\]\:h_130px {
height: 130px
}
@@ -940,6 +919,9 @@
.\[\@media_\(max-width\:_768px\)\]\:min-w_100px {
min-width: 100px
}
.\[\@media_\(max-width\:_768px\)\]\:fs_40px {
font-size: 40px
}
.\[\@media_\(max-width\:_768px\)\]\:gap_10px {
gap: 10px
@@ -959,6 +941,9 @@
}
@media (max-width: 480px) {
.\[\@media_\(max-width\:_480px\)\]\:gap_8px {
gap: 8px
}
.\[\@media_\(max-width\:_480px\)\]\:h_120px {
height: 120px
}
@@ -966,6 +951,9 @@
.\[\@media_\(max-width\:_480px\)\]\:min-w_90px {
min-width: 90px
}
.\[\@media_\(max-width\:_480px\)\]\:fs_32px {
font-size: 32px
}
.\[\@media_\(max-width\:_480px\)\]\:gap_8px {
gap: 8px