59 lines
1.6 KiB
TypeScript
59 lines
1.6 KiB
TypeScript
import { useEffect, useState } from 'react';
|
|
|
|
import { animated, useSpring, config } from '@react-spring/web';
|
|
|
|
import { reduceMotion } from '../initial_state';
|
|
|
|
import { ShortNumber } from './short_number';
|
|
|
|
interface Props {
|
|
value: number;
|
|
}
|
|
export const AnimatedNumber: React.FC<Props> = ({ value }) => {
|
|
const [previousValue, setPreviousValue] = useState(value);
|
|
const direction = value > previousValue ? -1 : 1;
|
|
|
|
const [styles, api] = useSpring(
|
|
() => ({
|
|
from: { transform: `translateY(${100 * direction}%)` },
|
|
to: { transform: 'translateY(0%)' },
|
|
onRest() {
|
|
setPreviousValue(value);
|
|
},
|
|
config: { ...config.gentle, duration: 200 },
|
|
immediate: true, // This ensures that the animation is not played when the component is first rendered
|
|
}),
|
|
[value, previousValue],
|
|
);
|
|
|
|
// When the value changes, start the animation
|
|
useEffect(() => {
|
|
if (value !== previousValue) {
|
|
void api.start({ reset: true });
|
|
}
|
|
}, [api, previousValue, value]);
|
|
|
|
if (reduceMotion) {
|
|
return <ShortNumber value={value} />;
|
|
}
|
|
|
|
return (
|
|
<span className='animated-number'>
|
|
<animated.span style={styles}>
|
|
<ShortNumber value={value} />
|
|
</animated.span>
|
|
{value !== previousValue && (
|
|
<animated.span
|
|
style={{
|
|
...styles,
|
|
position: 'absolute',
|
|
top: `${-100 * direction}%`, // Adds extra space on top of translateY
|
|
}}
|
|
role='presentation'
|
|
>
|
|
<ShortNumber value={previousValue} />
|
|
</animated.span>
|
|
)}
|
|
</span>
|
|
);
|
|
};
|