@carbon/ibm-products
Version:
Carbon for IBM Products
161 lines (159 loc) • 6.71 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 pconsole_default from "../../global/js/utils/pconsole.js";
import { pkg } from "../../settings.js";
import { useIsomorphicEffect } from "../../global/js/hooks/useIsomorphicEffect.js";
import { getDevtoolsProps } from "../../global/js/utils/devtools.js";
import { CoachmarkHeader } from "../Coachmark/CoachmarkHeader.js";
import React, { forwardRef, useEffect, useRef, useState } from "react";
import PropTypes from "prop-types";
import { Button, Tooltip } from "@carbon/react";
import { createPortal } from "react-dom";
import { Idea } from "@carbon/react/icons";
//#region src/components/CoachmarkStack/CoachmarkStackHome.tsx
/**
* Copyright IBM Corp. 2023, 2024
*
* 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-stacked-home`;
const overlayClass = `${pkg.prefix}--coachmark-overlay`;
const componentName = "CoachmarkStackHome";
/**
* DO NOT USE. This component is for the exclusive use
* of other Onboarding components.
*/
const CoachmarkStackHome = forwardRef(({ className, description, isOpen, renderMedia, navLinkLabels, onClickNavItem, onClose, portalTarget, closeButtonLabel, title, tooltipAlign, ...rest }, ref) => {
const buttonFocusRef = useRef(null);
const [linkFocusIndex, setLinkFocusIndex] = useState(0);
const navItemRefs = useRef([]);
const [overflowStates, setOverflowStates] = useState([]);
useEffect(() => {
setTimeout(() => {
if (isOpen && buttonFocusRef.current) buttonFocusRef.current.focus();
}, 100);
}, [isOpen]);
const portalNode = useRef(null);
useIsomorphicEffect(() => {
portalNode.current = portalTarget ? document?.querySelector(portalTarget) ?? document?.querySelector("body") : document?.querySelector("body");
}, [portalTarget]);
if (!navLinkLabels) return pconsole_default.warn(`${componentName} is an Onboarding internal component and is not intended for general use.`);
const itemRefHandler = (index, node) => {
if (node && navItemRefs.current[index] !== node) {
const isOverflowing = node.scrollWidth > node.clientWidth;
navItemRefs.current[index] = node;
setOverflowStates((prev) => {
const newState = [...prev];
newState[index] = isOverflowing;
return newState;
});
}
};
function renderNavLink(index, label, ref = null) {
const isOverflowing = overflowStates[index] ?? false;
return /* @__PURE__ */ React.createElement("li", {
key: index,
ref: (node) => {
itemRefHandler(index, node);
}
}, /* @__PURE__ */ React.createElement(Button, {
kind: "ghost",
size: "sm",
onClick: () => {
setLinkFocusIndex(index);
onClickNavItem(index + 1);
},
ref
}, isOverflowing ? /* @__PURE__ */ React.createElement(Tooltip, {
highContrast: false,
label,
align: tooltipAlign,
className: `${blockClass}__navLinkLabels-tooltip`
}, /* @__PURE__ */ React.createElement("span", { className: `${blockClass}__navLinkLabels-text` }, label)) : label));
}
return portalNode?.current ? createPortal(/* @__PURE__ */ React.createElement("div", {
...rest,
className: (0, import_classnames.default)(blockClass, className),
ref,
...getDevtoolsProps(componentName)
}, /* @__PURE__ */ React.createElement(CoachmarkHeader, { onClose: () => {
setLinkFocusIndex(0);
onClose();
} }), /* @__PURE__ */ React.createElement("div", { className: `${overlayClass}__body` }, /* @__PURE__ */ React.createElement("div", { className: `${overlayClass}-element` }, !renderMedia && /* @__PURE__ */ React.createElement(Idea, {
size: 20,
className: `${blockClass}__icon-idea`
}), renderMedia && /* @__PURE__ */ React.createElement("div", { className: `${blockClass}__element-stepped-media` }, renderMedia({ playStep: 0 })), /* @__PURE__ */ React.createElement("div", { className: `${overlayClass}-element__content` }, title && /* @__PURE__ */ React.createElement("h2", { className: `${overlayClass}-element__title` }, title), description && /* @__PURE__ */ React.createElement("p", { className: `${overlayClass}-element__body` }, description)), /* @__PURE__ */ React.createElement("ul", { className: `${blockClass}__nav-links` }, navLinkLabels.map((label, index) => {
if (index === linkFocusIndex) return renderNavLink(index, label, buttonFocusRef);
return renderNavLink(index, label);
})), closeButtonLabel && /* @__PURE__ */ React.createElement("div", { className: `${overlayClass}__footer` }, /* @__PURE__ */ React.createElement(Button, {
size: "sm",
onClick: () => {
setLinkFocusIndex(0);
onClose();
}
}, closeButtonLabel))))), portalNode?.current) : null;
});
CoachmarkStackHome.displayName = componentName;
CoachmarkStackHome.propTypes = {
/**
* Optional class name for this component.
*/
className: PropTypes.string,
/**
* The label for the button that will close the stack
*/
closeButtonLabel: PropTypes.string,
/**
* The description of the Coachmark.
*/
description: PropTypes.node.isRequired,
/**
* If the stack home is open.
*/
isOpen: PropTypes.bool.isRequired,
/**
* The labels used to link to the stackable Coachmarks.
*/
navLinkLabels: PropTypes.arrayOf(PropTypes.string.isRequired).isRequired,
/**
* For internal use only by CoachmarkStack and CoachmarkStackHome.
*/
onClickNavItem: PropTypes.func.isRequired,
/**
* Function to call when the stack closes.
*/
onClose: PropTypes.func.isRequired,
/**
* By default, the CoachmarkStackHome will be appended to the end of `document.body`.
* The CoachmarkStackHome will remain persistent as the user navigates the app until
* the user closes the CoachmarkStackHome.
*
* Alternatively, the app developer can tightly couple the CoachmarkStackHome to a DOM
* element or other component by specifying a CSS selector. The CoachmarkStackHome will
* remain visible as long as that element remains visible or mounted. When the
* element is hidden or component is unmounted, the CoachmarkStackHome will disappear.
*/
portalTarget: PropTypes.string,
/**
* Optional prop to render any media like images or animated media.
*/
renderMedia: PropTypes.func,
/**
* The title of the Coachmark.
*/
title: PropTypes.string.isRequired,
/**
* Label's tooltip position
*/
tooltipAlign: PropTypes.oneOf(["top", "bottom"])
};
//#endregion
export { CoachmarkStackHome };