UNPKG

@gravityforms/components

Version:

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

530 lines (484 loc) 18 kB
import { React, SimpleBar, PropTypes, classnames } from '@gravityforms/libraries'; import { bodyLock, getClosest, uniqueId } from '@gravityforms/utils'; import { ConditionalWrapper, useStateWithDep, useFocusTrap } from '@gravityforms/react-utils'; import IconIndicator from '../Indicators/IconIndicator'; const { useState, useEffect, useRef, forwardRef } = React; /** * @module Dialog * @description A dialog component in react that handle full screen containers, modals, alerts and dialogs. * * @since 1.1.15 * * @param {object} props Component props. * @param {string} props.alertButtonText If in alert mode, the text for the ok button below content. * @param {string} props.alignment Position of message in window. * @param {boolean} props.animateModal When in modal or dialog mode, animate the dialog in with a fade in and up? * @param {number} props.animationDelay The css animation delay for the reveal/hide effect. Synchronize with css if modifying the built-in 250 ms delay. * @param {string} props.buttonWidth The button width, one of `auto` or `full`. * @param {string} props.cancelButtonHeight The height for the cancel button. * @param {string} props.cancelButtonText If in dialog mode, the white cancel buttons text is set here. * @param {string} props.cancelButtonType The type of the cancel button. * @param {JSX.Element} props.children React element children. * @param {string} props.closeButtonSize The close button size. Default is `xs`. * @param {string} props.closeButtonTitle The close button title for accessibility purposes. * @param {string} props.closeButtonType The close button type. Default is `round`. Also supports `noborder` and `unstyled`. * @param {boolean} props.closeOnMaskClick Whether to close if the background mask is clicked. * @param {object} props.confirmButtonAttributes Arbitrary additional html attributes for the confirm button if in dialog mode. * @param {string} props.confirmButtonHeight The height of the confirm button. * @param {string} props.confirmButtonIcon If in dialog mode, the optional confirmation button icon before the button text. * @param {string} props.confirmButtonText If in dialog mode, the confirmation button text. * @param {string} props.confirmButtonType The type of the confirm button. * @param {string} props.content Container content. Can only be strings. Use React children for html. * @param {string|Array|object} props.customCloseButtonClasses Custom classes for the close button as array. * @param {string|Array|object} props.customMaskClasses Custom classes for the mask as array. * @param {string|Array|object} props.customWrapperClasses Custom classes for the wrapper as array. * @param {string|Array|object} props.description Optional text to show below title. * @param {string} props.id The id for the dialog. If not passed auto generated using uniqueId from our utils with a prefix of `dialog`. * @param {boolean} props.isOpen Prop to control whether the dialog is currently open. * @param {boolean} props.lockBody Whether to lock the body behind the dialog to prevent interaction or scrolling. * @param {boolean} props.maskBlur Whether to blur behind the mask for the dialog. * @param {string} props.maskTheme Mask background color scheme: `none`, `light` or `dark`. * @param {string} props.maxHeight Max height for the dialog. * @param {string} props.mode Container mode: `container`, `modal`, `alert` or `dialog`. * @param {Function} props.onClose Function to execute when the dialog closes. * @param {Function} props.onCloseAfterAnimation Function to execute after the dialog close animation. * @param {Function} props.onOpen Function to execute when the dialog opens. * @param {Function} props.onOpenAfterAnimation Function to execute after the dialog open animation. * @param {boolean} props.padContent Whether to pad the content on the right or not. * @param {string} props.position Position for the mask: `fixed`, `absolute`. * @param {boolean} props.showCancelButton Whether to show the cancel button or not. * @param {boolean} props.showCloseButton Whether to show the close button top right or not. * @param {boolean} props.showConfirmButton Whether to show the confirm button or not. * @param {boolean} props.simplebar Whether or not to use SimpleBar on the content. * @param {string} props.theme Theme for the dialog, one of `gravity-blue` or `cosmos`. * @param {string} props.title Title for the dialog. Does not show in container mode. * @param {boolean} props.titleDivider Whether to show a divider below the title or not. * @param {string} props.titleIndicatorType Indicator type for the dialog title. * @param {string} props.titleSize Size for the title, sm or md currently. * @param {string} props.titleTagName Tagname for the title of the dialog. * @param {number} props.zIndex The z-index for the dialog. * @param {object|null} ref Ref to the component. * * @return {JSX.Element} Return the functional dialog component in React. * * @example * import Dialog from '@gravityforms/components/react/admin/modules/Dialog'; * * const Example = () => { * const dialogArgs = { * closeOnMaskClick: false, * customCloseButtonClasses: [ 'gform-example--exit-button' ], * customWrapperClasses: [ * 'gform-example', * ], * id: 'test-id', * isOpen, * lockBody: true, * mode: 'container', * zIndex: 100001, * }; * * return ( * <Dialog { ...dialogArgs }> * { children } * </Dialog> * ); * } * */ const Dialog = forwardRef( ( { alertButtonText = '', alignment = 'center', animateModal = false, animationDelay = 250, buttonWidth = 'auto', cancelButtonAttributes = {}, cancelButtonHeight = 'height-l', cancelButtonText = '', cancelButtonType = 'white', children = null, closeButtonIcon = 'x', closeButtonIconPrefix = 'gravity-component-icon', closeButtonSize = 'xs', closeButtonTitle = '', closeButtonType = 'round', closeOnMaskClick = true, confirmButtonAttributes = {}, confirmButtonHeight = 'height-l', confirmButtonIcon = '', confirmButtonIconPrefix = 'gravity-component-icon', confirmButtonText = '', confirmButtonType = 'primary-new', content = '', customCloseButtonClasses = [], customCloseButtonLabelAttributes = {}, customMaskClasses = [], customWrapperClasses = [], description = '', id = '', isOpen = false, lockBody = false, maskBlur = true, maskTheme = 'none', maxHeight = '', mode = '', onClose = () => {}, onCloseAfterAnimation = () => {}, onOpen = () => {}, onOpenAfterAnimation = () => {}, padContent = true, position = 'fixed', showCancelButton = true, showCloseButton = true, showConfirmButton = true, simplebar = false, theme = 'gravity-blue', title = '', titleDivider = false, titleIndicatorAttributes = {}, titleIndicatorType = '', titleSize = 'sm', titleTagName = 'h5', zIndex = 10, }, ref ) => { // eslint-disable-line const [ animationReady, setAnimationReady ] = useState( false ); const [ animationActive, setAnimationActive ] = useState( false ); const [ modalAnimation, setModalAnimation ] = useState( false ); const [ dialogActive, setDialogActive ] = useStateWithDep( isOpen ); const trapRef = useFocusTrap( dialogActive ); const mountedRef = useRef( true ); const closeRef = useRef( true ); useEffect( () => { if ( dialogActive ) { showDialog(); } else if ( ! dialogActive && mountedRef.current ) { closeDialog(); } }, [ dialogActive ] ); useEffect( () => { if ( animateModal ) { setTimeout( () => { setModalAnimation( true ); }, 200 ); } return () => { mountedRef.current = false; }; }, [ animateModal ] ); useEffect( () => { closeRef.current.addEventListener( 'keydown', handleEscapeRequest ); return () => { if ( ! closeRef.current ) { return; } closeRef.current.removeEventListener( 'keydown', handleEscapeRequest ); }; } ); const handleEscapeRequest = ( e ) => { if ( getClosest( e.target, '.gform-dialog' ) !== closeRef.current ) { return; } if ( e.key !== 'Escape' ) { return; } closeDialog(); }; const closeDialog = () => { setAnimationActive( false ); setTimeout( () => { setAnimationReady( false ); onCloseAfterAnimation(); }, animationDelay ); if ( lockBody ) { bodyLock.unlock(); } onClose(); }; const showDialog = () => { setAnimationReady( true ); setTimeout( () => { setAnimationActive( true ); setTimeout( () => onOpenAfterAnimation, animationDelay ); }, 25 ); if ( lockBody ) { bodyLock.lock(); } onOpen(); }; const pointerDownOrigin = useRef( null ); const handlePointerDown = ( event ) => { pointerDownOrigin.current = event.target; }; const handlePointerUp = ( event ) => { if ( pointerDownOrigin.current === event.target && event.target.classList.contains( 'gform-dialog__mask' ) && closeOnMaskClick && dialogActive ) { event.stopPropagation(); setDialogActive( false ); } pointerDownOrigin.current = null; }; const maskProps = { className: classnames( { 'gform-dialog__mask': true, 'gform-dialog--anim-in-ready': animationReady, 'gform-dialog--anim-in-active': animationReady && animationActive, [ `gform-dialog__mask--position-${ position }` ]: true, [ `gform-dialog__mask--theme-${ maskTheme }` ]: true, [ `gform-dialog--alignment-${ alignment }` ]: mode !== 'container', 'gform-dialog__mask--blur': maskBlur, }, customMaskClasses ), onPointerDown: handlePointerDown, onPointerUp: handlePointerUp, style: { zIndex, }, }; const wrapperProps = { id: id || uniqueId( 'dialog-' ), className: classnames( { 'gform-dialog': true, 'gform-dialog--animated': animateModal, 'gform-dialog--animate-reveal': modalAnimation, [ `gform-dialog--title-size-${ titleSize }` ]: true, 'gform-dialog--container': mode === 'container', 'gform-dialog--simplebar': simplebar, [ `gform-dialog__theme--${ theme }` ]: true, }, customWrapperClasses ), style: { maxHeight, }, }; const closeButtonLabelProps = { className: classnames( { 'gform-button__icon': true, [ closeButtonIconPrefix ]: true, [ `${ closeButtonIconPrefix }--${ closeButtonIcon }` ]: true, } ), ...customCloseButtonLabelAttributes, }; const contentProps = { className: classnames( { 'gform-dialog__content': true, 'gform-dialog__content--with-divider': titleDivider, 'gform-dialog__content--pad-content': padContent, } ), }; const closeButtonProps = { className: classnames( { 'gform-dialog__close': true, 'gform-button': true, 'gform-button--unstyled': closeButtonType === 'unstyled', 'gform-button--white': closeButtonType === 'round', 'gform-button--circular': closeButtonType === 'round' || closeButtonType === 'simplified', 'gform-button--simplified': closeButtonType === 'simplified', [ `gform-button--size-${ closeButtonSize }` ]: true, }, customCloseButtonClasses ), onClick: () => setDialogActive( false ), style: { zIndex: zIndex + 1, }, title: closeButtonTitle, }; if ( closeButtonProps.ariaLabel ) { closeButtonProps[ 'aria-label' ] = closeButtonProps.ariaLabel; } else { closeButtonProps[ 'aria-label' ] = closeButtonTitle; } const getTitleIndicator = () => { if ( ! titleIndicatorType ) { return null; } const titleIndicatorProps = { type: titleIndicatorType, ...titleIndicatorAttributes, }; return <IconIndicator { ...titleIndicatorProps } />; }; const getTitle = () => { const headerProps = { className: classnames( { 'gform-dialog__head': true, 'gform-dialog__head--with-divider': titleDivider, } ), 'data-js': 'gform-dialog-header', }; const headingProps = { className: classnames( { 'gform-dialog__title': true, 'gform-dialog__title--has-icon': !! titleIndicatorType, [ `gform-dialog__title--icon-type-${ titleIndicatorType }` ]: !! titleIndicatorType, } ), }; const TitleTag = titleTagName; return ( <header { ...headerProps }> { getTitleIndicator() } <TitleTag { ...headingProps }>{ title }</TitleTag> { description && <span className="gform-dialog__description">{ description }</span> } </header> ); }; const getConfirmButtonIcon = () => { const iconProps = { className: classnames( { 'gform-button__icon': true, [ `${ confirmButtonIconPrefix } ${ confirmButtonIconPrefix }--${ confirmButtonIcon }` ]: true, } ), }; return ( <span { ...iconProps } /> ); }; const getDialogButtons = () => { const cancelButtonProps = { className: classnames( { 'gform-dialog__cancel': true, 'gform-button': true, [ `gform-button--size-${ cancelButtonHeight }` ]: true, [ `gform-button--${ cancelButtonType }` ]: true, [ `gform-button--width-${ buttonWidth }` ]: true, } ), onClick: () => setDialogActive( false ), 'data-js': 'gform-dialog-cancel', ...cancelButtonAttributes, }; const confirmButtonProps = { className: classnames( { 'gform-dialog__confirm': true, 'gform-button': true, [ `gform-button--size-${ confirmButtonHeight }` ]: true, [ `gform-button--${ confirmButtonType }` ]: true, 'gform-button--icon-leading': confirmButtonIcon, [ `gform-button--width-${ buttonWidth }` ]: true, } ), 'data-js': 'gform-dialog-confirm', ...confirmButtonAttributes, }; return ( <> { showCancelButton && ( <button { ...cancelButtonProps }> { cancelButtonText } </button> ) } { showConfirmButton && ( <button { ...confirmButtonProps }> { confirmButtonIcon && getConfirmButtonIcon() }{ confirmButtonText } </button> ) } </> ); }; const getAlertButtons = () => { const buttonProps = { className: classnames( { 'gform-dialog__alert': true, 'gform-button': true, [ `gform-button--size-${ confirmButtonHeight }` ]: true, [ `gform-button--${ confirmButtonType }` ]: true, [ `gform-button--width-${ buttonWidth }` ]: true, } ), onClick: () => setDialogActive( false ), 'data-js': 'gform-dialog-alert', }; return ( <> <button { ...buttonProps }> { alertButtonText } </button> </> ); }; const getFooter = () => { const footerProps = { className: classnames( { 'gform-dialog__footer': true, } ), 'data-js': 'gform-dialog-footer', }; return ( <> <footer { ...footerProps }> { mode === 'dialog' && getDialogButtons() } { mode === 'alert' && getAlertButtons() } </footer> </> ); }; return ( <div { ...maskProps } ref={ trapRef }> <article { ...wrapperProps } ref={ closeRef }> <ConditionalWrapper condition={ simplebar } wrapper={ ( ch ) => <SimpleBar>{ ch }</SimpleBar> } > { showCloseButton && <button { ...closeButtonProps } > <span { ...closeButtonLabelProps } /> </button> } { mode !== 'container' && title && getTitle() } <div { ...contentProps }> { mode !== 'container' && content } { children } </div> { ( mode === 'dialog' || mode === 'alert' ) && getFooter() } </ConditionalWrapper> </article> </div> ); } ); Dialog.propTypes = { alertButtonText: PropTypes.string, alignment: PropTypes.string, animateModal: PropTypes.bool, animationDelay: PropTypes.number, buttonWidth: PropTypes.string, cancelButtonHeight: PropTypes.string, cancelButtonText: PropTypes.string, cancelButtonType: PropTypes.string, children: PropTypes.oneOfType( [ PropTypes.arrayOf( PropTypes.node ), PropTypes.node, ] ), closeButtonSize: PropTypes.string, closeButtonTitle: PropTypes.string, closeButtonType: PropTypes.string, closeOnMaskClick: PropTypes.bool, confirmButtonAttributes: PropTypes.object, confirmButtonHeight: PropTypes.string, confirmButtonIcon: PropTypes.string, confirmButtonText: PropTypes.string, confirmButtonType: PropTypes.string, content: PropTypes.string, customCloseButtonClasses: PropTypes.oneOfType( [ PropTypes.string, PropTypes.array, PropTypes.object, ] ), customMaskClasses: PropTypes.oneOfType( [ PropTypes.string, PropTypes.array, PropTypes.object, ] ), customWrapperClasses: PropTypes.oneOfType( [ PropTypes.string, PropTypes.array, PropTypes.object, ] ), id: PropTypes.string, isOpen: PropTypes.bool, lockBody: PropTypes.bool, maskBlur: PropTypes.bool, maskTheme: PropTypes.string, mode: PropTypes.string, onClose: PropTypes.func, onCloseAfterAnimation: PropTypes.func, onOpen: PropTypes.func, onOpenAfterAnimation: PropTypes.func, position: PropTypes.string, showCancelButton: PropTypes.bool, showCloseButton: PropTypes.bool, showConfirmButton: PropTypes.bool, theme: PropTypes.string, title: PropTypes.string, titleIndicatorType: PropTypes.string, zIndex: PropTypes.number, }; Dialog.displayName = 'Dialog'; export default Dialog;