UNPKG

@carbon/ibm-products

Version:

Carbon for IBM Products

210 lines (201 loc) 7.61 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, useRef, useState, useEffect } from 'react'; import pconsole from '../../global/js/utils/pconsole.js'; import PropTypes from '../../_virtual/index.js'; import cx from 'classnames'; import { Idea } from '@carbon/react/icons'; import { Button, Tooltip } from '@carbon/react'; import { getDevtoolsProps } from '../../global/js/utils/devtools.js'; import { pkg } from '../../settings.js'; import { createPortal } from 'react-dom'; import { CoachmarkHeader } from '../Coachmark/CoachmarkHeader.js'; import { useIsomorphicEffect } from '../../global/js/hooks/useIsomorphicEffect.js'; // Carbon and package components we use. /* TODO: @import(s) of carbon components and other package components. */ // The block part of our conventional BEM class names (blockClass__E--M). 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. */ let CoachmarkStackHome = /*#__PURE__*/forwardRef((_ref, ref) => { let { 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.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) { let ref = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null; const isOverflowing = overflowStates[index] ?? false; return /*#__PURE__*/React__default.createElement("li", { key: index, ref: node => { itemRefHandler(index, node); } }, /*#__PURE__*/React__default.createElement(Button, { kind: "ghost", size: "sm", onClick: () => { setLinkFocusIndex(index); onClickNavItem(index + 1); }, ref: ref }, isOverflowing ? /*#__PURE__*/React__default.createElement(Tooltip, { highContrast: false, label: label, align: tooltipAlign, className: `${blockClass}__navLinkLabels-tooltip` }, /*#__PURE__*/React__default.createElement("span", { className: `${blockClass}__navLinkLabels-text` }, label)) : label)); } return portalNode?.current ? /*#__PURE__*/createPortal(/*#__PURE__*/React__default.createElement("div", _extends({}, rest, { className: cx(blockClass, className), ref: ref }, getDevtoolsProps(componentName)), /*#__PURE__*/React__default.createElement(CoachmarkHeader, { onClose: () => { setLinkFocusIndex(0); onClose(); } }), /*#__PURE__*/React__default.createElement("div", { className: `${overlayClass}__body` }, /*#__PURE__*/React__default.createElement("div", { className: `${overlayClass}-element` }, !renderMedia && /*#__PURE__*/React__default.createElement(Idea, { size: 20, className: `${blockClass}__icon-idea` }), renderMedia && /*#__PURE__*/React__default.createElement("div", { className: `${blockClass}__element-stepped-media` }, renderMedia({ playStep: 0 })), /*#__PURE__*/React__default.createElement("div", { className: `${overlayClass}-element__content` }, title && /*#__PURE__*/React__default.createElement("h2", { className: `${overlayClass}-element__title` }, title), description && /*#__PURE__*/React__default.createElement("p", { className: `${overlayClass}-element__body` }, description)), /*#__PURE__*/React__default.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__default.createElement("div", { className: `${overlayClass}__footer` }, /*#__PURE__*/React__default.createElement(Button, { size: "sm", onClick: () => { setLinkFocusIndex(0); onClose(); } }, closeButtonLabel))))), portalNode?.current) : null; }); // Return a placeholder if not released and not enabled by feature flag CoachmarkStackHome = pkg.checkComponentEnabled(CoachmarkStackHome, componentName); // The display name of the component, used by React. Note that displayName // is used in preference to relying on function.name. CoachmarkStackHome.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. 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']) }; export { CoachmarkStackHome };