UNPKG

@mskcc/carbon-react

Version:

Carbon react components for the MSKCC DSM

282 lines (266 loc) 9.96 kB
/** * MSKCC 2021, 2024 */ 'use strict'; 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;