import React , { useMemo , Fragment , useState } from 'react' ; import PropTypes from 'prop-types' ; import { Transition } from 'react-transition-group' ; import { css , cx } from '@leafygreen-ui/emotion' ; import Portal from '@leafygreen-ui/portal' ; import { usePopoverPortalContainer } from '@leafygreen-ui/leafygreen-provider' ; import { useViewportSize , useMutationObserver , useIsomorphicLayoutEffect , useObjectDependency , usePrevious , } from '@leafygreen-ui/hooks' ; import { Align , Justify , PopoverProps } from './types' ; import { calculatePosition , getElementViewportPosition , getElementDocumentPosition , } from './positionUtils' ; const rootPopoverStyle = css `
  position: absolute;
  transition: transform 150ms ease-in-out, opacity 150ms ease-in-out;
  opacity: 0;
` ; const mutationOptions = { attributes : true , characterData : true , childList : true , subtree : true , } ; param props . active param props . spacing param props . align param props . justify param props . adjustOnMutation param props . children param props . className param props . popoverZIndex param props . refEl param props . usePortal param props . portalClassName param props . portalContainer param props . scrollContainer function Popover ( { active = false , spacing = 10 , align = Align . Bottom , justify = Justify . Start , adjustOnMutation = false , children , className , popoverZIndex , refEl , usePortal = true , portalClassName , portalContainer : portalContainerProp , scrollContainer : scrollContainerProp , ... rest } ) { const [ placeholderNode , setPlaceholderNode ] = useState < HTMLElement | null > ( null ) ; const [ contentNode , setContentNode ] = useState < HTMLElement | null > ( null ) ; const [ forceUpdateCounter , setForceUpdateCounter ] = useState ( 0 ) ; let { portalContainer , scrollContainer } = usePopoverPortalContainer ( ) ; portalContainer = portalContainerProp || portalContainer ; scrollContainer = scrollContainerProp || scrollContainer ; const contentNodeRef = React . useRef ( contentNode ) ; contentNodeRef . current = contentNode ; let referenceElement = null ; if ( refEl && refEl . current ) { referenceElement = refEl . current ; } else if ( placeholderNode ) { const parent = placeholderNode . parentNode ; if ( parent && parent instanceof HTMLElement ) { referenceElement = parent ; } } const viewportSize = useViewportSize ( ) ; const observeMutations = adjustOnMutation && active ; const lastTimeRefElMutated = useMutationObserver ( referenceElement , mutationOptions , Date . now , observeMutations ) ; const lastTimeContentElMutated = useMutationObserver ( contentNode , mutationOptions , Date . now , observeMutations ) ; const referenceElViewportPos = useObjectDependency ( getElementViewportPosition ( referenceElement , scrollContainer ) ) ; const contentElViewportPos = useObjectDependency ( getElementViewportPosition ( contentNode , scrollContainer ) ) ; const referenceElDocumentPos = useObjectDependency ( useMemo ( ( ) => getElementDocumentPosition ( referenceElement , scrollContainer ) , [ referenceElement , scrollContainer , viewportSize , lastTimeRefElMutated , active , align , justify , forceUpdateCounter , ] ) ) ; const contentElDocumentPos = useObjectDependency ( useMemo ( ( ) => getElementDocumentPosition ( contentNode ) , [ contentNode , viewportSize , lastTimeContentElMutated , active , align , justify , forceUpdateCounter , ] ) ) ; const prevJustify = usePrevious < Justify > ( justify ) ; const prevAlign = usePrevious < Align > ( align ) ; const layoutMightHaveChanged = ( prevJustify !== justify && ( justify === Justify . Fit || prevJustify === Justify . Fit ) ) || ( prevAlign !== align && justify === Justify . Fit ) ; useIsomorphicLayoutEffect ( ( ) => { if ( layoutMightHaveChanged ) { setForceUpdateCounter ( n => n + 1 ) ; } } , [ layoutMightHaveChanged ] ) ; const [ shouldRender , setShouldRender ] = useState ( false ) ; useIsomorphicLayoutEffect ( ( ) => setShouldRender ( true ) , [ ] ) ; if ( ! shouldRender ) { return null ; } const { align : windowSafeAlign , justify : windowSafeJustify , positionCSS : { transform , ... positionCSS } , } = calculatePosition ( { useRelativePositioning : ! usePortal , spacing , align , justify , referenceElViewportPos , referenceElDocumentPos , contentElViewportPos , contentElDocumentPos , } ) ; const activeStyle = css `
    opacity: 1;
    position: ${ usePortal ? '' : 'absolute' };
    pointer-events: initial;
  ` ; const Root = usePortal ? Portal : Fragment ; const rootProps = usePortal ? portalContainer ? { container : portalContainer } : { className : portalClassName ?? undefined } : { } ; let renderedChildren ; if ( children == null ) { renderedChildren = null ; } else if ( typeof children === 'function' ) { renderedChildren = children ( { align : windowSafeAlign , justify : windowSafeJustify , referenceElPos : referenceElDocumentPos , } ) ; } else { renderedChildren = children ; } return ( < Transition nodeRef = { contentNodeRef } in = { active } timeout = { 150 } mountOnEnter unmountOnExit appear > { state => ( < > < div ref = { setPlaceholderNode } className = { css `
              display: none;
            ` } /> < Root { ... rootProps } > < div { ... rest } className = { cx ( rootPopoverStyle , css ( positionCSS ) , { [ css ( { transform } ) ] : state === 'entering' || state === 'exiting' , [ activeStyle ] : state === 'entered' , [ css `
                    z-index: ${ popoverZIndex };
                  ` ] : typeof popoverZIndex === 'number' , } , className ) } > { } < div ref = { setContentNode } > { renderedChildren } < /div> < /div> < /Root> < />)} < /Transition>); } Popover . displayName = 'Popover' ; Popover . propTypes = { children : PropTypes . oneOfType ( [ PropTypes . node , PropTypes . func ] ) , active : PropTypes . bool , className : PropTypes . string , align : PropTypes . oneOf ( Object . values ( Align ) ) , justify : PropTypes . oneOf ( Object . values ( Justify ) ) , refEl : PropTypes . shape ( { current : typeof window !== 'undefined' ? PropTypes . instanceOf ( Element ) : PropTypes . any , } ) , usePortal : PropTypes . bool , portalClassName : PropTypes . string , spacing : PropTypes . number , adjustOnMutation : PropTypes . bool , } ; export default Popover ;