UNPKG

@carbon/ibm-products

Version:

Carbon for IBM Products

240 lines (230 loc) 7.46 kB
/** * Copyright IBM Corp. 2020, 2025 * * This source code is licensed under the Apache-2.0 license found in the * LICENSE file in the root directory of this source tree. */ import { extends as _extends } from '../../_virtual/_rollupPluginBabelHelpers.js'; import React__default, { forwardRef, useState, useRef, useMemo, useEffect } from 'react'; import uuidv4 from '../../global/js/utils/uuidv4.js'; import PropTypes from '../../_virtual/index.js'; import cx from 'classnames'; import { getDevtoolsProps } from '../../global/js/utils/devtools.js'; import { pkg } from '../../settings.js'; import { CoachmarkDragbar } from './CoachmarkDragbar.js'; import { CoachmarkHeader } from './CoachmarkHeader.js'; import { getOffsetTune } from './utils/constants.js'; import { useCoachmark } from './utils/context.js'; import { COACHMARK_OVERLAY_KIND } from './utils/enums.js'; import { useIsomorphicEffect } from '../../global/js/hooks/useIsomorphicEffect.js'; // The block part of our conventional BEM class names (blockClass__E--M). const blockClass = `${pkg.prefix}--coachmark-overlay`; const componentName = 'CoachmarkOverlay'; // NOTE: the component SCSS is not imported here: it is rolled up separately. const defaults = { kind: COACHMARK_OVERLAY_KIND.FLOATING, theme: 'light' }; /** * DO NOT USE. This component is for the exclusive use * of other Onboarding components. */ let CoachmarkOverlay = /*#__PURE__*/forwardRef((_ref, ref) => { let { children, onClose, fixedIsVisible, className, kind = defaults.kind, theme = defaults.theme, ...rest } = _ref; const { winHeight, winWidth } = useWindowDimensions(); const [a11yDragMode, setA11yDragMode] = useState(false); const overlayRef = useRef(null); const coachmark = useCoachmark(); const isBeacon = kind === COACHMARK_OVERLAY_KIND.TOOLTIP; const isDraggable = kind === COACHMARK_OVERLAY_KIND.FLOATING; const isVisible = className?.includes('is-visible'); const handleKeyPress = event => { const { shiftKey, key } = event; /* istanbul ignore next */ if (key === 'Enter' || key === ' ') { setA11yDragMode(prevVal => !prevVal); } else if (a11yDragMode) { const distanceToMove = shiftKey ? 128 : 32; switch (key) { case 'ArrowLeft': handleDrag(distanceToMove * -1, 0); break; case 'ArrowRight': handleDrag(distanceToMove, 0); break; case 'ArrowUp': handleDrag(0, distanceToMove * -1); break; case 'ArrowDown': handleDrag(0, distanceToMove); break; } } }; const styledTune = useMemo(() => { const style = {}; if (isBeacon || isDraggable) { if (coachmark.targetRect) { style.left = coachmark.targetRect.x + window.scrollX; style.top = coachmark.targetRect.y + window.scrollY; } if (style.left && style.top) { if (isBeacon) { style.left = style.left + 16; style.top = style.top + 16; } if (isDraggable) { const offsetTune = getOffsetTune(coachmark, kind); style.left = style.left + offsetTune.left; style.top = style.top + offsetTune.top; } } } return style; }, [isBeacon, isDraggable, coachmark, kind]); /* istanbul ignore next */ function handleDragBounds(x, y) { let xRes = x; let yRes = y; const xMax = winWidth - 288; const yMax = winHeight - 150; if (xRes < 0) { xRes = 0; } else if (xRes > xMax) { xRes = xMax; } if (yRes < 0) { yRes = 0; } else if (yRes > yMax) { yRes = yMax; } return { targetX: xRes, targetY: yRes }; } function handleDrag(movementX, movementY) { const overlay = overlayRef.current; if (!overlay) { return; } const { x, y } = overlay.getBoundingClientRect(); const { targetX, targetY } = handleDragBounds(x + movementX, y + movementY); overlay.style.transform = 'none'; overlay.style.position = 'fixed'; overlay.style.left = `${targetX}px`; overlay.style.top = `${targetY}px`; overlay.style.bottom = 'auto'; } const contentId = uuidv4(); useIsomorphicEffect(() => { if (overlayRef.current) { const currentStyle = overlayRef.current?.style; Object.keys(styledTune).forEach(key => { const value = styledTune[key]; currentStyle.setProperty(key, `${value}px`); }); } }, [styledTune, overlayRef]); return /*#__PURE__*/React__default.createElement("div", _extends({}, rest, { className: cx(blockClass, `${blockClass}--${kind}`, `${blockClass}__${theme}`, (isBeacon || isDraggable) && `${blockClass}--${coachmark.align}`, fixedIsVisible && `${blockClass}--is-visible`, a11yDragMode && `${blockClass}--is-dragmode`, className), ref: overlayRef, "aria-labelledby": contentId, tabIndex: -1 }, getDevtoolsProps(componentName)), isDraggable ? /*#__PURE__*/React__default.createElement(CoachmarkDragbar, { a11yKeyboardHandler: handleKeyPress, onBlur: () => setA11yDragMode(false), onDrag: handleDrag, theme: theme, onClose: onClose }) : /*#__PURE__*/React__default.createElement(CoachmarkHeader, { onClose: onClose }), /*#__PURE__*/React__default.createElement("div", { className: `${blockClass}__body`, ref: ref, id: contentId }, React__default.Children.map(children, child => { return /*#__PURE__*/React__default.cloneElement(child, { isVisible }); }))); }); function getWindowDimensions() { const { innerWidth: winWidth, innerHeight: winHeight } = window; return { winWidth, winHeight }; } const useWindowDimensions = () => { const [windowDimensions, setWindowDimensions] = useState(getWindowDimensions()); useEffect(() => { /* istanbul ignore next */ function handleResize() { setWindowDimensions(getWindowDimensions()); } window.addEventListener('resize', handleResize); return () => window.removeEventListener('resize', handleResize); }, []); return windowDimensions; }; // Return a placeholder if not released and not enabled by feature flag CoachmarkOverlay = pkg.checkComponentEnabled(CoachmarkOverlay, componentName); // The display name of the component, used by React. Note that displayName // is used in preference to relying on function.name. CoachmarkOverlay.displayName = componentName; // The types and DocGen commentary for the component props, // in alphabetical order (for consistency). // See https://www.npmjs.com/package/prop-types#usage. CoachmarkOverlay.propTypes = { /** * The CoachmarkOverlayElements child components. * Validation is handled in the parent Coachmark component. */ children: PropTypes.node.isRequired, /** * Optional class name for this component. */ className: PropTypes.string, /** * The visibility of CoachmarkOverlay is * managed in the parent Coachmark component. */ fixedIsVisible: PropTypes.bool.isRequired, /** * What kind or style of Coachmark to render. */ kind: PropTypes.oneOf(Object.values(COACHMARK_OVERLAY_KIND)), /** * Function to call when the Coachmark closes. */ onClose: PropTypes.func.isRequired, /** * Determines the theme of the component. */ theme: PropTypes.oneOf(['light', 'dark']) }; export { CoachmarkOverlay };