UNPKG

@carbon/ibm-products

Version:
275 lines (273 loc) 9.95 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. */ const require_runtime = require("../../_virtual/_rolldown/runtime.js"); const require_index = require("../../node_modules/classnames/index.js"); const require_settings = require("../../settings.js"); const require_useIsomorphicEffect = require("../../global/js/hooks/useIsomorphicEffect.js"); const require_devtools = require("../../global/js/utils/devtools.js"); require("./utils/enums.js"); const require_hooks = require("./utils/hooks.js"); const require_context = require("./utils/context.js"); const require_CoachmarkOverlay = require("./CoachmarkOverlay.js"); const require_throttle = require("../../global/js/utils/throttle.js"); let react = require("react"); react = require_runtime.__toESM(react); let prop_types = require("prop-types"); prop_types = require_runtime.__toESM(prop_types); let _carbon_react = require("@carbon/react"); let react_dom = require("react-dom"); //#region src/components/Coachmark/Coachmark.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__ */ require_runtime.__toESM(require_index.default); const blockClass = `${require_settings.pkg.prefix}--coachmark`; const overlayBlockClass = `${blockClass}-overlay`; const componentName = "Coachmark"; const defaults = { align: "bottom", onClose: () => {}, overlayKind: "tooltip", theme: "light", isOpenByDefault: false, closeIconDescription: "" }; /** * Coachmarks are used to call out specific functionality or concepts * within the UI that may not be intuitive but are important for the * user to gain understanding of the product's main value and discover new use cases. * @deprecated This component is deprecated. */ const Coachmark = (0, react.forwardRef)(({ align = defaults.align, autoAlign, children, className, onClose = defaults.onClose, overlayClassName, overlayKind = defaults.overlayKind, overlayRef, positionTune, portalTarget, target, theme = defaults.theme, isOpenByDefault = defaults.isOpenByDefault, closeIconDescription = defaults.closeIconDescription, ...rest }, ref) => { const isBeacon = overlayKind === "tooltip"; const isStacked = overlayKind === "stacked"; const [isOpen, setIsOpen] = (0, react.useState)(isStacked || isOpenByDefault); const [shouldResetPosition, setShouldResetPosition] = (0, react.useState)(false); const [targetRect, setTargetRect] = (0, react.useState)(); const [targetOffset, setTargetOffset] = (0, react.useState)({ x: 0, y: 0 }); const overlayBackupRef = (0, react.useRef)(void 0); const backupRef = (0, react.useRef)(void 0); const _coachmarkRef = ref || backupRef; const _overlayRef = overlayRef || overlayBackupRef; const portalNode = (0, react.useRef)(null); const popoverRef = (0, react.useRef)(null); let targetName; if (react.default.isValidElement(target) && typeof target.type !== "string") targetName = target.type; require_useIsomorphicEffect.useIsomorphicEffect(() => { portalNode.current = portalTarget ? document?.querySelector(portalTarget) ?? document?.querySelector("body") : document?.querySelector("body"); }, [portalTarget]); const closeOverlay = () => { setIsOpen(false); }; const handleClose = (0, react.useCallback)(() => { if (isStacked) onClose(); else { setIsOpen(false); onClose(); } }, [isStacked, onClose]); const escFunction = (0, react.useCallback)((event) => { if (event.key === "Escape") handleClose(); }, [handleClose]); (0, react.useEffect)(() => { document.addEventListener("keydown", escFunction, false); return () => { document.removeEventListener("keydown", escFunction, false); }; }, [escFunction]); const handleTargetClick = (e) => { setTargetRect(e.target.getBoundingClientRect()); setTargetOffset({ x: e.target.offsetLeft, y: e.target.offsetTop }); if (isBeacon) setIsOpen((prevIsOpen) => !prevIsOpen); else { setIsOpen(false); setShouldResetPosition(true); } }; const contextValue = { buttonProps: { "aria-expanded": isOpen, tabIndex: 0, onClick: handleTargetClick, onDoubleClick: handleTargetClick }, closeButtonProps: { onClick: handleClose }, targetRect, targetOffset, align, positionTune, isOpen, closeIconDescription }; const handleResize = require_throttle.throttle(() => { closeOverlay(); }, 2e3); (0, react.useEffect)(() => { if (shouldResetPosition) { setShouldResetPosition(false); setIsOpen(true); } }, [shouldResetPosition]); require_useIsomorphicEffect.useIsomorphicEffect(() => { const overlayPositionStyle = { top: `${(positionTune?.y ?? 0) - 16}px`, left: `${(positionTune?.x ?? 0) - 16}px` }; if (popoverRef.current && popoverRef.current.style && overlayPositionStyle) { const combinedStyle = { position: "absolute", ...overlayPositionStyle }; Object.assign(popoverRef.current.style, combinedStyle); } }, [popoverRef, positionTune]); (0, react.useEffect)(() => { return () => setIsOpen(false); }, []); require_hooks.useClickOutsideElement(_coachmarkRef, _overlayRef, overlayKind, closeOverlay); require_hooks.useWindowEvent("resize", handleResize); return /* @__PURE__ */ react.default.createElement(require_context.CoachmarkContext.Provider, { value: contextValue }, /* @__PURE__ */ react.default.createElement("div", { className: (0, import_classnames.default)(blockClass, `${blockClass}__${theme}`, className), ref: _coachmarkRef, ...rest, ...require_devtools.getDevtoolsProps(componentName) }, overlayKind !== "tooltip" ? /* @__PURE__ */ react.default.createElement(react.default.Fragment, null, targetName?.displayName === "CoachmarkBeacon" ? react.default.cloneElement(target, { buttonProps: contextValue.buttonProps }) : target, isOpen && portalNode?.current && (0, react_dom.createPortal)(/* @__PURE__ */ react.default.createElement(require_CoachmarkOverlay.CoachmarkOverlay, { ref: _overlayRef, fixedIsVisible: false, kind: overlayKind, onClose: handleClose, theme, className: (0, import_classnames.default)(overlayClassName, `${overlayBlockClass}--is-visible`) }, children), portalNode?.current)) : /* @__PURE__ */ react.default.createElement(_carbon_react.Popover, { highContrast: true, caret: true, ref: popoverRef, align, autoAlign, open: isOpen }, targetName?.displayName === "CoachmarkBeacon" ? react.default.cloneElement(target, { buttonProps: contextValue.buttonProps }) : target, /* @__PURE__ */ react.default.createElement(_carbon_react.PopoverContent, null, isOpen && /* @__PURE__ */ react.default.createElement(require_CoachmarkOverlay.CoachmarkOverlay, { ref: _overlayRef, fixedIsVisible: false, kind: overlayKind, onClose: handleClose, theme, className: (0, import_classnames.default)(overlayClassName, { [`${overlayBlockClass}--is-visible`]: isOpen }) }, children))))); }); const overlayRefType = typeof HTMLElement === "undefined" ? prop_types.default.object : prop_types.default.instanceOf(HTMLElement); /**@ts-ignore*/ Coachmark.deprecated = { level: "warn", details: `${componentName} is deprecated.` }; Coachmark.displayName = componentName; const deprecatedProps = { /** * **Deprecated** * Optional class name for the Coachmark Overlay component. */ overlayClassName: prop_types.default.string, /** * **Deprecated** * What kind or style of Coachmark to render. */ overlayKind: prop_types.default.oneOf([ "tooltip", "floating", "stacked" ]), overlayRef: prop_types.default.shape({ current: overlayRefType }), /** * **Deprecated** * By default, the Coachmark will be appended to the end of `document.body`. * The Coachmark will remain persistent as the user navigates the app until * the user closes the Coachmark. * * Alternatively, the app developer can tightly couple the Coachmark to a DOM * element or other component by specifying a CSS selector. The Coachmark will * remain visible as long as that element remains visible or mounted. When the * element is hidden or component is unmounted, the Coachmark will disappear. */ portalTarget: prop_types.default.string, /** * **Deprecated** * The optional button or beacon that the user will click to show the Coachmark. */ target: prop_types.default.node, /** * **Deprecated** * Determines the theme of the component. */ theme: prop_types.default.oneOf(["light", "dark"]) }; Coachmark.propTypes = { /** * Where to render the Coachmark relative to its target. * Applies only to Floating and Tooltip Coachmarks. * @see COACHMARK_ALIGNMENT */ align: prop_types.default.oneOf([ "bottom", "bottom-left", "bottom-right", "left", "left-top", "left-bottom", "right", "right-top", "right-bottom", "top", "top-left", "top-right" ]), /** * Auto aligns the coachmark based on screen boundaries * Applies only to Tooltip Coachmarks. */ autoAlign: prop_types.default.bool, /** * Coachmark should use a single CoachmarkOverlayElements component as a child. * @see CoachmarkOverlayElements */ children: prop_types.default.node.isRequired, /** * Optional class name for this component. */ className: prop_types.default.string, /** * Tooltip text and aria label for the Close button icon. */ closeIconDescription: prop_types.default.string, /** * Determines if the coachmark is open by default. * Does nothing if `overlayKind=stacked`. */ isOpenByDefault: prop_types.default.bool, /** * Function to call when the Coachmark closes. */ onClose: prop_types.default.func, /** * Fine tune the position of the target in pixels. Applies only to Beacons. */ positionTune: prop_types.default.shape({ x: prop_types.default.number, y: prop_types.default.number }), ...deprecatedProps }; //#endregion exports.Coachmark = Coachmark;