@mskcc/carbon-react
Version:
Carbon react components for the MSKCC DSM
282 lines (266 loc) • 9.96 kB
JavaScript
/**
* MSKCC 2021, 2024
*/
;
Object.defineProperty(exports, '__esModule', { value: true });
var _rollupPluginBabelHelpers = require('../../_virtual/_rollupPluginBabelHelpers.js');
var cx = require('classnames');
var PropTypes = require('prop-types');
var React = require('react');
var useIsomorphicEffect = require('../../internal/useIsomorphicEffect.js');
var useMergedRefs = require('../../internal/useMergedRefs.js');
var usePrefix = require('../../internal/usePrefix.js');
var useEvent = require('../../internal/useEvent.js');
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
var cx__default = /*#__PURE__*/_interopDefaultLegacy(cx);
var PropTypes__default = /*#__PURE__*/_interopDefaultLegacy(PropTypes);
var React__default = /*#__PURE__*/_interopDefaultLegacy(React);
const PopoverContext = /*#__PURE__*/React__default["default"].createContext({
floating: {
current: null
}
});
function PopoverRenderFunction(_ref, forwardRef) {
let {
isTabTip,
align = isTabTip ? 'bottom-left' : 'bottom',
as: BaseComponent = 'span',
autoAlign = false,
caret = isTabTip ? false : true,
className: customClassName,
children,
dropShadow = true,
highContrast = false,
onRequestClose,
open,
...rest
} = _ref;
const prefix = usePrefix.usePrefix();
const floating = React.useRef(null);
const popover = React.useRef(null);
// If the `Popover` is the last focusable item in the tab order, it should also close when the browser window loses focus (#12922)
useEvent.useWindowEvent('blur', () => {
if (open) {
onRequestClose?.();
}
});
useEvent.useWindowEvent('click', event => {
if (open && !popover?.current?.contains(event.target)) {
onRequestClose?.();
}
});
const value = React.useMemo(() => {
return {
floating
};
}, []);
if (isTabTip) {
const tabTipAlignments = ['bottom-left', 'bottom-right'];
if (!tabTipAlignments.includes(align)) {
align = 'bottom-left';
}
}
const ref = useMergedRefs.useMergedRefs([forwardRef, popover]);
const [autoAligned, setAutoAligned] = React.useState(false);
const [autoAlignment, setAutoAlignment] = React.useState(align);
const className = cx__default["default"]({
[`${prefix}--popover-container`]: true,
[`${prefix}--popover--caret`]: caret,
[`${prefix}--popover--drop-shadow`]: dropShadow,
[`${prefix}--popover--high-contrast`]: highContrast,
[`${prefix}--popover--open`]: open,
[`${prefix}--popover--${autoAlignment}`]: autoAligned && !isTabTip,
[`${prefix}--popover--${align}`]: !autoAligned,
[`${prefix}--popover--tab-tip`]: isTabTip
}, customClassName);
useIsomorphicEffect["default"](() => {
if (!open) {
return;
}
if (!autoAlign || isTabTip) {
setAutoAligned(false);
return;
}
if (!floating.current) {
return;
}
if (autoAligned === true) {
return;
}
const rect = floating.current.getBoundingClientRect();
// The conditions, per side, of when the popover is not visible, excluding the popover's internal padding(16)
const conditions = {
left: rect.x < -16,
top: rect.y < -16,
right: rect.x + (rect.width - 16) > document.documentElement.clientWidth,
bottom: rect.y + (rect.height - 16) > document.documentElement.clientHeight
};
if (!conditions.left && !conditions.top && !conditions.right && !conditions.bottom) {
setAutoAligned(false);
return;
}
const alignments = ['top', 'top-left', 'right-bottom', 'right', 'right-top', 'bottom-left', 'bottom', 'bottom-right', 'left-top', 'left', 'left-bottom', 'top-right'];
// Creates the prioritized list of options depending on ideal alignment coming from `align`
const options = [align];
let option = alignments[(alignments.indexOf(align) + 1) % alignments.length];
while (option) {
if (options.includes(option)) {
break;
}
options.push(option);
option = alignments[(alignments.indexOf(option) + 1) % alignments.length];
}
function isVisible(alignment) {
if (!popover.current || !floating.current) {
return false;
}
popover.current.classList.add(`${prefix}--popover--${alignment}`);
const rect = floating.current.getBoundingClientRect();
// Check if popover is not visible to the left of the screen
if (rect.x < -16) {
popover.current.classList.remove(`${prefix}--popover--${alignment}`);
return false;
}
// Check if popover is not visible at the top of the screen
if (rect.y < -16) {
popover.current.classList.remove(`${prefix}--popover--${alignment}`);
return false;
}
// Check if popover is not visible to right of screen
if (rect.x + (rect.width - 16) > document.documentElement.clientWidth) {
popover.current.classList.remove(`${prefix}--popover--${alignment}`);
return false;
}
// Check if popover is not visible to bottom of screen
if (rect.y + (rect.height - 16) > document.documentElement.clientHeight) {
popover.current.classList.remove(`${prefix}--popover--${alignment}`);
return false;
}
popover.current.classList.remove(`${prefix}--popover--${alignment}`);
return true;
}
let alignment = null;
for (let i = 0; i < options.length; i++) {
const option = options[i];
if (isVisible(option)) {
alignment = option;
break;
}
}
if (alignment) {
setAutoAligned(true);
setAutoAlignment(alignment);
}
}, [autoAligned, align, autoAlign, prefix, open, isTabTip]);
const mappedChildren = React__default["default"].Children.map(children, child => {
const item = child;
if (item?.type === 'button') {
const {
className
} = item.props;
const tabTipClasses = cx__default["default"](`${prefix}--popover--tab-tip__button`, className);
return /*#__PURE__*/React__default["default"].cloneElement(item, {
className: tabTipClasses
});
} else {
return item;
}
});
const BaseComponentAsAny = BaseComponent;
return /*#__PURE__*/React__default["default"].createElement(PopoverContext.Provider, {
value: value
}, /*#__PURE__*/React__default["default"].createElement(BaseComponentAsAny, _rollupPluginBabelHelpers["extends"]({}, rest, {
className: className,
ref: ref
}), isTabTip ? mappedChildren : children));
}
const Popover = /*#__PURE__*/React__default["default"].forwardRef(PopoverRenderFunction);
// Note: this displayName is temporarily set so that Storybook ArgTable
// correctly displays the name of this component
if (process.env.NODE_ENV !== "production") {
Popover.displayName = 'Popover';
}
Popover.propTypes = {
/**
* Specify how the popover should align with the trigger element
*/
align: PropTypes__default["default"].oneOf(['top', 'top-left', 'top-right', 'bottom', 'bottom-left', 'bottom-right', 'left', 'left-bottom', 'left-top', 'right', 'right-bottom', 'right-top']),
/**
* Provide a custom element or component to render the top-level node for the
* component.
*/
as: PropTypes__default["default"].oneOfType([PropTypes__default["default"].string, PropTypes__default["default"].elementType]),
/**
* Will auto-align the popover on first render if it is not visible. This prop is currently experimental and is subject to future changes.
*/
autoAlign: PropTypes__default["default"].bool,
/**
* Specify whether a caret should be rendered
*/
caret: PropTypes__default["default"].bool,
/**
* Provide elements to be rendered inside of the component
*/
children: PropTypes__default["default"].node,
/**
* Provide a custom class name to be added to the outermost node in the
* component
*/
className: PropTypes__default["default"].string,
/**
* Specify whether a drop shadow should be rendered on the popover
*/
dropShadow: PropTypes__default["default"].bool,
/**
* Render the component using the high-contrast variant
*/
highContrast: PropTypes__default["default"].bool,
/**
* Render the component using the tab tip variant
*/
isTabTip: PropTypes__default["default"].bool,
/**
* Specify a handler for closing popover.
* The handler should take care of closing the popover, e.g. changing the `open` prop.
*/
onRequestClose: PropTypes__default["default"].func,
/**
* Specify whether the component is currently open or closed
*/
open: PropTypes__default["default"].bool.isRequired
};
function PopoverContentRenderFunction( // eslint-disable-next-line react/prop-types
_ref2, forwardRef) {
let {
className,
children,
...rest
} = _ref2;
const prefix = usePrefix.usePrefix();
const {
floating
} = React__default["default"].useContext(PopoverContext);
const ref = useMergedRefs.useMergedRefs([floating, forwardRef]);
return /*#__PURE__*/React__default["default"].createElement("span", _rollupPluginBabelHelpers["extends"]({}, rest, {
className: `${prefix}--popover`
}), /*#__PURE__*/React__default["default"].createElement("span", {
className: cx__default["default"](`${prefix}--popover-content`, className),
ref: ref
}, children), /*#__PURE__*/React__default["default"].createElement("span", {
className: `${prefix}--popover-caret`
}));
}
const PopoverContent = /*#__PURE__*/React__default["default"].forwardRef(PopoverContentRenderFunction);
PopoverContent.propTypes = {
/**
* Provide elements to be rendered inside of the component
*/
children: PropTypes__default["default"].node,
/**
* Provide a custom class name to be added to the outermost node in the
* component
*/
className: PropTypes__default["default"].string
};
exports.Popover = Popover;
exports.PopoverContent = PopoverContent;