diff --git a/src/components/TextRotater/TextRotater.jsx b/src/components/TextRotater/TextRotater.jsx index 8487804b06a7..9649b28b560f 100644 --- a/src/components/TextRotater/TextRotater.jsx +++ b/src/components/TextRotater/TextRotater.jsx @@ -1,104 +1,89 @@ import { clsx } from "clsx"; import PropTypes from "prop-types"; -import { Children, PureComponent, cloneElement } from "react"; +import { + Children, + cloneElement, + memo, + useCallback, + useEffect, + useRef, + useState, +} from "react"; -export default class TextRotater extends PureComponent { - static defaultProps = { - delay: 0, - repeatDelay: 3000, - }; +function TextRotater({ children, delay = 0, repeatDelay = 3000, maxWidth }) { + const [currentIndex, setCurrentIndex] = useState(0); + const [contentHeight, setContentHeight] = useState(0); + const [isAnimating, setIsAnimating] = useState(false); + const contentNodeRef = useRef(null); + const heightTimeoutRef = useRef(null); + const animationTimeoutRef = useRef(null); + const repeatTimeoutRef = useRef(null); - static propTypes = { - children: PropTypes.arrayOf(PropTypes.node), - delay: PropTypes.number, - repeatDelay: PropTypes.number, - // Needed to prevent jump when - // rotating between texts of different widths - maxWidth: PropTypes.number, - }; + const contentCallbackRef = useCallback((node) => { + contentNodeRef.current = node; + }, []); - state = { - currentIndex: 0, - contentHeight: 0, - isAnimating: false, - }; + useEffect(() => { + const calculateHeight = () => { + if (contentNodeRef.current) { + setContentHeight(contentNodeRef.current.clientHeight); + } + }; - render() { - const { children, maxWidth } = this.props; - const { currentIndex, contentHeight, isAnimating } = this.state; + heightTimeoutRef.current = setTimeout(calculateHeight, 50); + animationTimeoutRef.current = setTimeout(() => setIsAnimating(true), delay); + + window.addEventListener("resize", calculateHeight); + + return () => { + clearTimeout(heightTimeoutRef.current); + clearTimeout(animationTimeoutRef.current); + clearTimeout(repeatTimeoutRef.current); + window.removeEventListener("resize", calculateHeight); + }; + }, []); // eslint-disable-line react-hooks/exhaustive-deps + + const handleTransitionEnd = useCallback(() => { const childrenCount = Children.count(children); + setCurrentIndex((prev) => (prev + 1) % childrenCount); + setIsAnimating(false); - const currentChild = cloneElement(children[currentIndex], { - ref: (child) => (this.content = child), - }); + repeatTimeoutRef.current = setTimeout(() => { + setIsAnimating(true); + }, repeatDelay); + }, [children, repeatDelay]); - const nextChild = cloneElement( - children[(currentIndex + 1) % childrenCount], - ); + const childrenCount = Children.count(children); + const nextChild = cloneElement(children[(currentIndex + 1) % childrenCount]); - return ( + return ( +