UNPKG

@carbon/ibm-products

Version:

Carbon for IBM Products

223 lines (213 loc) 8.76 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. */ 'use strict'; var _rollupPluginBabelHelpers = require('../../_virtual/_rollupPluginBabelHelpers.js'); var react = require('@carbon/react'); var React = require('react'); var Carousel = require('../Carousel/Carousel.js'); require('../Carousel/CarouselItem.js'); var index = require('../../_virtual/index.js'); var clamp = require('../../global/js/utils/clamp.js'); var cx = require('classnames'); var devtools = require('../../global/js/utils/devtools.js'); var pconsole = require('../../global/js/utils/pconsole.js'); var settings = require('../../settings.js'); require('../Coachmark/Coachmark.js'); var context = require('../Coachmark/utils/context.js'); // The block part of our conventional BEM class names (blockClass__E--M). const blockClass = `${settings.pkg.prefix}--coachmark-overlay-elements`; const componentName = 'CoachmarkOverlayElements'; // NOTE: the component SCSS is not imported here: it is rolled up separately. // Default values can be included here and then assigned to the prop params, // e.g. prop = defaults.prop, // This gathers default values together neatly and ensures non-primitive // values are initialized early to avoid react making unnecessary re-renders. // Note that default values are not required for props that are 'required', // nor for props where the component can apply undefined values reasonably. // Default values should be provided when the component needs to make a choice // or assumption when a prop is not supplied. // Default values for props const defaults = { isVisible: false, nextButtonText: 'Next', previousButtonLabel: 'Back', closeButtonLabel: 'Got it', onNext: undefined, onBack: undefined, currentStep: 0 }; /** * Composable container to allow for the displaying of CoachmarkOverlayElement * components in a carousel fashion. */ exports.CoachmarkOverlayElements = /*#__PURE__*/React.forwardRef((_ref, ref) => { let { className, children, isVisible = defaults.isVisible, renderMedia, currentStep = defaults.currentStep, nextButtonText = defaults.nextButtonText, previousButtonLabel = defaults.previousButtonLabel, closeButtonLabel = defaults.closeButtonLabel, onNext = defaults.onNext, onBack = defaults.onBack, // Collect any other property values passed in. ...rest } = _ref; const buttonFocusRef = React.useRef(undefined); const scrollRef = React.useRef(undefined); const [scrollPosition, setScrollPosition] = React.useState(0); const [currentProgStep, _setCurrentProgStep] = React.useState(currentStep); const coachmark = context.useCoachmark(); const setCurrentProgStep = value => { if (currentProgStep > 0 && value === 0 && buttonFocusRef.current) { setTimeout(() => { buttonFocusRef.current?.focus(); }, 1000); } _setCurrentProgStep(value); }; const numProgSteps = React.Children.count(children); const progStepFloor = 0; const progStepCeil = numProgSteps - 1; const renderMediaContent = React.useMemo(() => renderMedia?.({ playStep: currentProgStep }), [currentProgStep, renderMedia]); React.useEffect(() => { // When current step is set by props // scroll to the appropriate view on the carrousel const targetStep = clamp.clamp(currentStep, progStepFloor, progStepCeil); scrollRef?.current?.scrollToView?.(targetStep); // Avoid circular call to this hook // eslint-disable-next-line react-hooks/exhaustive-deps }, [currentStep]); React.useEffect(() => { // On mount, one of the two primary buttons ("next" or "close") // will be rendered and must have focus. (a11y) if (buttonFocusRef.current) { buttonFocusRef.current.focus(); } }, []); React.useEffect(() => { setTimeout(() => { if (buttonFocusRef.current && isVisible) { buttonFocusRef.current.focus(); } }, 100); }, [isVisible]); if (!coachmark) { return pconsole.default.warn(`The ${componentName} is a composable container element which should be used only within the scope of a Coachmark or a CoachmarkFixed component.`); } return /*#__PURE__*/React.createElement("section", _rollupPluginBabelHelpers.extends({}, rest, { className: cx(blockClass, // Apply the block class to the main HTML element className, // Apply any supplied class names to the main HTML element. // example: `${blockClass}__template-string-class-${kind}-n-${size}`, { // switched classes dependant on props or state // example: [`${blockClass}__here-if-small`]: size === 'sm', }), ref: ref }, devtools.getDevtoolsProps(componentName)), renderMedia && /*#__PURE__*/React.createElement("div", { className: `${blockClass}__element-stepped-media` }, renderMediaContent), numProgSteps === 1 ? /*#__PURE__*/React.createElement(React.Fragment, null, children, closeButtonLabel && /*#__PURE__*/React.createElement("div", { className: cx(`${blockClass}__footer`, 'coachmark-carousel-controls') }, /*#__PURE__*/React.createElement(react.Button, _rollupPluginBabelHelpers.extends({ size: "sm" }, coachmark.closeButtonProps, { ref: buttonFocusRef }), closeButtonLabel))) : /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Carousel.Carousel, { ref: scrollRef, onScroll: scrollPercent => { setScrollPosition(scrollPercent); } }, children), /*#__PURE__*/React.createElement("div", { className: cx(`${blockClass}__footer`) }, /*#__PURE__*/React.createElement("div", { className: `${blockClass}--controls-progress` }, `${currentProgStep + 1} / ${numProgSteps}`), scrollPosition > 0 && /*#__PURE__*/React.createElement(react.Button, { size: "sm", kind: "ghost", title: previousButtonLabel, disabled: scrollPosition === 0, onClick: () => { const targetStep = clamp.clamp(currentProgStep - 1, progStepFloor, progStepCeil); scrollRef?.current?.scrollToView?.(targetStep); setCurrentProgStep(targetStep); onBack?.(); } }, previousButtonLabel), currentProgStep < progStepCeil ? /*#__PURE__*/React.createElement(react.Button, { size: "sm", ref: buttonFocusRef, title: nextButtonText, disabled: scrollPosition === 1, onClick: () => { const targetStep = clamp.clamp(currentProgStep + 1, progStepFloor, progStepCeil); scrollRef?.current?.scrollToView?.(targetStep); setCurrentProgStep(targetStep); onNext?.(); } }, nextButtonText) : closeButtonLabel && /*#__PURE__*/React.createElement(react.Button, _rollupPluginBabelHelpers.extends({ size: "sm", ref: buttonFocusRef }, coachmark.closeButtonProps), closeButtonLabel)))); }); // Return a placeholder if not released and not enabled by feature flag exports.CoachmarkOverlayElements = settings.pkg.checkComponentEnabled(exports.CoachmarkOverlayElements, componentName); // The display name of the component, used by React. Note that displayName // is used in preference to relying on function.name. exports.CoachmarkOverlayElements.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. exports.CoachmarkOverlayElements.propTypes = { // TODO: UPDATE COMMENT HERE - UPDATE MDX TO HAVE DIRECTION TO USE ONLY OVERLAY ELEMENTS>...CoachmarkOverlayElements will accept only one or more CoachmarkOverlayElement as child components. /** * CoachmarkOverlayElements should be used with one or many CoachmarkOverlayElement components as children. * @see CoachmarkOverlayElement */ children: index.default.node.isRequired, /** * Optional class name for this component. */ className: index.default.string, /** * The label for the Close button. */ closeButtonLabel: index.default.string, /** * Current step of the coachmarks */ currentStep: index.default.number, /** * The visibility of CoachmarkOverlayElements is * managed in the parent component. */ isVisible: index.default.bool, /** * The label for the Next button. */ nextButtonText: index.default.string, /** * Optional callback called when clicking on the Previous button. */ onBack: index.default.func, /** * Optional callback called when clicking on the Next button. */ onNext: index.default.func, /** * The label for the Previous button. */ previousButtonLabel: index.default.string, /** * Optional prop to render any media like images or animated media. */ renderMedia: index.default.func };