@carbon/ibm-products
Version:
Carbon for IBM Products
210 lines (208 loc) • 6.74 kB
JavaScript
/**
* Copyright IBM Corp. 2020, 2026
*
* 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 { __toESM } from "../../_virtual/_rolldown/runtime.js";
import { require_classnames } from "../../node_modules/classnames/index.js";
import { pkg } from "../../settings.js";
import { useIsomorphicEffect } from "../../global/js/hooks/useIsomorphicEffect.js";
import { getDevtoolsProps } from "../../global/js/utils/devtools.js";
import uuidv4 from "../../global/js/utils/uuidv4.js";
import { COACHMARK_OVERLAY_KIND } from "./utils/enums.js";
import { useCoachmark } from "./utils/context.js";
import { CoachmarkDragbar } from "./CoachmarkDragbar.js";
import { CoachmarkHeader } from "./CoachmarkHeader.js";
import { getOffsetTune } from "./utils/constants.js";
import React, { forwardRef, useEffect, useMemo, useRef, useState } from "react";
import PropTypes from "prop-types";
//#region src/components/Coachmark/CoachmarkOverlay.tsx
/**
* Copyright IBM Corp. 2023, 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 import_classnames = /* @__PURE__ */ __toESM(require_classnames());
const blockClass = `${pkg.prefix}--coachmark-overlay`;
const componentName = "CoachmarkOverlay";
const defaults = {
kind: "floating",
theme: "light"
};
/**
* DO NOT USE. This component is for the exclusive use
* of other Onboarding components.
* @deprecated This component is deprecated.
*/
const CoachmarkOverlay = forwardRef((props, ref) => {
const { children, onClose, fixedIsVisible, className, kind = defaults.kind, theme = defaults.theme, ...rest } = props;
const { winHeight, winWidth } = useWindowDimensions();
const [a11yDragMode, setA11yDragMode] = useState(false);
const overlayRef = useRef(null);
const coachmark = useCoachmark();
const isBeacon = kind === "tooltip";
const isDraggable = 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;
default: 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.createElement("div", {
...rest,
className: (0, import_classnames.default)(blockClass, `${blockClass}--${kind}`, `${blockClass}__${theme}`, (isBeacon || isDraggable) && coachmark?.align && `${blockClass}--${coachmark.align}`, fixedIsVisible && `${blockClass}--is-visible`, a11yDragMode && `${blockClass}--is-dragmode`, className),
ref: overlayRef,
"aria-labelledby": contentId,
tabIndex: -1,
...getDevtoolsProps(componentName)
}, isDraggable ? /* @__PURE__ */ React.createElement(CoachmarkDragbar, {
a11yKeyboardHandler: handleKeyPress,
onBlur: () => setA11yDragMode(false),
onDrag: handleDrag,
theme,
onClose
}) : /* @__PURE__ */ React.createElement(CoachmarkHeader, { onClose }), /* @__PURE__ */ React.createElement("div", {
className: `${blockClass}__body`,
ref,
id: contentId
}, React.Children.map(children, (child) => {
return React.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;
};
/**@ts-ignore*/
CoachmarkOverlay.deprecated = {
level: "warn",
details: `${componentName} is deprecated.`
};
CoachmarkOverlay.displayName = componentName;
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"])
};
//#endregion
export { CoachmarkOverlay };