UNPKG

@carbon/ibm-products

Version:

Carbon for IBM Products

345 lines (333 loc) 12.8 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 { Tooltip, Form, ComposedModal, ModalHeader, ModalBody, ModalFooter, Button } from '@carbon/react'; import React__default, { useState, useRef, useEffect, createContext } from 'react'; import { overflowAriaLabel_required_if_breadcrumbs_exist, SimpleHeader } from '../SimpleHeader/SimpleHeader.js'; import { ActionSet } from '../ActionSet/ActionSet.js'; import PropTypes from '../../_virtual/index.js'; import cx from 'classnames'; import { getDevtoolsProps } from '../../global/js/utils/devtools.js'; import { getNumberOfHiddenSteps } from '../../global/js/utils/getNumberOfHiddenSteps.js'; import { lastIndexInArray } from '../../global/js/utils/lastIndexInArray.js'; import { pkg } from '../../settings.js'; import { usePreviousValue } from '../../global/js/hooks/usePreviousValue.js'; import { useCreateComponentFocus } from '../../global/js/hooks/useCreateComponentFocus.js'; import { useValidCreateStepCount } from '../../global/js/hooks/useValidCreateStepCount.js'; import { useCreateComponentStepChange } from '../../global/js/hooks/useCreateComponentStepChange.js'; import { CreateInfluencer } from '../CreateInfluencer/CreateInfluencer.js'; const blockClass = `${pkg.prefix}--create-full-page`; const componentName = 'CreateFullPage'; // This is a general context for the steps container // containing information about the state of the container // and providing some callback methods for steps to use const StepsContext = /*#__PURE__*/createContext(null); // This is a context supplied separately to each step in the container // to let it know what number it is in the sequence of steps const StepNumberContext = /*#__PURE__*/createContext(-1); /** * Use with creations that must be completed in order for a service to be usable. * * ### Grid * * The `CreateFullPage` component utilizes Carbons' grid system in the inner content of the main section inside of the component. You can read more guidance on the Carbon's grid system [here](https://www.carbondesignsystem.com/guidelines/2x-grid/overview). You can include `<Row>` and `<Column>` components inside of each `CreateFullPageStep` component to get the desired affect. */ let CreateFullPage = /*#__PURE__*/React__default.forwardRef((_ref, ref) => { let { breadcrumbsOverflowAriaLabel, breadcrumbs, backButtonText, cancelButtonText, children, className, initialStep, maxVisibleBreadcrumbs, modalDangerButtonText, modalDescription, modalSecondaryButtonText, modalTitle, nextButtonText, onClickInfluencerStep, onClose, onRequestSubmit, firstFocusElement, submitButtonText, noTrailingSlash, title, secondaryTitle, breadcrumbOverflowTooltipAlign = 'right', ...rest } = _ref; const [createFullPageActions, setCreateFullPageActions] = useState([]); const [shouldViewAll, setShouldViewAll] = useState(false); const [currentStep, setCurrentStep] = useState(1); const [isSubmitting, setIsSubmitting] = useState(false); const [modalIsOpen, setModalIsOpen] = useState(false); const previousState = usePreviousValue({ currentStep }); const [isDisabled, setIsDisabled] = useState(false); const [onPrevious, setOnPrevious] = useState(); const [onNext, setOnNext] = useState(); const [onMount, setOnMount] = useState(); const [stepData, setStepData] = useState([]); const [firstIncludedStep, setFirstIncludedStep] = useState(1); const [lastIncludedStep, setLastIncludedStep] = useState(); const invalidInitialStepWarned = useRef(false); useEffect(() => { const firstItem = stepData.findIndex(item => item?.shouldIncludeStep) + 1; const lastItem = lastIndexInArray(stepData, 'shouldIncludeStep', true); if (firstItem !== firstIncludedStep) { setFirstIncludedStep(firstItem); } if (lastItem !== lastIncludedStep) { setLastIncludedStep(lastItem); } /**@ts-ignore */ if (initialStep) { const numberOfHiddenSteps = getNumberOfHiddenSteps(stepData, initialStep); setCurrentStep(Number(initialStep + numberOfHiddenSteps)); } }, [stepData, firstIncludedStep, lastIncludedStep, initialStep, modalIsOpen]); useEffect(() => { if (!invalidInitialStepWarned?.current) { checkForValidInitialStep(); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [initialStep, stepData]); useCreateComponentFocus({ previousState, currentStep, blockClass, onMount, firstFocusElement }); useValidCreateStepCount(stepData.length, componentName); useCreateComponentStepChange({ firstIncludedStep, lastIncludedStep, stepData, onPrevious, onNext, isSubmitDisabled: isDisabled, setCurrentStep, setIsSubmitting, onClose, onRequestSubmit, componentName, currentStep, backButtonText, cancelButtonText, submitButtonText, nextButtonText, isSubmitting, componentBlockClass: blockClass, setCreateComponentActions: setCreateFullPageActions, setModalIsOpen }); const checkForValidInitialStep = () => { // An invalid initialStep value was provided, we'll default to rendering the first step or last step if (initialStep && stepData?.length && Number(initialStep) > Number(stepData?.length) || Number(initialStep) <= 0) { invalidInitialStepWarned.current = true; setCurrentStep(1); console.warn(`${componentName}: An invalid \`initialStep\` prop was supplied. The \`initialStep\` prop should be a number that is greater than 0 or less than or equal to the number of steps your ${componentName} has.`); } }; // currently, we are not supporting the use of 'view all' toggle state /* istanbul ignore next */ return /*#__PURE__*/React__default.createElement("div", _extends({}, rest, { ref: ref, className: cx(blockClass, className) }, getDevtoolsProps(componentName)), (title || breadcrumbs) && /*#__PURE__*/React__default.createElement(SimpleHeader, { title: title, breadcrumbs: breadcrumbs, noTrailingSlash: noTrailingSlash, overflowAriaLabel: breadcrumbsOverflowAriaLabel, maxVisible: maxVisibleBreadcrumbs, className: `${blockClass}__header`, overflowTooltipAlign: breadcrumbOverflowTooltipAlign }), /*#__PURE__*/React__default.createElement("div", { className: `${blockClass}__influencer-and-body-container` }, /*#__PURE__*/React__default.createElement("div", { className: `${blockClass}__influencer` }, /*#__PURE__*/React__default.createElement(CreateInfluencer, { stepData: stepData, currentStep: currentStep, title: secondaryTitle, onClickStep: onClickInfluencerStep })), /*#__PURE__*/React__default.createElement("div", { className: `${blockClass}__body` }, /*#__PURE__*/React__default.createElement("div", { className: `${blockClass}__main` }, /*#__PURE__*/React__default.createElement("div", { className: `${blockClass}__content` }, /*#__PURE__*/React__default.createElement(Form, { className: `${blockClass}__form`, "aria-label": title }, /*#__PURE__*/React__default.createElement(StepsContext.Provider, { value: { currentStep, setIsDisabled, setOnPrevious: fn => setOnPrevious(() => fn), setOnNext: fn => setOnNext(() => fn), setOnMount: fn => setOnMount(() => fn), setStepData, stepData } }, React__default.Children.toArray(children).filter(Boolean).map((child, index) => /*#__PURE__*/React__default.createElement(StepNumberContext.Provider, { value: index + 1, key: index }, child))))), /*#__PURE__*/React__default.createElement(ActionSet, { className: `${blockClass}__buttons`, actions: createFullPageActions, buttonSize: "2xl", size: "2xl" }))), /*#__PURE__*/React__default.createElement(ComposedModal, { className: `${blockClass}__modal`, size: "sm", open: modalIsOpen, "aria-label": modalTitle, onClose: () => { setModalIsOpen(false); } }, /*#__PURE__*/React__default.createElement(ModalHeader, { title: modalTitle }), /*#__PURE__*/React__default.createElement(ModalBody, null, /*#__PURE__*/React__default.createElement("p", null, modalDescription)), /*#__PURE__*/React__default.createElement(ModalFooter, null, /*#__PURE__*/React__default.createElement(Button, { type: "button", kind: "secondary", onClick: () => { setModalIsOpen(!modalIsOpen); }, "data-modal-primary-focus": true }, modalSecondaryButtonText), /*#__PURE__*/React__default.createElement(Button, { type: "button", kind: "danger", onClick: onClose }, modalDangerButtonText))))); }); // Return a placeholder if not released and not enabled by feature flag. CreateFullPage = pkg.checkComponentEnabled(CreateFullPage, componentName); // The display name of the component, used by React. Note that displayName // is used in preference to relying on function.name. CreateFullPage.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. CreateFullPage.propTypes = { /** * The back button text */ backButtonText: PropTypes.string.isRequired, /** * align breadcrumb overflow tooltip */ breadcrumbOverflowTooltipAlign: Tooltip.propTypes.align, /** The header breadcrumbs */ /**@ts-ignore */ breadcrumbs: PropTypes.arrayOf(PropTypes.shape({ /** breadcrumb item key */ key: PropTypes.string.isRequired, /** breadcrumb item label */ label: PropTypes.string.isRequired, /** breadcrumb item link */ href: PropTypes.string, /** breadcrumb item title tooltip */ title: PropTypes.string, /** Provide if this breadcrumb item represents the current page */ isCurrentPage: PropTypes.bool })), /** * Label for open/close overflow button used for breadcrumb items that do not fit */ breadcrumbsOverflowAriaLabel: overflowAriaLabel_required_if_breadcrumbs_exist, /** * The cancel button text */ cancelButtonText: PropTypes.string.isRequired, /** * The main content of the full page */ children: PropTypes.node, /** * Provide an optional class to be applied to the containing node. */ className: PropTypes.string, /** * Specifies elements to focus on first on render. */ firstFocusElement: PropTypes.string, /** * This can be used to open the component to a step other than the first step. * For example, a create flow was previously in progress, data was saved, and * is now being completed. */ initialStep: PropTypes.number, /** Maximum visible breadcrumb-items before overflow is used (values less than 1 are treated as 1) */ maxVisibleBreadcrumbs: PropTypes.number, /** * The primary 'danger' button text in the modal */ modalDangerButtonText: PropTypes.string.isRequired, /** * The description located below the title in the modal */ modalDescription: PropTypes.string, /** * The secondary button text in the modal */ modalSecondaryButtonText: PropTypes.string.isRequired, /** * The title located in the header of the modal */ modalTitle: PropTypes.string.isRequired, /** * The next button text */ nextButtonText: PropTypes.string.isRequired, /** * A prop to omit the trailing slash for the breadcrumbs */ noTrailingSlash: PropTypes.bool, /** * onChange event for Progress Indicator in the Influencer */ onClickInfluencerStep: PropTypes.func, /** * An optional handler that is called when the user closes the full page (by * clicking the secondary button, located in the modal, which triggers after * clicking the ghost button in the modal */ onClose: PropTypes.func, /** * Specify a handler for submitting the multi step full page (final step). * This function can _optionally_ return a promise that is either resolved or rejected and the CreateFullPage will handle the submitting state of the create button. */ onRequestSubmit: PropTypes.func.isRequired, /** * A secondary title of the full page, displayed in the influencer area */ secondaryTitle: PropTypes.string, /** * @ignore * The aria label to be used for the UI Shell SideNav Carbon component */ sideNavAriaLabel: PropTypes.string, /** * The submit button text */ submitButtonText: PropTypes.string.isRequired, /** * The main title of the full page, displayed in the header area */ title: PropTypes.string }; export { CreateFullPage, StepNumberContext, StepsContext };