UNPKG

@carbon/ibm-products

Version:
161 lines (159 loc) 6.71 kB
/** * 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 };