73 lines
1.8 KiB
TypeScript
73 lines
1.8 KiB
TypeScript
import type { FC } from '../../lib/teact/teact';
|
|
import React, {
|
|
useEffect, useMemo, useRef,
|
|
} from '../../lib/teact/teact';
|
|
import { getGlobal } from '../../global';
|
|
|
|
import { selectCanAnimateInterface } from '../../global/selectors';
|
|
import buildClassName from '../../util/buildClassName';
|
|
|
|
import useFlag from '../../hooks/useFlag';
|
|
import useLang from '../../hooks/useLang';
|
|
|
|
import styles from './AnimatedCounter.module.scss';
|
|
|
|
type OwnProps = {
|
|
text: string;
|
|
className?: string;
|
|
};
|
|
|
|
const AnimatedCounter: FC<OwnProps> = ({
|
|
text,
|
|
className,
|
|
}) => {
|
|
const lang = useLang();
|
|
|
|
const prevTextRef = useRef<string>();
|
|
const [isAnimating, markAnimating, unmarkAnimating] = useFlag(false);
|
|
|
|
const shouldAnimate = selectCanAnimateInterface(getGlobal());
|
|
|
|
const textElement = useMemo(() => {
|
|
if (!shouldAnimate) {
|
|
return text;
|
|
}
|
|
if (!isAnimating) {
|
|
return prevTextRef.current || text;
|
|
}
|
|
|
|
const prevText = prevTextRef.current;
|
|
|
|
const elements = [];
|
|
for (let i = 0; i < text.length; i++) {
|
|
if (prevText && text[i] !== prevText[i]) {
|
|
elements.push(
|
|
<div className={styles.characterContainer}>
|
|
<div className={styles.character}>{text[i]}</div>
|
|
<div className={styles.characterOld} onAnimationEnd={unmarkAnimating}>{prevText[i]}</div>
|
|
<div className={styles.characterNew} onAnimationEnd={unmarkAnimating}>{text[i]}</div>
|
|
</div>,
|
|
);
|
|
} else {
|
|
elements.push(<span>{text[i]}</span>);
|
|
}
|
|
}
|
|
|
|
prevTextRef.current = text;
|
|
|
|
return elements;
|
|
}, [shouldAnimate, isAnimating, text]);
|
|
|
|
useEffect(() => {
|
|
markAnimating();
|
|
}, [text]);
|
|
|
|
return (
|
|
<span className={buildClassName(styles.root, className)} dir={lang.isRtl ? 'rtl' : undefined}>
|
|
{textElement}
|
|
</span>
|
|
);
|
|
};
|
|
|
|
export default AnimatedCounter;
|