Improve and enable animated number display
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
@ -1,36 +1,68 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { FormattedNumber } from 'react-intl';
|
||||
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) => {
|
||||
const obfuscatedCount = (count: number): string => {
|
||||
if (count < 0) {
|
||||
return 0;
|
||||
return '0';
|
||||
} else if (count <= 1) {
|
||||
return count;
|
||||
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<IAnimatedNumber> = ({ value, obfuscate }) => {
|
||||
const AnimatedNumber: React.FC<IAnimatedNumber> = ({ value, obfuscate, short }) => {
|
||||
const intl = useIntl();
|
||||
const { reduceMotion } = useSettings();
|
||||
|
||||
const [direction, setDirection] = useState(1);
|
||||
const [displayedValue, setDisplayedValue] = useState<number>(value);
|
||||
const [formattedValue, setFormattedValue] = useState<string>(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 });
|
||||
@ -38,14 +70,12 @@ const AnimatedNumber: React.FC<IAnimatedNumber> = ({ value, obfuscate }) => {
|
||||
const willLeave = () => ({ y: spring(1 * direction, { damping: 35, stiffness: 400 }) });
|
||||
|
||||
if (reduceMotion) {
|
||||
return obfuscate
|
||||
? <>{obfuscatedCount(displayedValue)}</>
|
||||
: <FormattedNumber value={displayedValue} numberingSystem='latn' />;
|
||||
return <>{formattedValue}</>;
|
||||
}
|
||||
|
||||
const styles = [{
|
||||
key: `${displayedValue}`,
|
||||
data: displayedValue,
|
||||
key: `${formattedValue}`,
|
||||
data: formattedValue,
|
||||
style: { y: spring(0, { damping: 35, stiffness: 400 }) },
|
||||
}];
|
||||
|
||||
@ -58,9 +88,7 @@ const AnimatedNumber: React.FC<IAnimatedNumber> = ({ value, obfuscate }) => {
|
||||
key={key}
|
||||
style={{ position: (direction * style.y) > 0 ? 'absolute' : 'static', transform: `translateY(${style.y * 100}%)` }}
|
||||
>
|
||||
{obfuscate
|
||||
? obfuscatedCount(data)
|
||||
: <FormattedNumber value={data} numberingSystem='latn' />}
|
||||
{data}
|
||||
</span>
|
||||
))}
|
||||
</span>
|
||||
|
||||
@ -3,7 +3,8 @@ import React from 'react';
|
||||
|
||||
import { Text, Icon, Emoji } from 'soapbox/components/ui';
|
||||
import { useSettings } from 'soapbox/hooks';
|
||||
import { shortNumberFormat } from 'soapbox/utils/numbers';
|
||||
|
||||
import AnimatedNumber from './animated-number';
|
||||
|
||||
import type { EmojiReaction } from 'soapbox/schemas';
|
||||
|
||||
@ -24,7 +25,7 @@ const StatusActionCounter: React.FC<IStatusActionCounter> = ({ count = 0 }): JSX
|
||||
|
||||
return (
|
||||
<Text size='xs' weight='semibold' theme='inherit'>
|
||||
{demetricator && count > 1 ? '1+' : shortNumberFormat(count)}
|
||||
<AnimatedNumber value={count} obfuscate={demetricator} short />
|
||||
</Text>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user