UNPKG

@gravityforms/components

Version:

UI components for use in Gravity Forms development. Both React and vanilla js flavors.

213 lines (197 loc) 7.05 kB
import { React, PropTypes, classnames } from '@gravityforms/libraries'; const { forwardRef, useCallback, useEffect, useState } = React; const TOP = 'top'; const BOTTOM = 'bottom'; const LEFT = 'left'; const RIGHT = 'right'; /** * @function getPlacement * @description Calculate the left and top position based on alignment and placement. * * @since 6.0.1 * * @param {string} align The alignment of the popover. * @param {string} placement The placement of the popover. * @param {object|null} popoverRef Ref to the popover element. * @param {object|null} triggerRef Ref to the trigger element. * @param {object|null} containerRef Ref to the container element. * * @return {object} The calculated left and top position for the popover. */ const getPlacement = ( align, placement, popoverRef, triggerRef, containerRef ) => { let left = 0; let top = 0; const triggerRect = triggerRef?.current?.getBoundingClientRect?.(); const popoverRect = popoverRef?.current?.getBoundingClientRect?.(); let containerRect = containerRef?.current?.getBoundingClientRect?.(); if ( ! containerRect ) { containerRect = { top: 0, left: 0, }; } if ( triggerRect && popoverRect && containerRect ) { switch ( placement ) { case TOP: top = triggerRect.top - containerRect.top - popoverRect.height; if ( align === LEFT ) { left = triggerRect.left - containerRect.left; } else if ( align === RIGHT ) { left = triggerRect.right - containerRect.left - popoverRect.width; } break; case BOTTOM: top = triggerRect.bottom - containerRect.top; if ( align === LEFT ) { left = triggerRect.left - containerRect.left; } else if ( align === RIGHT ) { left = triggerRect.right - containerRect.left - popoverRect.width; } break; case LEFT: left = triggerRect.left - containerRect.left - popoverRect.width; if ( align === TOP ) { top = triggerRect.top - containerRect.top; } else if ( align === BOTTOM ) { top = triggerRect.bottom - containerRect.top - popoverRect.height; } break; case RIGHT: left = triggerRect.right - containerRect.left; if ( align === TOP ) { top = triggerRect.top - containerRect.top; } else if ( align === BOTTOM ) { top = triggerRect.bottom - containerRect.top - popoverRect.height; } break; default: break; } } return { left, top }; }; /** * @module Popover * @description A popover component. Should be used with the `usePopup` hook to manage visibility state. * * @since 6.0.1 * * @param {object} props Component props. * @param {string} props.align The alignment of the popover, used with placement, one of `left`, `right`, 'top', or 'bottom'. * if placement is `top` or `bottom`, align controls horizontal alignment. * if placement is `left` or `right`, align controls vertical alignment. * @param {boolean} props.autoPlacement Whether to autoplace the popover based on the trigger element. * @param {JSX.Element} props.children The popover content. * @param {object|null} props.containerRef Ref to the container element. * @param {object} props.customAttributes Custom attributes for the component. * @param {string|Array|object} props.customClasses Custom classes for the component. * @param {boolean} props.isHide Whether the popover is hidden. * @param {boolean} props.isOpen Whether the popover is open. * @param {boolean} props.isReveal Whether the popover is revealed. * @param {string} props.placement The placement of the popover, one of `top`, `bottom`, `left`, or `right`. * @param {object} props.popoverAttributes Custom attributes for the popover element. * @param {string|Array|object} props.popoverClasses Custom classes for the popover element. * @param {object|null} props.popoverRef Ref to the popover element. * @param {object|null} props.triggerRef Ref to the trigger element. * @param {number} props.width The width of the popover in pixels. * @param {object|null} ref Ref to the component. * * @return {JSX.Element} The popover component. */ const Popover = forwardRef( ( { align = LEFT, autoPlacement = false, children = null, containerRef = null, customAttributes = {}, customClasses = [], isHide = false, isOpen = false, isReveal = false, placement = BOTTOM, popoverAttributes = {}, popoverClasses = [], popoverRef = null, triggerRef = null, width = 0, }, ref ) => { const { left: initialLeft, top: initialTop, } = getPlacement( align, placement, popoverRef, triggerRef, containerRef ); const [ left, setLeft ] = useState( initialLeft ); const [ top, setTop ] = useState( initialTop ); const updatePlacement = useCallback( () => { const { left: newLeft, top: newTop } = getPlacement( align, placement, popoverRef, triggerRef, containerRef ); setLeft( newLeft ); setTop( newTop ); }, [ align, placement, popoverRef, triggerRef, containerRef ] ); // Update placement when revealed. useEffect( () => { if ( ! isReveal ) { return; } updatePlacement(); }, [ isReveal, updatePlacement ] ); const componentProps = { ...customAttributes, className: classnames( { 'gform-popover': true, [ `gform-popover--align-${ align }` ]: true, [ `gform-popover--placement-${ placement }` ]: true, 'gform-popover--open': isOpen, 'gform-popover--reveal': isReveal, 'gform-popover--hide': isHide, }, customClasses ), }; if ( autoPlacement ) { componentProps.style = { insetInlineStart: `${ left }px`, insetBlockStart: `${ top }px`, }; } const popoverProps = { ...popoverAttributes, className: classnames( { 'gform-popover__popover': true, }, popoverClasses ), role: 'listbox', tabIndex: '-1', style: { inlineSize: width ? `${ width }px` : undefined, }, }; return ( <div { ...componentProps } ref={ ref }> <div { ...popoverProps } ref={ popoverRef }> { children } </div> </div> ); } ); Popover.propTypes = { align: PropTypes.oneOf( [ LEFT, RIGHT, TOP, BOTTOM ] ), autoPlacement: PropTypes.bool, children: PropTypes.node, customAttributes: PropTypes.object, customClasses: PropTypes.oneOfType( [ PropTypes.string, PropTypes.array, PropTypes.object, ] ), isHide: PropTypes.bool, isOpen: PropTypes.bool, isReveal: PropTypes.bool, placement: PropTypes.oneOf( [ TOP, BOTTOM, LEFT, RIGHT ] ), popoverAttributes: PropTypes.object, popoverClasses: PropTypes.oneOfType( [ PropTypes.string, PropTypes.array, PropTypes.object, ] ), popoverRef: PropTypes.object, triggerRef: PropTypes.object, width: PropTypes.number, }; Popover.displayName = 'Popover'; export default Popover;