import React, { useEffect, useState } from 'react'; import { useIntl, type IntlShape } from 'react-intl'; import { TransitionMotion, spring } from 'react-motion'; import { useSettings } from 'soapbox/hooks'; import { isNumber, roundDown } from 'soapbox/utils/numbers'; const obfuscatedCount = (count: number): string => { if (count < 0) { return '0'; } else if (count <= 1) { return count.toString(); } else { return '1+'; } }; const shortNumberFormat = (number: any, intl: IntlShape) => { if (!isNumber(number)) return '•'; let value = number; let factor: string = ''; if (number >= 1000 && number < 1000000) { factor = 'k'; value = roundDown(value / 1000); } else if (number >= 1000000) { factor = 'M'; value = roundDown(value / 1000000); } return intl.formatNumber(value, { maximumFractionDigits: 0, minimumFractionDigits: 0, maximumSignificantDigits: 3, numberingSystem: 'latn', style: 'decimal', }) + factor; }; interface IAnimatedNumber { value: number; obfuscate?: boolean; short?: boolean; } const AnimatedNumber: React.FC = ({ value, obfuscate, short }) => { const intl = useIntl(); const { reduceMotion } = useSettings(); const [direction, setDirection] = useState(1); const [displayedValue, setDisplayedValue] = useState(value); const [formattedValue, setFormattedValue] = useState(intl.formatNumber(value, { numberingSystem: 'latn' })); useEffect(() => { if (displayedValue !== undefined) { if (value > displayedValue) setDirection(1); else if (value < displayedValue) setDirection(-1); } setDisplayedValue(value); setFormattedValue(obfuscate ? obfuscatedCount(value) : short ? shortNumberFormat(value, intl) : intl.formatNumber(value, { numberingSystem: 'latn' })); }, [value]); const willEnter = () => ({ y: -1 * direction }); const willLeave = () => ({ y: spring(1 * direction, { damping: 35, stiffness: 400 }) }); if (reduceMotion) { return <>{formattedValue}; } const styles = [{ key: `${formattedValue}`, data: formattedValue, style: { y: spring(0, { damping: 35, stiffness: 400 }) }, }]; return ( {items => ( {items.map(({ key, data, style }) => ( 0 ? 'absolute' : 'static', transform: `translateY(${style.y * 100}%)` }} > {data} ))} )} ); }; export default AnimatedNumber;