UNPKG

@momentum-ui/react-collaboration

Version:

Cisco Momentum UI Framework for React Collaboration Applications

149 lines 12.9 kB
var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; import React, { forwardRef, useCallback, useEffect, useRef } from 'react'; import './Popover.style.scss'; import ModalContainer from '../ModalContainer'; import ButtonCircle from '../ButtonCircle'; import { LazyTippy } from './LazyTippy'; import { ARROW_ID, ELEVATIONS, ROUNDS } from '../ModalContainer/ModalContainer.constants'; import { ARROW_PADDING, DEFAULTS, STYLE } from './Popover.constants'; import { ARROW_HEIGHT } from '../ModalArrow/ModalArrow.constants'; import classNames from 'classnames'; import { isMRv2Button } from '../../helpers/verifyTypes'; import { addTippyPlugins } from './Popover.utils'; // eslint-disable-next-line import/no-unresolved import { v4 as uuidV4 } from 'uuid'; /** * The Popover component allows adding a Popover to whatever provided * `triggerComponent`. It will show the Popover after a specific event, which is * defined by the provided `trigger` prop. * * Popover uses @tippyjs/react under the hood - possible attributes for future modification * can be found here: https://atomiks.github.io/tippyjs/v6/all-props/ */ var Popover = forwardRef(function (props, ref) { var _a, _b, _c; var children = props.children, _d = props.trigger, triggerFromProps = _d === void 0 ? DEFAULTS.TRIGGER : _d, triggerComponent = props.triggerComponent, _e = props.variant, variant = _e === void 0 ? DEFAULTS.VARIANT : _e, _f = props.placement, placement = _f === void 0 ? DEFAULTS.PLACEMENT : _f, _g = props.interactive, interactive = _g === void 0 ? DEFAULTS.INTERACTIVE : _g, _h = props.showArrow, showArrow = _h === void 0 ? DEFAULTS.SHOW_ARROW : _h, _j = props.offsetDistance, offsetDistance = _j === void 0 ? DEFAULTS.OFFSET_DISTANCE : _j, _k = props.offsetSkidding, offsetSkidding = _k === void 0 ? DEFAULTS.OFFSET_SKIDDING : _k, color = props.color, delay = props.delay, setInstance = props.setInstance, className = props.className, id = props.id, style = props.style, _l = props.boundary, boundary = _l === void 0 ? DEFAULTS.BOUNDARY : _l, _m = props.hideOnEsc, hideOnEsc = _m === void 0 ? DEFAULTS.HIDE_ON_ESC : _m, _o = props.hideOnBlur, hideOnBlur = _o === void 0 ? DEFAULTS.HIDE_ON_BLUR : _o, _p = props.singleOpenGroupId, singleOpenGroupId = _p === void 0 ? undefined : _p, _q = props.isChildPopoverOpen, isChildPopoverOpen = _q === void 0 ? DEFAULTS.IS_CHILD_POPOVER_OPEN : _q, _r = props.addBackdrop, addBackdrop = _r === void 0 ? DEFAULTS.ADD_BACKDROP : _r, focusBackOnTriggerFromProps = props.focusBackOnTrigger, _s = props.closeButtonPlacement, closeButtonPlacement = _s === void 0 ? DEFAULTS.CLOSE_BUTTON_PLACEMENT : _s, _t = props.disableFocusLock, disableFocusLock = _t === void 0 ? DEFAULTS.DISABLE_FOCUS_LOCK : _t, closeButtonProps = props.closeButtonProps, _u = props.strategy, strategy = _u === void 0 ? DEFAULTS.STRATEGY : _u, _v = props.role, role = _v === void 0 ? DEFAULTS.ROLE : _v, onAfterUpdate = props.onAfterUpdate, onBeforeUpdate = props.onBeforeUpdate, onCreate = props.onCreate, onDestroy = props.onDestroy, onHidden = props.onHidden, onHide = props.onHide, onMount = props.onMount, onShow = props.onShow, onShown = props.onShown, onTrigger = props.onTrigger, onUntrigger = props.onUntrigger, onClickOutside = props.onClickOutside, firstFocusElement = props.firstFocusElement, _w = props.autoFocus, autoFocus = _w === void 0 ? DEFAULTS.AUTO_FOCUS : _w, _x = props.appendTo, appendTo = _x === void 0 ? DEFAULTS.APPEND_TO : _x, continuePropagationOnTrigger = props.continuePropagationOnTrigger, providedAriaLabelledby = props["aria-labelledby"], zIndex = props.zIndex, ariaLabel = props["aria-label"], rest = __rest(props, ["children", "trigger", "triggerComponent", "variant", "placement", "interactive", "showArrow", "offsetDistance", "offsetSkidding", "color", "delay", "setInstance", "className", "id", "style", "boundary", "hideOnEsc", "hideOnBlur", "singleOpenGroupId", "isChildPopoverOpen", "addBackdrop", "focusBackOnTrigger", "closeButtonPlacement", "disableFocusLock", "closeButtonProps", "strategy", "role", "onAfterUpdate", "onBeforeUpdate", "onCreate", "onDestroy", "onHidden", "onHide", "onMount", "onShow", "onShown", "onTrigger", "onUntrigger", "onClickOutside", "firstFocusElement", "autoFocus", "appendTo", "continuePropagationOnTrigger", 'aria-labelledby', "zIndex", 'aria-label']); if ((hideOnBlur && !disableFocusLock) || (hideOnBlur && !interactive)) { console.warn('MRV2 Popover: This component cannot hideOnBlur when focus locked. disableFocusLock must be true if hideOnBlur is true. Additionally, hideOnBlur will only have an effect if the popover has interactive={true}'); } var focusBackOnTrigger = focusBackOnTriggerFromProps !== null && focusBackOnTriggerFromProps !== void 0 ? focusBackOnTriggerFromProps : (interactive ? DEFAULTS.FOCUS_BACK_ON_TRIGGER_COMPONENT_INTERACTIVE : DEFAULTS.FOCUS_BACK_ON_TRIGGER_COMPONENT_NON_INTERACTIVE); var popoverInstance = React.useRef(undefined); var generatedTriggerIdRef = useRef(uuidV4()); var generatedTriggerId = generatedTriggerIdRef.current; var ariaLabelledby = providedAriaLabelledby || ((_a = triggerComponent.props) === null || _a === void 0 ? void 0 : _a.id) || generatedTriggerId; var isGenericAriaRole = ['generic', 'presentation', 'none'].includes(role); var modalConditionalProps = __assign({}, (interactive && __assign(__assign(__assign({}, ((providedAriaLabelledby || !ariaLabel) && !isGenericAriaRole && { 'aria-labelledby': ariaLabelledby })), (ariaLabel && !isGenericAriaRole && { 'aria-label': ariaLabel })), { focusLockProps: !disableFocusLock ? { restoreFocus: focusBackOnTrigger, autoFocus: autoFocus } : undefined }))); // memoize arrow id to avoid memory leak (arrow will be different, but JS still tries to find old ones): var arrowId = React.useMemo(function () { return "".concat(ARROW_ID).concat(uuidV4()); }, []); var popoverSetInstance = useCallback(function (instance) { if (instance) { // initialize to whatever prop value is // from now on, will be controlled by hideOnBlur plugin instance.shouldFocusTrigger = focusBackOnTrigger; } popoverInstance.current = instance; setInstance === null || setInstance === void 0 ? void 0 : setInstance(instance); }, [setInstance, focusBackOnTrigger]); var handleOnCloseButtonClick = useCallback(function () { var _a; (_a = popoverInstance.current) === null || _a === void 0 ? void 0 : _a.hide(); }, []); // needs special handling since FocusScope doesn't work with the Popover from Tippy // needs to focus back to the reference item when the popover is completely hidden var handleOnPopoverHidden = useCallback(function () { // When the popover hides, the focus goes to the next focusable element by default. Except if focusBackOnTrigger popover prop is true AND shouldFocusTrigger (determined by hideOnBlurPlugin) is true. // shouldFocusTrigger is true when the focusout event has no relatedTarget, that is, focusOut was triggered by Esc or Click. var _a, _b, _c; if (focusBackOnTrigger && ((_a = popoverInstance === null || popoverInstance === void 0 ? void 0 : popoverInstance.current) === null || _a === void 0 ? void 0 : _a.shouldFocusTrigger)) { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore (_c = (_b = popoverInstance.current) === null || _b === void 0 ? void 0 : _b.reference) === null || _c === void 0 ? void 0 : _c.focus(); } }, [focusBackOnTrigger, (_b = popoverInstance === null || popoverInstance === void 0 ? void 0 : popoverInstance.current) === null || _b === void 0 ? void 0 : _b.shouldFocusTrigger]); useEffect(function () { firstFocusElement === null || firstFocusElement === void 0 ? void 0 : firstFocusElement.focus(); }, [firstFocusElement]); var triggerComponentCommonProps = {}; if (interactive) { triggerComponentCommonProps['aria-haspopup'] = ((_c = triggerComponent === null || triggerComponent === void 0 ? void 0 : triggerComponent.props) === null || _c === void 0 ? void 0 : _c['aria-haspopup']) || 'dialog'; if (!providedAriaLabelledby && !ariaLabel) { triggerComponentCommonProps['id'] = ariaLabelledby; } } var mrv2Props = isMRv2Button(triggerComponent) ? { useNativeKeyDown: true } : {}; var clonedTriggerComponent = React.cloneElement(triggerComponent, __assign(__assign({}, triggerComponentCommonProps), mrv2Props)); var trigger = triggerFromProps; if (triggerFromProps === 'mouseenter') { if (interactive) { // if the popover is interactive, there is interactive content inside the popover // so we can't use the focusin trigger, since after closing with escape key, the // popover keeps opening. So we need to use the click trigger instead. trigger = 'mouseenter click'; } else { // non-interactive popovers with trigger mouseenter (like a tooltip) should also open // when focusing to the trigger element trigger = 'mouseenter focusin'; } } return (React.createElement(LazyTippy, __assign({ ref: ref, /* needed to prevent the popover from closing when the focus is changed via click events */ hideOnClick: !trigger.includes('manual'), continuePropagationOnTrigger: continuePropagationOnTrigger, render: function (attrs) { return (React.createElement(ModalContainer, __assign({ id: id, showArrow: showArrow, arrowId: arrowId, placement: attrs['data-placement'], isPadded: true, round: variant === 'medium' ? ROUNDS[75] : ROUNDS[50], elevation: ELEVATIONS[3], style: style, color: color, className: className, role: role, ariaModal: interactive }, modalConditionalProps, rest), closeButtonPlacement !== 'none' && (React.createElement(ButtonCircle, __assign({}, closeButtonProps, { stopPropagation: false, className: classNames(STYLE.closeButton, closeButtonProps === null || closeButtonProps === void 0 ? void 0 : closeButtonProps.className), "data-placement": closeButtonPlacement, size: 20, prefixIcon: "cancel-bold", onClick: handleOnCloseButtonClick }))), children)); }, placement: placement, trigger: trigger, interactive: interactive, appendTo: appendTo, popperOptions: { modifiers: [ { name: 'arrow', enabled: showArrow, options: { element: "#".concat(arrowId), // use unique arrow Id for each popover instance with an arrow padding: ARROW_PADDING, }, }, { name: 'preventOverflow', options: { padding: 8, altAxis: true, boundariesElement: boundary, }, }, ], strategy: strategy, }, animation: false, delay: delay, plugins: addTippyPlugins(hideOnEsc, hideOnBlur, addBackdrop, singleOpenGroupId), // add arrow height to default offset if arrow is shown: offset: [offsetSkidding, showArrow ? ARROW_HEIGHT + offsetDistance : offsetDistance], onAfterUpdate: onAfterUpdate, onBeforeUpdate: onBeforeUpdate, onCreate: onCreate, onDestroy: onDestroy, onHide: onHide, onMount: onMount, onShow: onShow, onShown: onShown, onTrigger: onTrigger, onUntrigger: onUntrigger, onClickOutside: onClickOutside }, (hideOnBlur && { isChildPopoverOpen: isChildPopoverOpen }), { onHidden: function (instance) { handleOnPopoverHidden(); onHidden === null || onHidden === void 0 ? void 0 : onHidden(instance); }, setInstance: popoverSetInstance, zIndex: zIndex }), clonedTriggerComponent)); }); Popover.displayName = 'Popover'; export default Popover; //# sourceMappingURL=Popover.js.map