/** @jsxImportSource @emotion/react */ import React, { FunctionComponent } from 'react'; import tw, { css, styled } from 'twin.macro'; /** * The amount of time in milliseconds for the * overlay in/out transition to complete. * This const is used to determine when to apply * the hidden state. */ const TRANSITION_DURATION = 300; type TransitionState = 'entering' | 'entered' | 'exiting' | 'exited'; type OverlayProps = { isOpen: boolean; offsetTop?: number; }; type OverlayWrapperProps = OverlayProps & { transitionState: TransitionState; }; const OverlayWrapper = styled.div( ({ transitionState = 'exited', offsetTop = 0 }: OverlayWrapperProps) => [ tw`fixed inset-x-0 z-50 bottom-0`, css` top: ${offsetTop}px; `, tw`transition-opacity duration-300`, transitionState === 'entered' && tw`opacity-100`, transitionState === 'entering' && tw`opacity-100 pointer-events-none`, transitionState === 'exiting' && tw`opacity-0 pointer-events-none`, transitionState === 'exited' && tw`opacity-0`, ], ); export const Overlay: FunctionComponent, 'as'> & OverlayProps> = ({ isOpen = false, offsetTop = 0, children, ...props }) => { const [transitionState, setTransitionState] = React.useState( isOpen ? 'entered' : 'exited', ); React.useEffect(() => { let timeout = setTimeout(() => { setTransitionState((transitionState) => { if (transitionState === 'entering') { return 'entered'; } if (transitionState === 'exiting') { return 'exited'; } return transitionState; }); timeout = null; }, TRANSITION_DURATION); return () => { if (!timeout) return; clearTimeout(timeout); }; }, [isOpen, setTransitionState]); React.useEffect(() => { if (isOpen) { if (transitionState !== 'entered') setTransitionState('entering'); } else { if (transitionState !== 'exited') setTransitionState('exiting'); } }, [isOpen, setTransitionState]); return ( <> {isOpen && ( {children} )} ); }; export default Overlay;