diff --git a/static/app/components/pageOverlay.tsx b/static/app/components/pageOverlay.tsx index 3ae11f3c18b270..b1f13cce724f8f 100644 --- a/static/app/components/pageOverlay.tsx +++ b/static/app/components/pageOverlay.tsx @@ -1,4 +1,4 @@ -import {useEffect, useRef} from 'react'; +import {Component, createRef} from 'react'; import {css} from '@emotion/react'; import styled from '@emotion/styled'; import {motion} from 'framer-motion'; @@ -105,6 +105,8 @@ type Props = { customWrapper?: React.ComponentType; }; +type DefaultProps = Pick; + /** * When a background with a anchor is used and no positioningStrategy is * provided, by default we'll align the top left of the container to the anchor @@ -127,96 +129,109 @@ const defaultPositioning = ({mainRect, anchorRect}: PositioningStrategyOpts) => * wrapper to a safe space in the background to aid in alignment of the wrapper * to a safe space in the background. */ -function PageOverlay({ - positioningStrategy = defaultPositioning, - text, - animateDelay, - background: BackgroundComponent, - customWrapper, - children, -}: Props) { - const contentRef = useRef(null); - const wrapperRef = useRef(null); - const anchorRef = useRef(null); - - useEffect(() => { - if (contentRef.current === null || anchorRef.current === null) { - return () => {}; +class PageOverlay extends Component { + static defaultProps: DefaultProps = { + positioningStrategy: defaultPositioning, + }; + + componentDidMount() { + if (this.contentRef.current === null || this.anchorRef.current === null) { + return; } - /** - * Align the wrapper component to the anchor by computing x/y values using - * the passed function. By default if no function is specified it will align - * to the top left of the anchor. - */ - function anchorWrapper() { - if ( - contentRef.current === null || - wrapperRef.current === null || - anchorRef.current === null - ) { - return; - } - - // Absolute position the container, this avoids the browser having to reflow - // the component - wrapperRef.current.style.position = 'absolute'; - wrapperRef.current.style.left = `0px`; - wrapperRef.current.style.top = `0px`; - - const mainRect = contentRef.current.getBoundingClientRect(); - const anchorRect = anchorRef.current.getBoundingClientRect(); - const wrapperRect = wrapperRef.current.getBoundingClientRect(); - - // Compute the position of the wrapper - const {x, y} = positioningStrategy({mainRect, anchorRect, wrapperRect}); - - const transform = `translate(${Math.round(x)}px, ${Math.round(y)}px)`; - wrapperRef.current.style.transform = transform; + this.anchorWrapper(); + + // Observe changes to the upsell container to reanchor if available + if (window.ResizeObserver) { + this.bgResizeObserver = new ResizeObserver(this.anchorWrapper); + this.bgResizeObserver.observe(this.contentRef.current); } + } + + componentWillUnmount() { + this.bgResizeObserver?.disconnect(); + } - anchorWrapper(); + /** + * Used to re-anchor the text wrapper to the anchor point in the background when + * the size of the page changes. + */ + bgResizeObserver: ResizeObserver | null = null; - /** - * Used to re-anchor the text wrapper to the anchor point in the background when - * the size of the page changes. - */ - let bgResizeObserver: ResizeObserver | null = null; + contentRef = createRef(); + wrapperRef = createRef(); + anchorRef = createRef(); - // Observe changes to the upsell container to reanchor if available - if (window.ResizeObserver) { - bgResizeObserver = new ResizeObserver(anchorWrapper); - bgResizeObserver.observe(contentRef.current); + /** + * Align the wrapper component to the anchor by computing x/y values using + * the passed function. By default if no function is specified it will align + * to the top left of the anchor. + */ + anchorWrapper = () => { + if ( + this.contentRef.current === null || + this.wrapperRef.current === null || + this.anchorRef.current === null + ) { + return; } - return () => bgResizeObserver?.disconnect(); - }, [positioningStrategy]); - - const Wrapper = customWrapper ?? DefaultWrapper; - - const transition = testableTransition({ - delay: 1, - duration: 1.2, - ease: 'easeInOut', - delayChildren: animateDelay ?? (BackgroundComponent ? 0.5 : 1.5), - staggerChildren: 0.15, - }); - - return ( - - {children} - - {BackgroundComponent && ( - - - - )} - - {text({Body, Header})} - - - - ); + // Absolute position the container, this avoids the browser having to reflow + // the component + this.wrapperRef.current.style.position = 'absolute'; + this.wrapperRef.current.style.left = `0px`; + this.wrapperRef.current.style.top = `0px`; + + const mainRect = this.contentRef.current.getBoundingClientRect(); + const anchorRect = this.anchorRef.current.getBoundingClientRect(); + const wrapperRect = this.wrapperRef.current.getBoundingClientRect(); + + // Compute the position of the wrapper + const {x, y} = this.props.positioningStrategy({mainRect, anchorRect, wrapperRect}); + + const transform = `translate(${Math.round(x)}px, ${Math.round(y)}px)`; + this.wrapperRef.current.style.transform = transform; + }; + + render() { + const { + text, + children, + animateDelay, + background: BackgroundComponent, + customWrapper, + ...props + } = this.props; + const Wrapper = customWrapper ?? DefaultWrapper; + + const transition = testableTransition({ + delay: 1, + duration: 1.2, + ease: 'easeInOut', + delayChildren: animateDelay ?? (BackgroundComponent ? 0.5 : 1.5), + staggerChildren: 0.15, + }); + + return ( + + {children} + + {BackgroundComponent && ( + + + + )} + + {text({Body, Header})} + + + + ); + } } const absoluteFull = css`