Improve and enable animated number display

Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
marcin mikołajczak 2024-05-05 14:46:22 +02:00
parent 5a8a13a136
commit 3e8989c0b0
4 changed files with 48 additions and 18 deletions

View file

@ -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>

View file

@ -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>
);
};

View file

@ -3,10 +3,10 @@ import { FormattedMessage } from 'react-intl';
import { Link } from 'react-router-dom';
import { openModal } from 'soapbox/actions/modals';
import AnimatedNumber from 'soapbox/components/animated-number';
import { HStack, Text, Emoji } from 'soapbox/components/ui';
import { useAppSelector, useSoapboxConfig, useFeatures, useAppDispatch } from 'soapbox/hooks';
import { reduceEmoji } from 'soapbox/utils/emoji-reacts';
import { shortNumberFormat } from 'soapbox/utils/numbers';
import type { Status } from 'soapbox/types/entities';
@ -218,7 +218,8 @@ const InteractionCounter: React.FC<IInteractionCounter> = ({ count, children, on
const body = (
<HStack space={1} alignItems='center'>
<Text weight='bold'>
{shortNumberFormat(count)}
<AnimatedNumber value={count} short />
{/* {shortNumberFormat(count)} */}
</Text>
<Text tag='div' theme='muted'>

View file

@ -10,7 +10,7 @@ export const realNumberSchema = z.coerce.number().refine(n => !isNaN(n));
export const secondsToDays = (seconds: number) => Math.floor(seconds / (3600 * 24));
const roundDown = (num: number) => {
export const roundDown = (num: number) => {
if (num >= 100 && num < 1000) {
num = Math.floor(num);
}