UNPKG

@zendeskgarden/react-modals

Version:

Components relating to modals in the Garden Design System

183 lines (180 loc) 5.82 kB
/** * Copyright Zendesk, Inc. * * Use of this source code is governed under the Apache License, Version 2.0 * found at http://www.apache.org/licenses/LICENSE-2.0. */ import React__default, { forwardRef, useRef, useContext, useState, useEffect, useMemo } from 'react'; import ReactDOM from 'react-dom'; import PropTypes from 'prop-types'; import { mergeRefs } from 'react-merge-refs'; import { CSSTransition } from 'react-transition-group'; import { ThemeContext } from 'styled-components'; import { useModal } from '@zendeskgarden/container-modal'; import { useDocument, useText } from '@zendeskgarden/react-theming'; import activeElement from 'dom-helpers/activeElement'; import { ModalsContext } from '../../utils/useModalContext.js'; import { StyledBackdrop } from '../../styled/StyledBackdrop.js'; import '../../styled/StyledBody.js'; import '../../styled/StyledClose.js'; import '../../styled/StyledFooter.js'; import '../../styled/StyledFooterItem.js'; import '../../styled/StyledHeader.js'; import '../../styled/StyledDangerIcon.js'; import '../../styled/StyledModal.js'; import '../../styled/StyledTooltipDialogBackdrop.js'; import '../../styled/StyledTooltipWrapper.js'; import '../../styled/StyledTooltipDialog.js'; import '../../styled/StyledTooltipDialogTitle.js'; import '../../styled/StyledTooltipDialogBody.js'; import '../../styled/StyledTooltipDialogFooter.js'; import '../../styled/StyledTooltipDialogFooterItem.js'; import '../../styled/StyledTooltipDialogClose.js'; import { StyledDrawer } from '../../styled/StyledDrawer.js'; import '../../styled/StyledDrawerHeader.js'; import '../../styled/StyledDrawerClose.js'; import '../../styled/StyledDrawerBody.js'; import '../../styled/StyledDrawerFooter.js'; import '../../styled/StyledDrawerFooterItem.js'; import { Header } from './Header.js'; import { Body } from './Body.js'; import { Close } from './Close.js'; import { Footer } from './Footer.js'; import { FooterItem } from './FooterItem.js'; const DrawerComponent = forwardRef((_ref, ref) => { let { id, isOpen, onClose, backdropProps, appendToNode, focusOnMount, restoreFocus, ...props } = _ref; const modalRef = useRef(null); const transitionRef = useRef(null); const triggerRef = useRef(null); const theme = useContext(ThemeContext); const environment = useDocument(theme); const [isCloseButtonPresent, setIsCloseButtonPresent] = useState(false); const [hasHeader, setHasHeader] = useState(false); const { getTitleProps, getCloseProps, getContentProps, getBackdropProps, getModalProps } = useModal({ idPrefix: id, modalRef, focusOnMount: false , restoreFocus: false , environment, onClose }); useEffect(() => { if (environment) { if (isOpen && modalRef.current) { if (restoreFocus) { triggerRef.current = activeElement(environment); } if (focusOnMount) { modalRef.current.focus(); } } if (!isOpen && triggerRef.current) { triggerRef.current.focus(); } } return () => { if (!(restoreFocus && isOpen)) { triggerRef.current = null; } }; }, [environment, restoreFocus, focusOnMount, isOpen]); useEffect(() => { if (!environment) { return undefined; } const htmlElement = environment.querySelector('html'); let previousHtmlOverflow; if (htmlElement && isOpen) { previousHtmlOverflow = htmlElement.style.overflow; htmlElement.style.overflow = 'hidden'; } return () => { if (htmlElement && isOpen) { htmlElement.style.overflow = previousHtmlOverflow; } }; }, [environment, isOpen]); const rootNode = useMemo(() => { if (appendToNode) { return appendToNode; } if (environment) { return environment.body; } return undefined; }, [appendToNode, environment]); const value = useMemo(() => ({ isCloseButtonPresent, hasHeader, setHasHeader, getTitleProps, getContentProps, getCloseProps, setIsCloseButtonPresent }), [isCloseButtonPresent, hasHeader, getTitleProps, getContentProps, getCloseProps]); const modalProps = getModalProps({ 'aria-describedby': undefined, ...(hasHeader ? {} : { 'aria-labelledby': undefined }) }); const attribute = hasHeader ? 'aria-labelledby' : 'aria-label'; const defaultValue = hasHeader ? modalProps['aria-labelledby'] : 'Modal dialog'; const labelValue = hasHeader ? modalProps['aria-labelledby'] : props['aria-label']; const ariaProps = { [attribute]: useText(DrawerComponent, { [attribute]: labelValue }, attribute, defaultValue) }; if (!rootNode) { return null; } return ReactDOM.createPortal(React__default.createElement(ModalsContext.Provider, { value: value }, React__default.createElement(CSSTransition, { in: isOpen, timeout: 250, unmountOnExit: true, classNames: "garden-drawer-transition", nodeRef: transitionRef }, React__default.createElement(StyledBackdrop, Object.assign({ $isAnimated: true }, getBackdropProps(backdropProps)), React__default.createElement(StyledDrawer, Object.assign({}, modalProps, ariaProps, props, { ref: mergeRefs([ref, modalRef, transitionRef]) }))))), rootNode); }); DrawerComponent.displayName = 'Drawer'; DrawerComponent.propTypes = { backdropProps: PropTypes.object, focusOnMount: PropTypes.bool, restoreFocus: PropTypes.bool, onClose: PropTypes.func, appendToNode: PropTypes.any, isOpen: PropTypes.bool }; DrawerComponent.defaultProps = { focusOnMount: true , restoreFocus: true }; const Drawer = DrawerComponent; Drawer.Body = Body; Drawer.Close = Close; Drawer.Footer = Footer; Drawer.FooterItem = FooterItem; Drawer.Header = Header; export { Drawer };