@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
JavaScript
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;