UNPKG

@gravityforms/components

Version:

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

207 lines (193 loc) 5.34 kB
import { React, classnames, PropTypes } from '@gravityforms/libraries'; import { usePopup } from '@gravityforms/react-utils'; import { spacerClasses } from '@gravityforms/utils'; import DroplistItem from './DroplistItem'; import DroplistList from './DroplistList'; import Popover from '../../elements/Popover'; const { forwardRef, useCallback, useEffect, useRef } = React; /** * @module DroplistGroupItem * @description The DroplistGroupItem component. * * @since 4.3.0 * * @param {object} props Props for the DroplistGroupItem component. * @param {number} props.depth The depth of the item. * @param {number} props.index The index of the item. * @param {object} props.item The item object. * @param {object} props.propsWithState The props and state object. * @param {object|null} ref Ref to the component. * * @return {JSX.Element|null} The DroplistGroupItem component. */ export const DroplistGroupItem = forwardRef( ( { depth = 0, index = 0, item = {}, propsWithState = {}, }, ref ) => { const containerRef = useRef( null ); const { align, itemKey, openOnHover, selectedState, setSelectedState, stackNestedGroups, } = propsWithState; const { onAfterClose = () => {}, onAfterOpen = () => {}, onClose = () => {}, onOpen = () => {}, } = item; const { customClasses: groupTriggerCustomClasses = [], id: triggerId = `${ itemKey }-trigger`, onClick = () => {}, ...restGroupTriggerAttributes } = item.triggerAttributes || {}; const { customClasses: groupListContainerCustomClasses = [], width = 0, ...restGroupListContainerAttributes } = item.listContainerAttributes || {}; const { closePopup, openPopup, popupHide, popupOpen, popupReveal, popupRef, triggerRef, } = usePopup( { closeOnClickOutside: false, onAfterClose, onAfterOpen, onClose, onOpen, } ); /** * @function updateSelectedState * @description Updates the selected state. */ const updateSelectedState = () => { if ( popupOpen ) { setSelectedStateClosed(); } else { setSelectedStateOpen(); } }; /** * @function setSelectedStateOpen * @description Sets the selected state to open. */ const setSelectedStateOpen = () => { // If the group is already open, do nothing. if ( selectedState[ depth ] === triggerId ) { return; } const depthKeys = Object.keys( selectedState ); const filteredState = depthKeys .filter( ( key ) => key < depth ) .reduce( ( acc, key ) => { acc[ key ] = selectedState[ key ]; return acc; }, {} ); const newSelectedState = { ...filteredState, [ depth ]: triggerId, }; setSelectedState( newSelectedState ); }; /** * @function setSelectedStateClosed * @description Sets the selected state to closed. */ const setSelectedStateClosed = () => { const depthKeys = Object.keys( selectedState ); const filteredState = depthKeys .filter( ( key ) => key < depth ) .reduce( ( acc, key ) => { acc[ key ] = selectedState[ key ]; return acc; }, {} ); setSelectedState( filteredState ); }; useEffect( () => { if ( selectedState[ depth ] === triggerId && ! popupOpen ) { openPopup(); } else if ( selectedState[ depth ] !== triggerId && popupOpen ) { closePopup(); } }, [ selectedState, triggerId, popupOpen ] ); const groupListItemProps = { className: classnames( { 'gform-droplist__item': true, 'gform-droplist__item--group': true, 'gform-droplist__item--has-divider': item.hasDivider, }, item.customClasses || [] ), }; const groupTriggerProps = { customAttributes: { 'aria-expanded': popupOpen ? 'true' : 'false', 'aria-haspopup': 'listbox', id: triggerId, onClick: ( event ) => { onClick( event ); updateSelectedState(); }, onMouseEnter: openOnHover && ! stackNestedGroups ? () => { setSelectedStateOpen(); } : undefined, }, customClasses: classnames( 'gform-droplist__item-trigger', groupTriggerCustomClasses, ), depth, index, ...restGroupTriggerAttributes, }; const popoverProps = { align: 'top', autoPlacement: true, className: classnames( 'gform-droplist__popover', groupListContainerCustomClasses, ), containerRef, isHide: popupHide, isOpen: popupOpen, isReveal: popupReveal, placement: align === 'left' ? 'right' : 'left', popoverClasses: spacerClasses( align === 'left' ? [ 0, 0, 0, 2 ] : [ 0, 2, 0, 0 ] ), popoverRef: popupRef, triggerRef: containerRef, // Use container to position the popover rather than the trigger itself. width, ...restGroupListContainerAttributes, }; const setRefs = useCallback( ( node ) => { containerRef.current = node; if ( ref ) { ref.current = node; } }, [ ref ] ); return ( <li { ...groupListItemProps } ref={ setRefs }> <DroplistItem { ...groupTriggerProps } ref={ triggerRef } /> { stackNestedGroups ? null : ( <Popover { ...popoverProps } ref={ popupRef }> <DroplistList { ...propsWithState } listItems={ item.listItems } depth={ depth + 1 } /> </Popover> ) } </li> ); } ); DroplistGroupItem.propTypes = { depth: PropTypes.number, item: PropTypes.object, propsWithState: PropTypes.object, }; DroplistGroupItem.displayName = 'DroplistGroupItem'; export default DroplistGroupItem;