UNPKG

@zendeskgarden/react-modals

Version:

Components relating to modals in the Garden Design System

193 lines (190 loc) 6.87 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, { useContext, useRef, useState, useEffect } from 'react'; import PropTypes from 'prop-types'; import { ThemeContext } from 'styled-components'; import { CSSTransition } from 'react-transition-group'; import { useFloating, platform, offset, autoPlacement, flip, autoUpdate } from '@floating-ui/react-dom'; import { useModal } from '@zendeskgarden/container-modal'; import { mergeRefs } from 'react-merge-refs'; import { TooltipDialogContext } from '../../utils/useTooltipDialogContext.js'; import '../../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 { StyledTooltipDialogBackdrop } from '../../styled/StyledTooltipDialogBackdrop.js'; import { StyledTooltipWrapper } from '../../styled/StyledTooltipWrapper.js'; import { StyledTooltipDialog } from '../../styled/StyledTooltipDialog.js'; import '../../styled/StyledTooltipDialogTitle.js'; import '../../styled/StyledTooltipDialogBody.js'; import '../../styled/StyledTooltipDialogFooter.js'; import '../../styled/StyledTooltipDialogFooterItem.js'; import '../../styled/StyledTooltipDialogClose.js'; import '../../styled/StyledDrawer.js'; import '../../styled/StyledDrawerHeader.js'; import '../../styled/StyledDrawerClose.js'; import '../../styled/StyledDrawerBody.js'; import '../../styled/StyledDrawerFooter.js'; import '../../styled/StyledDrawerFooterItem.js'; import { PLACEMENT } from '../../types/index.js'; import { Title } from './Title.js'; import { Body } from './Body.js'; import { Close } from './Close.js'; import { Footer } from './Footer.js'; import { FooterItem } from './FooterItem.js'; import { DEFAULT_THEME, getFloatingPlacements, useText } from '@zendeskgarden/react-theming'; import { createPortal } from 'react-dom'; const PLACEMENT_DEFAULT = 'top'; const TooltipDialogComponent = React__default.forwardRef((_ref, ref) => { let { appendToNode, referenceElement, placement: _placement = 'auto', fallbackPlacements: _fallbackPlacements, offset: _offset, onClose, hasArrow = true, isAnimated, zIndex, backdropProps, focusOnMount = true, restoreFocus = true, id, ...props } = _ref; const theme = useContext(ThemeContext) || DEFAULT_THEME; const previousReferenceElementRef = useRef(); const modalRef = useRef(null); const transitionRef = useRef(null); const [floatingElement, setFloatingElement] = useState(); const [hasTitle, setHasTitle] = useState(false); const { getTitleProps, getCloseProps, getContentProps, getBackdropProps, getModalProps } = useModal({ idPrefix: id, onClose, modalRef, focusOnMount, restoreFocus: false }); const [floatingPlacement, fallbackPlacements] = getFloatingPlacements(theme, _placement === 'auto' ? PLACEMENT_DEFAULT : _placement, _fallbackPlacements); const { refs, placement, update, floatingStyles: { transform } } = useFloating({ platform: { ...platform, isRTL: () => theme.rtl }, elements: { reference: referenceElement, floating: floatingElement }, placement: floatingPlacement, middleware: [offset(_offset === undefined ? theme.space.base * 3 : _offset), _placement === 'auto' ? autoPlacement() : flip({ fallbackPlacements })] }); useEffect(() => { let cleanup; if (referenceElement && floatingElement && refs.reference.current && refs.floating.current) { cleanup = autoUpdate(refs.reference.current, refs.floating.current, update, { elementResize: typeof ResizeObserver === 'function' }); } return () => cleanup && cleanup(); }, [referenceElement, floatingElement, refs.reference, refs.floating, update]); useEffect(() => { if (!referenceElement && previousReferenceElementRef.current && restoreFocus) { previousReferenceElementRef.current.focus(); } previousReferenceElementRef.current = referenceElement; }, [referenceElement, restoreFocus]); const modalProps = getModalProps({ 'aria-describedby': undefined, ...(hasTitle ? {} : { 'aria-labelledby': undefined }) }); const attribute = hasTitle ? 'aria-labelledby' : 'aria-label'; const defaultValue = hasTitle ? modalProps['aria-labelledby'] : 'Modal dialog'; const labelValue = hasTitle ? modalProps['aria-labelledby'] : props['aria-label']; const ariaProps = { [attribute]: useText(TooltipDialogComponent, { [attribute]: labelValue }, attribute, defaultValue, modalRef.current !== null ) }; const value = { hasTitle, setHasTitle, getTitleProps, getContentProps, getCloseProps }; const Node = React__default.createElement(CSSTransition, { unmountOnExit: true, timeout: isAnimated ? 200 : 0, in: Boolean(referenceElement), classNames: isAnimated ? 'garden-tooltip-modal-transition' : '', nodeRef: transitionRef }, transitionState => { return React__default.createElement(TooltipDialogContext.Provider, { value: value }, React__default.createElement(StyledTooltipDialogBackdrop, Object.assign({}, getBackdropProps(), backdropProps, { ref: transitionRef }), React__default.createElement(StyledTooltipWrapper, { ref: setFloatingElement, style: { transform }, $placement: placement, $zIndex: zIndex, $isAnimated: isAnimated }, React__default.createElement(StyledTooltipDialog, Object.assign({ $transitionState: transitionState, $placement: placement, $hasArrow: hasArrow, $isAnimated: isAnimated }, modalProps, ariaProps, props, { ref: mergeRefs([modalRef, ref]) }))))); }); return appendToNode ? createPortal(Node, appendToNode) : Node; }); TooltipDialogComponent.displayName = 'TooltipDialog'; TooltipDialogComponent.propTypes = { appendToNode: PropTypes.any, referenceElement: PropTypes.any, placement: PropTypes.any, fallbackPlacements: PropTypes.arrayOf(PropTypes.oneOf(PLACEMENT.filter(placement => placement !== 'auto'))), isAnimated: PropTypes.bool, hasArrow: PropTypes.bool, zIndex: PropTypes.number, onClose: PropTypes.func, backdropProps: PropTypes.any, focusOnMount: PropTypes.bool, restoreFocus: PropTypes.bool }; const TooltipDialog = TooltipDialogComponent; TooltipDialog.Body = Body; TooltipDialog.Close = Close; TooltipDialog.Footer = Footer; TooltipDialog.FooterItem = FooterItem; TooltipDialog.Title = Title; export { TooltipDialog };