UNPKG

@gechiui/components

Version:
182 lines (170 loc) 4.11 kB
/** * External dependencies */ import classnames from 'classnames'; /** * GeChiUI dependencies */ import { createPortal, useEffect, useRef } from '@gechiui/element'; import { useInstanceId, useFocusReturn, useFocusOnMount, __experimentalUseFocusOutside as useFocusOutside, useConstrainedTabbing, useMergeRefs, } from '@gechiui/compose'; import deprecated from '@gechiui/deprecated'; import { ESCAPE } from '@gechiui/keycodes'; import { __ } from '@gechiui/i18n'; import { closeSmall } from '@gechiui/icons'; /** * Internal dependencies */ import * as ariaHelper from './aria-helper'; import Button from '../button'; import StyleProvider from '../style-provider'; // Used to count the number of open modals. let openModalCount = 0; export default function Modal( { bodyOpenClassName = 'modal-open', role = 'dialog', title = null, focusOnMount = true, shouldCloseOnEsc = true, shouldCloseOnClickOutside = true, isDismissable, // Deprecated isDismissible = isDismissable || true, /* accessibility */ aria = { labelledby: null, describedby: null, }, onRequestClose, icon, closeButtonLabel, children, style, overlayClassName, className, contentLabel, onKeyDown, isFullScreen = false, } ) { const ref = useRef(); const instanceId = useInstanceId( Modal ); const headingId = title ? `components-modal-header-${ instanceId }` : aria.labelledby; const focusOnMountRef = useFocusOnMount( focusOnMount ); const constrainedTabbingRef = useConstrainedTabbing(); const focusReturnRef = useFocusReturn(); const focusOutsideProps = useFocusOutside( onRequestClose ); useEffect( () => { openModalCount++; if ( openModalCount === 1 ) { ariaHelper.hideApp( ref.current ); document.body.classList.add( bodyOpenClassName ); } return () => { openModalCount--; if ( openModalCount === 0 ) { document.body.classList.remove( bodyOpenClassName ); ariaHelper.showApp(); } }; }, [] ); if ( isDismissable ) { deprecated( 'isDismissable prop of the Modal component', { since: '5.4', alternative: 'isDismissible prop (renamed) of the Modal component', } ); } function handleEscapeKeyDown( event ) { if ( shouldCloseOnEsc && event.keyCode === ESCAPE && ! event.defaultPrevented ) { event.preventDefault(); if ( onRequestClose ) { onRequestClose( event ); } } } return createPortal( // eslint-disable-next-line jsx-a11y/no-static-element-interactions <div ref={ ref } className={ classnames( 'components-modal__screen-overlay', overlayClassName ) } onKeyDown={ handleEscapeKeyDown } > <StyleProvider document={ document }> <div className={ classnames( 'components-modal__frame', className, { 'is-full-screen': isFullScreen, } ) } style={ style } ref={ useMergeRefs( [ constrainedTabbingRef, focusReturnRef, focusOnMountRef, ] ) } role={ role } aria-label={ contentLabel } aria-labelledby={ contentLabel ? null : headingId } aria-describedby={ aria.describedby } tabIndex="-1" { ...( shouldCloseOnClickOutside ? focusOutsideProps : {} ) } onKeyDown={ onKeyDown } > <div className={ 'components-modal__content' } role="document" > <div className="components-modal__header"> <div className="components-modal__header-heading-container"> { icon && ( <span className="components-modal__icon-container" aria-hidden > { icon } </span> ) } { title && ( <h1 id={ headingId } className="components-modal__header-heading" > { title } </h1> ) } </div> { isDismissible && ( <Button onClick={ onRequestClose } icon={ closeSmall } label={ closeButtonLabel || __( '关闭对话框' ) } /> ) } </div> { children } </div> </div> </StyleProvider> </div>, document.body ); }