/* * Copyright (c) 2018-present, Revolut LTD. * * This source code is licensed under the Apache 2.0 license found in the * LICENSE file in the root directory of this source tree. */ import * as React from 'react' import { useEffect, useRef, RefObject } from 'react' import { Portal } from 'react-portal' import { TransitionStatus } from 'react-transition-group/Transition' import { usePrevious } from 'react-hanger' import styled from 'styled-components' import { zIndex as ssZIndex } from 'styled-system' import { propGet } from '../../style/utils' import { Animation, getDuration, isEntered, isEntering, TimeoutType } from '../Animation' const SIDE = { LEFT: 'left', RIGHT: 'right', } const MODE = { OVERLAY: 'overlay', SQUEEZE: 'squeeze', } const OFFSET_TO_HIDE_SHADOW = '3vw' const isLeft = (side: string) => side === SIDE.LEFT const isRight = (side: string) => side === SIDE.RIGHT const isOverlay = (mode: string) => mode === MODE.OVERLAY const isSqueeze = (mode: string) => mode === MODE.SQUEEZE const positioningAndShadow = ({ state, side, mode }: MenuContainerProps) => { const boxShadow = '0 1px 26px 3px #000' const shouldApplyEnterStyles = isEntering(state) || isEntered(state) const isOverlayMode = isOverlay(mode) if (shouldApplyEnterStyles && isRight(side)) { return { right: 0, boxShadow: isOverlayMode ? boxShadow : '' } } if (shouldApplyEnterStyles && isLeft(side)) { return { left: 0, boxShadow: isOverlayMode ? boxShadow : '' } } return { boxShadow: isOverlayMode ? boxShadow : '' } } type MenuContainerProps = SideMenuProps & { state: TransitionStatus } const StyledMenuContainer = styled.div` position: fixed; top: ${propGet('topOffset')}; min-height: calc(100vh - ${propGet('topOffset')} - ${propGet('bottomOffset')}); max-height: 100%; background: white; padding: 1rem; transition: ${propGet('side')} ${propGet('duration')}ms; width: ${propGet('width')}; max-width: ${propGet('width')}; border: 1px solid lightgray; ${propGet('side')} calc(-${propGet('width')} - ${OFFSET_TO_HIDE_SHADOW}); // @ts-ignore ${positioningAndShadow}; ${ssZIndex}; ` const useHandleElementSqueeze = ({ isOpen, mode, side, width, duration, elementToSqueeze, }: SideMenuProps) => { const element = useRef(null) const isOpenPrev = usePrevious(isOpen) useEffect( () => { if (isSqueeze(mode)) { element.current = document.querySelector(elementToSqueeze) if (element.current) { if (isOpen === true) { if (isLeft(side)) element.current.style.paddingLeft = width else element.current.style.paddingRight = width } element.current.style.transition = `padding ${getDuration(duration).enter}ms` } } return () => { if (element.current) { if (isLeft(side)) { element.current.style.paddingLeft = '0' } else { element.current.style.paddingRight = '0' } } } }, [duration, elementToSqueeze, isOpen, mode, side, width], ) useEffect( () => { const isClosing = isOpenPrev === true if (isOpenPrev !== isOpen && isSqueeze(mode) && element.current) { if (isClosing) { if (isLeft(side)) { element.current.style.paddingLeft = '0' } else { element.current.style.paddingRight = '0' } } else if (isLeft(side)) { element.current.style.paddingLeft = width } else { element.current.style.paddingRight = width } } }, [isOpen, isOpenPrev, mode, side, width], ) } type SideMenuRef = ((instance: T) => void) | RefObject export type SideMenuProps = { width?: string topOffset?: string bottomOffset?: string isOpen: boolean side?: 'right' | 'left' elementToSqueeze?: string mode?: 'squeeze' | 'overlay' duration?: TimeoutType | number zIndex?: number sideMenuRef?: SideMenuRef } export const SideMenu: React.FC = ({ width = '300px', topOffset = '0px', bottomOffset = '0px', isOpen, side = 'right', elementToSqueeze = 'body', mode = 'overlay', duration = 500, zIndex = 10, sideMenuRef, ...rest }) => { // sideMenuRef should be changed to RefForwarding component wrapper const propsWithDefaults = { width, topOffset, bottomOffset, isOpen, side, elementToSqueeze, mode, duration, zIndex, sideMenuRef, ...rest, } useHandleElementSqueeze(propsWithDefaults) return ( {({ duration: animationDuration, state }) => ( {propsWithDefaults.children} )} ) }