UNPKG

@gravityforms/components

Version:

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

350 lines (326 loc) 10.5 kB
import { React, SimpleBar, classnames, PropTypes } from '@gravityforms/libraries'; import { useId, useStateWithDep, ConditionalWrapper, } from '@gravityforms/react-utils'; import Button from '../../elements/Button'; import Heading from '../../elements/Heading'; import Text from '../../elements/Text'; const { Fragment, forwardRef, useState, useEffect } = React; /** * @module Flyout * @description A flyout component in react. * * @since 1.1.18 * * @param {object} props Component props. * @param {JSX.Element} props.afterContent Any custom content to be placed after the body of the flyout. * @param {number} props.animationDelay Total runtime of close animation. Synchronize with css if modifying the built-in 170 ms delay. * @param {JSX.Element} props.beforeContent Any custom content to be placed before the header of the flyout. * @param {JSX.Element} props.children React element children. * @param {object} props.closeButtonCustomAttributes Custom attributes for the close button. * @param {object} props.customAttributes Custom attributes for the component. * @param {string|Array|object} props.customBodyClasses Custom classes for the flyout body as array. * @param {string|Array|object} props.customClasses Custom classes for the component. * @param {string|Array|object} props.customInnerBodyClasses Custom classes for the flyout inner_body as array. * @param {string} props.description Subheading description for the flyout. * @param {number} props.desktopWidth Width in % for the flyout on desktop. * @param {string} props.direction Flyout direction, left or right. * @param {object} props.headerHeadingCustomAttributes Custom attributes for the header heading. * @param {object} props.headerDescriptionCustomAttributes Custom attributes for the header description. * @param {string} props.id Flyout id. * @param {boolean} props.isOpen Prop to control whether the dialog is currently open. * @param {number} props.maxWidth Max width in pixels for the flyout. * @param {number} props.mobileBreakpoint Mobile breakpoint in pixels for the flyout. * @param {number} props.mobileWidth Width in % for the flyout on mobile. * @param {Function} props.onClose Function to fire on flyout close. * @param {Function} props.onOpen Function to fire on flyout open. * @param {string} props.position The position of the flyout, `absolute` or `fixed`. * @param {boolean} props.showDivider Whether or not to show the divider border below title. * @param {boolean} props.simplebar Whether or not to use SimpleBar on the content. * @param {string} props.title The title of the flyout. * @param {number} props.zIndex z-index of the flyout. * @param {object|null} ref Ref to the component. * * @return {JSX.Element} The Flyout component. * * @example * import Flyout from '@gravityforms/components/react/admin/modules/Flyout'; * * return ( * <Flyout direction="right" title="Flyout title"> * { children } * </Flyout> * ); * */ const Flyout = forwardRef( ( { afterContent = null, animationDelay = 170, beforeContent = null, children = null, closeButtonCustomAttributes = { customClasses: [], icon: 'delete', iconPrefix: 'gravity-component-icon', size: 'size-xs', title: '', type: 'round', }, customAttributes = {}, customBodyClasses = [], customClasses = [], customInnerBodyClasses = [], description = '', desktopWidth = 0, direction = '', headerHeadingCustomAttributes = {}, headerDescriptionCustomAttributes = {}, id: defaultId = '', isOpen = false, maxWidth = 0, mobileBreakpoint = 0, mobileWidth = 0, onClose = () => {}, onOpen = () => {}, position = 'fixed', showDivider = true, simplebar = false, title = '', zIndex = 10, }, ref ) => { const [ animationReady, setAnimationReady ] = useState( false ); const [ animationActive, setAnimationActive ] = useState( false ); const [ flyoutActive, setFlyoutActive ] = useStateWithDep( isOpen ); const id = useId( defaultId ); const componentProps = { className: classnames( { 'gform-flyout': true, 'gform-flyout--anim-in-ready': animationReady, 'gform-flyout--anim-in-active': animationActive, [ `gform-flyout--${ direction }` ]: true, [ `gform-flyout--${ position }` ]: true, 'gform-flyout--divider': showDivider, 'gform-flyout--no-divider': ! showDivider, 'gform-flyout--no-description': ! description, }, customClasses ), id, ...customAttributes, }; useEffect( () => { if ( flyoutActive ) { showFlyout(); } else if ( ! flyoutActive ) { closeFlyout(); } }, [ flyoutActive ] ); /** * @function showFlyout * @description Opens the flyout and fires the `onOpen` function if passed in. * * @since 1.1.18 * * @return {void} */ const showFlyout = () => { setAnimationReady( true ); setTimeout( () => { setAnimationActive( true ); onOpen(); }, 25 ); }; /** * @function closeFlyout * @description Closes the flyout and fires the `onClose` function if passed in. * * @since 1.1.18 * * @return {void} */ const closeFlyout = () => { setAnimationActive( false ); setTimeout( () => { setAnimationReady( false ); onClose(); }, animationDelay ); }; /** * @function getHeader * @description Returns the header of the flyout that contains the title and description. * * @since 1.1.18 * * @return {JSX.Element} */ const getHeader = () => { let buttonType = 'unstyled'; if ( closeButtonCustomAttributes.type === 'round' ) { buttonType = 'white'; } else if ( closeButtonCustomAttributes.type === 'simplified' ) { buttonType = 'simplified'; } const closeButtonProps = { ...closeButtonCustomAttributes, customClasses: classnames( { 'gform-button': true, 'gform-flyout__close': true, }, closeButtonCustomAttributes.customClasses || [], ), circular: closeButtonCustomAttributes.type === 'round' || closeButtonCustomAttributes.type === 'simplified', onClick: () => setFlyoutActive( false ), type: buttonType, }; const headerProps = { className: classnames( { 'gform-flyout__head': true, } ), }; const headingProps = { customClasses: classnames( { 'gform-flyout__title': true, } ), content: title, size: 'display-xs', tagName: 'h3', weight: 'SemiBold', ...headerHeadingCustomAttributes, }; const descriptionProps = { customClasses: classnames( { 'gform-gform-flyout__desc': true, } ), content: description, ...headerDescriptionCustomAttributes, }; return ( <> <ConditionalWrapper condition={ title || description } wrapper={ ( ch ) => <header { ...headerProps }>{ ch }</header> } > { title && <Heading { ...headingProps } /> } { description && <Text { ...descriptionProps } /> } </ConditionalWrapper> <Button { ...closeButtonProps } /> </> ); }; /** * @function getBody * @description Returns the body that wrap the children of the flyout. * * @since 1.1.18 * * @return {JSX.Element} */ const getBody = () => { const bodyProps = { className: classnames( { 'gform-flyout__body': true, }, customBodyClasses ), }; const innerBodyProps = { className: classnames( { 'gform-flyout__body-inner': true, }, customInnerBodyClasses ), }; return ( <div { ...bodyProps } > <div { ...innerBodyProps } > { children } </div> </div> ); }; /** * @function getCSS * @description Returns the CSS used to handle the width and mobile media query. * * @since 1.1.18 * * @return {string} The CSS for the flyout. */ const getCSS = () => { let css = `#${ id } { max-width: ${ maxWidth ? `${ maxWidth }px` : 'none' }; width: ${ mobileWidth }%; z-index: ${ zIndex } } `; if ( mobileBreakpoint ) { css += ` @media only screen and (min-width: ${ mobileBreakpoint }px) { #${ id } { width: ${ desktopWidth }%; } } `; } return css; }; return ( <Fragment ref={ ref }> <article { ...componentProps } > <ConditionalWrapper condition={ simplebar } wrapper={ ( ch ) => <SimpleBar>{ ch }</SimpleBar> } > { beforeContent } { getHeader() } { getBody() } { afterContent } </ConditionalWrapper> </article> <style> { getCSS() } </style> </Fragment> ); } ); Flyout.propTypes = { afterContent: PropTypes.node, animationDelay: PropTypes.number, beforeContent: PropTypes.node, children: PropTypes.oneOfType( [ PropTypes.arrayOf( PropTypes.node ), PropTypes.node, ] ), closeButtonCustomAttributes: PropTypes.object, customAttributes: PropTypes.object, customBodyClasses: PropTypes.oneOfType( [ PropTypes.string, PropTypes.array, PropTypes.object, ] ), customClasses: PropTypes.oneOfType( [ PropTypes.string, PropTypes.array, PropTypes.object, ] ), customInnerBodyClasses: PropTypes.oneOfType( [ PropTypes.string, PropTypes.array, PropTypes.object, ] ), description: PropTypes.string, desktopWidth: PropTypes.number, direction: PropTypes.string, headerHeadingCustomAttributes: PropTypes.object, headerDescriptionCustomAttributes: PropTypes.object, id: PropTypes.string, isOpen: PropTypes.bool, maxWidth: PropTypes.number, mobileBreakpoint: PropTypes.number, mobileWidth: PropTypes.number, onClose: PropTypes.func, onOpen: PropTypes.func, position: PropTypes.oneOf( [ 'absolute', 'fixed' ] ), showDivider: PropTypes.bool, title: PropTypes.string, zIndex: PropTypes.number, }; Flyout.displayName = 'Flyout'; export default Flyout;