UNPKG

@atlaskit/inline-dialog

Version:

An inline dialog is a pop-up container for small amounts of information. It can also contain controls.

183 lines (175 loc) 5.85 kB
import React, { memo, useCallback, useEffect, useMemo, useRef } from 'react'; import { bind } from 'bind-event-listener'; import { usePlatformLeafEventHandler } from '@atlaskit/analytics-next'; import noop from '@atlaskit/ds-lib/noop'; import { Layering, useCloseOnEscapePress, useLayering } from '@atlaskit/layering'; import { fg } from '@atlaskit/platform-feature-flags'; import { Manager, Popper, Reference } from '@atlaskit/popper'; import NodeResolverWrapper from './node-resolver-wrapper'; import { Container } from './styled/container'; const checkIsChildOfPortal = node => { if (!node) { return false; } return node.classList && node.classList.contains('atlaskit-portal-container') || checkIsChildOfPortal(node.parentElement); }; // Close manager for layering const CloseManager = ({ handleEscapeClose, handleClick }) => { useCloseOnEscapePress({ onClose: handleEscapeClose }); const { isLayerDisabled } = useLayering(); useEffect(() => { return bind(window, { type: 'click', listener: e => { if (isLayerDisabled()) { return; } handleClick(e); }, options: { capture: true } }); }, [handleClick, isLayerDisabled]); // only create a dummy component for using ths hook in class component return /*#__PURE__*/React.createElement("span", null); }; const InlineDialog = /*#__PURE__*/memo(function InlineDialog({ isOpen = false, onContentBlur = noop, onContentClick = noop, onContentFocus = noop, onClose: providedOnClose = noop, placement = 'bottom-start', strategy = 'fixed', testId, content, children, fallbackPlacements }) { const containerRef = useRef(null); const triggerRef = useRef(null); const onClose = usePlatformLeafEventHandler({ fn: event => providedOnClose(event), action: 'closed', componentName: 'inlineDialog', packageName: "@atlaskit/inline-dialog", packageVersion: "17.2.5" }); // we put this into a ref to avoid handleCloseRequest having this as a dependency const onCloseRef = useRef(onClose); useEffect(() => { onCloseRef.current = onClose; }); const handleCloseRequest = useCallback(event => { const { target } = event; // checks for when target is not HTMLElement if (!(target instanceof HTMLElement)) { return; } // TODO: This is to handle the case where the target is no longer in the DOM. // This happens with react-select in datetime picker. There might be other // edge cases for this. if (!document.body.contains(target)) { return; } if (isOpen) { var _onCloseRef$current; (_onCloseRef$current = onCloseRef.current) === null || _onCloseRef$current === void 0 ? void 0 : _onCloseRef$current.call(onCloseRef, { isOpen: false, event }); return; } // handles the case where inline dialog opens portalled elements such as modal if (checkIsChildOfPortal(target)) { return; } // call onClose if the click originated from outside the dialog if (containerRef.current && !containerRef.current.contains(target)) { var _onCloseRef$current2; (_onCloseRef$current2 = onCloseRef.current) === null || _onCloseRef$current2 === void 0 ? void 0 : _onCloseRef$current2.call(onCloseRef, { isOpen: false, event }); } }, [isOpen]); const handleClick = useCallback(event => { var _containerRef$current; // exit if we click outside but on the trigger — it can handle the clicks itself if (triggerRef.current && triggerRef.current.contains(event.target)) { return; } if ((_containerRef$current = containerRef.current) !== null && _containerRef$current !== void 0 && _containerRef$current.contains(event.target)) { return; } handleCloseRequest(event); }, [handleCloseRequest, containerRef, triggerRef]); /** * Auto-flip is enabled by default in `@atlaskit/popper` and * the `InlineDialog` API does not allow disabling it. * * We only need to override it if there are `fallbackPlacements` specified. */ const modifiers = useMemo(() => fallbackPlacements ? [{ name: 'flip', options: { fallbackPlacements } }] : [], [fallbackPlacements]); const popper = isOpen ? /*#__PURE__*/React.createElement(Popper, { placement: placement, strategy: strategy, modifiers: modifiers }, ({ ref, style }) => /*#__PURE__*/React.createElement(Container, { onBlur: onContentBlur, onFocus: onContentFocus, onClick: onContentClick, ref: node => { if (node) { containerRef.current = node; if (typeof ref === 'function') { ref(node); } else { ref.current = node; } } } // eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop -- Ignored via go/DSP-18766 , style: style, testId: testId }, typeof content === 'function' ? content() : content)) : null; return /*#__PURE__*/React.createElement(Manager, null, /*#__PURE__*/React.createElement(Reference, null, ({ ref }) => /*#__PURE__*/React.createElement(NodeResolverWrapper, { innerRef: node => { triggerRef.current = node; if (typeof ref === 'function') { ref(node); } else { ref.current = node; } }, hasNodeResolver: !fg('platform_design_system_team_portal_logic_r18_fix') }, /*#__PURE__*/React.createElement(React.Fragment, null, children))), isOpen ? /*#__PURE__*/React.createElement(Layering, { isDisabled: false }, popper, /*#__PURE__*/React.createElement(CloseManager, { handleEscapeClose: handleCloseRequest, handleClick: handleClick })) : popper); }); // eslint-disable-next-line @repo/internal/react/require-jsdoc export default InlineDialog;