@carbon/ibm-products
Version:
Carbon for IBM Products
240 lines (230 loc) • 7.55 kB
JavaScript
/**
* 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.
*/
;
var _rollupPluginBabelHelpers = require('../../_virtual/_rollupPluginBabelHelpers.js');
var React = require('react');
var uuidv4 = require('../../global/js/utils/uuidv4.js');
var index = require('../../_virtual/index.js');
var cx = require('classnames');
var devtools = require('../../global/js/utils/devtools.js');
var settings = require('../../settings.js');
var CoachmarkDragbar = require('./CoachmarkDragbar.js');
var CoachmarkHeader = require('./CoachmarkHeader.js');
var constants = require('./utils/constants.js');
var context = require('./utils/context.js');
var enums = require('./utils/enums.js');
var useIsomorphicEffect = require('../../global/js/hooks/useIsomorphicEffect.js');
// The block part of our conventional BEM class names (blockClass__E--M).
const blockClass = `${settings.pkg.prefix}--coachmark-overlay`;
const componentName = 'CoachmarkOverlay';
// NOTE: the component SCSS is not imported here: it is rolled up separately.
const defaults = {
kind: enums.COACHMARK_OVERLAY_KIND.FLOATING,
theme: 'light'
};
/**
* DO NOT USE. This component is for the exclusive use
* of other Onboarding components.
*/
exports.CoachmarkOverlay = /*#__PURE__*/React.forwardRef((_ref, ref) => {
let {
children,
onClose,
fixedIsVisible,
className,
kind = defaults.kind,
theme = defaults.theme,
...rest
} = _ref;
const {
winHeight,
winWidth
} = useWindowDimensions();
const [a11yDragMode, setA11yDragMode] = React.useState(false);
const overlayRef = React.useRef(null);
const coachmark = context.useCoachmark();
const isBeacon = kind === enums.COACHMARK_OVERLAY_KIND.TOOLTIP;
const isDraggable = kind === enums.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 = React.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 = constants.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.default();
useIsomorphicEffect.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.createElement("div", _rollupPluginBabelHelpers.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
}, devtools.getDevtoolsProps(componentName)), isDraggable ? /*#__PURE__*/React.createElement(CoachmarkDragbar.CoachmarkDragbar, {
a11yKeyboardHandler: handleKeyPress,
onBlur: () => setA11yDragMode(false),
onDrag: handleDrag,
theme: theme,
onClose: onClose
}) : /*#__PURE__*/React.createElement(CoachmarkHeader.CoachmarkHeader, {
onClose: onClose
}), /*#__PURE__*/React.createElement("div", {
className: `${blockClass}__body`,
ref: ref,
id: contentId
}, React.Children.map(children, child => {
return /*#__PURE__*/React.cloneElement(child, {
isVisible
});
})));
});
function getWindowDimensions() {
const {
innerWidth: winWidth,
innerHeight: winHeight
} = window;
return {
winWidth,
winHeight
};
}
const useWindowDimensions = () => {
const [windowDimensions, setWindowDimensions] = React.useState(getWindowDimensions());
React.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
exports.CoachmarkOverlay = settings.pkg.checkComponentEnabled(exports.CoachmarkOverlay, componentName);
// The display name of the component, used by React. Note that displayName
// is used in preference to relying on function.name.
exports.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.
exports.CoachmarkOverlay.propTypes = {
/**
* The CoachmarkOverlayElements child components.
* Validation is handled in the parent Coachmark component.
*/
children: index.default.node.isRequired,
/**
* Optional class name for this component.
*/
className: index.default.string,
/**
* The visibility of CoachmarkOverlay is
* managed in the parent Coachmark component.
*/
fixedIsVisible: index.default.bool.isRequired,
/**
* What kind or style of Coachmark to render.
*/
kind: index.default.oneOf(Object.values(enums.COACHMARK_OVERLAY_KIND)),
/**
* Function to call when the Coachmark closes.
*/
onClose: index.default.func.isRequired,
/**
* Determines the theme of the component.
*/
theme: index.default.oneOf(['light', 'dark'])
};