/** @jsxImportSource @emotion/react */ import React, { FunctionComponent } from 'react'; import tw, { css, styled } from 'twin.macro'; /** * The amount of time in milliseconds for the * drawer in/out transition to complete. * This const is used to determine when to apply * the hidden state. */ const TRANSITION_DURATION = 600; type TransitionState = 'entering' | 'entered' | 'exiting' | 'exited'; type DrawerProps = { isOpen: boolean; onClose: () => void; anchor: 'left' | 'right'; }; type DrawerContentProps = Omit & { transitionState: TransitionState; }; const DrawerUnderlay = styled.div( ({ isOpen = false }: Partial) => [ tw`fixed inset-0 bg-white bg-opacity-10 z-40`, css` transition: backdrop-filter ${TRANSITION_DURATION}ms ease 0s, opacity 200ms ease 0s; `, isOpen && tw`opacity-100 backdrop-blur-[10px]`, !isOpen && tw`opacity-0 backdrop-blur-0 pointer-events-none`, ], ); const DrawerContent = styled.div( ({ isOpen = false, transitionState = 'exited', anchor = 'right', }: DrawerContentProps) => [ tw`fixed top-0 bottom-0 bg-white bg-opacity-10 z-50 transform`, css` transition: transform 600ms cubic-bezier(0, 0.25, 0, 1) 0s; `, anchor === 'left' && tw`left-0`, anchor === 'right' && tw`right-0`, transitionState === 'entered' && tw`translate-x-0`, (transitionState === 'entering' || transitionState === 'exiting') && anchor === 'left' && tw`-translate-x-1/1`, (transitionState === 'entering' || transitionState === 'exiting') && anchor === 'right' && tw`translate-x-1/1`, transitionState === 'exited' && anchor === 'left' && tw`-translate-x-1/1 hidden`, transitionState === 'exited' && anchor === 'right' && tw`translate-x-1/1 hidden`, ], ); export const Drawer: FunctionComponent = ({ isOpen = false, anchor = 'right', onClose, 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'; } }); 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 ( <> {children} ); }; export default Drawer;