From 76c063dbfe9047bea9b6d3ce9b87f604bdfeb9e2 Mon Sep 17 00:00:00 2001 From: paola-p <> Date: Tue, 5 Dec 2023 19:28:08 +0100 Subject: [PATCH] feat(components/navigation/TotalLayoutStepper): create stepper for the total layout component, adjust logic to dynamically show or hide steps according to the step form values --- .../src/layout/TotalLayout/TotalLayout.js | 85 +++++++-- .../layout/TotalLayout/TotalLayout.stories.js | 174 +++++------------- .../TotalLayoutBody/TotalLayoutBody.js | 19 +- .../layout/TotalLayout/TotalLayoutFooter.js | 7 +- .../TotalLayoutHeader/TotalLayoutHeader.js | 4 +- .../TotalLayoutHeader/crossIcon.js | 12 ++ .../TotalLayoutStepContainer.js | 2 +- .../TotalLayoutStepContainer.styles.js | 2 +- .../layout/TotalLayout/mock/BasicDataForm.js | 78 ++++++++ .../layout/TotalLayout/mock/ContentForm.js | 62 +++++++ .../layout/TotalLayout/mock/OptionalForm.js | 35 ++++ .../TotalLayoutProgress/CurrentIcon.js | 9 + .../TotalLayoutProgress.js | 59 ++++++ .../TotalLayoutProgress.styles.js | 98 ++++++++++ .../TotalLayoutStep/TotalLayoutStep.js | 44 +++++ .../TotalLayoutStep/TotalLayoutStep.styles.js | 35 ++++ .../TotalLayoutStepper.constants.js | 4 + .../TotalLayoutStepper/TotalLayoutStepper.js | 80 ++++++++ .../TotalLayoutStepper/TotalLayoutStepper.mdx | 48 +++++ .../TotalLayoutStepper.stories.js | 40 ++++ .../TotalLayoutStepper.styles.js | 0 .../navigation/TotalLayoutStepper/index.js | 2 + packages/components/src/navigation/index.js | 1 + 23 files changed, 749 insertions(+), 151 deletions(-) create mode 100644 packages/components/src/layout/TotalLayout/TotalLayoutHeader/crossIcon.js create mode 100644 packages/components/src/layout/TotalLayout/mock/BasicDataForm.js create mode 100644 packages/components/src/layout/TotalLayout/mock/ContentForm.js create mode 100644 packages/components/src/layout/TotalLayout/mock/OptionalForm.js create mode 100644 packages/components/src/navigation/TotalLayoutStepper/TotalLayoutProgress/CurrentIcon.js create mode 100644 packages/components/src/navigation/TotalLayoutStepper/TotalLayoutProgress/TotalLayoutProgress.js create mode 100644 packages/components/src/navigation/TotalLayoutStepper/TotalLayoutProgress/TotalLayoutProgress.styles.js create mode 100644 packages/components/src/navigation/TotalLayoutStepper/TotalLayoutStep/TotalLayoutStep.js create mode 100644 packages/components/src/navigation/TotalLayoutStepper/TotalLayoutStep/TotalLayoutStep.styles.js create mode 100644 packages/components/src/navigation/TotalLayoutStepper/TotalLayoutStepper.constants.js create mode 100644 packages/components/src/navigation/TotalLayoutStepper/TotalLayoutStepper.js create mode 100644 packages/components/src/navigation/TotalLayoutStepper/TotalLayoutStepper.mdx create mode 100644 packages/components/src/navigation/TotalLayoutStepper/TotalLayoutStepper.stories.js create mode 100644 packages/components/src/navigation/TotalLayoutStepper/TotalLayoutStepper.styles.js create mode 100644 packages/components/src/navigation/TotalLayoutStepper/index.js diff --git a/packages/components/src/layout/TotalLayout/TotalLayout.js b/packages/components/src/layout/TotalLayout/TotalLayout.js index 5627ed7ee..dde4d9b47 100644 --- a/packages/components/src/layout/TotalLayout/TotalLayout.js +++ b/packages/components/src/layout/TotalLayout/TotalLayout.js @@ -9,30 +9,61 @@ import { TOTAL_LAYOUT_DEFAULT_PROPS, TOTAL_LAYOUT_PROP_TYPES } from './TotalLayo const useTotalLayout = () => { const [activeStep, setActiveStep] = React.useState(0); - return { activeStep, setActiveStep }; + const [completedSteps, setCompletedSteps] = React.useState([]); + const [stepsInfo, setStepsInfo] = React.useState([]); + return { + activeStep, + setActiveStep, + completedSteps, + setCompletedSteps, + stepsInfo, + setStepsInfo, + }; }; const TotalLayout = ({ Header, showStepper, - Steps, - stepsInfo, + initialStepsInfo, activeStep = 0, setActiveStep = () => {}, + completedSteps = [], + setCompletedSteps = () => {}, minStepNumberForDraftSave, onSave, footerActionsLabels, footerFinalActions, + stepsInfo, + setStepsInfo, }) => { const form = useFormContext(); const [topScroll, setTopScroll] = React.useState(false); const [showFooterShadow, setShowFooterShadow] = React.useState(false); + const [lastValidStep, setLastValidStep] = React.useState(false); - const totalSteps = Steps.length; const footerLeftOffset = showStepper && 192 + 16; // Stepper plus margin (16) : margin const bodyRef = React.useRef(); const { classes } = TotalLayoutStyles({ topScroll }, { name: 'TotalLayout' }); + const Steps = React.useMemo(() => initialStepsInfo.map((step) => step.stepComponent), []); + const totalSteps = Steps.length; + + // Get relevant info from initialStepsInfo when first rendering for further manipulation + React.useEffect(() => { + setStepsInfo( + initialStepsInfo.map(({ validationSchema, stepComponent, ...props }) => ({ ...props })), + ); + }, []); + + // Find the last valid step + React.useEffect(() => { + const lastValidStepIndex = stepsInfo.reduce( + (lastIndex, step, index) => (step.showStep ? index : lastIndex), + -1, + ); + setLastValidStep(lastValidStepIndex); + }, [stepsInfo, activeStep]); + // Define scroll and window resizing behavior const handleScroll = () => { const div = bodyRef.current; @@ -48,7 +79,6 @@ const TotalLayout = ({ setShowFooterShadow(false); } }; - React.useEffect(() => { const body = bodyRef.current; if (body) { @@ -64,12 +94,40 @@ const TotalLayout = ({ }, [bodyRef.current, handleScroll]); // Set and validate active step + const getNextValidStep = (index) => { + while (index < totalSteps) { + if (stepsInfo[index].showStep) { + return index; + } + index += 1; + } + return index - 1; + }; + + const getPreviousValidStep = (index) => { + while (index >= 0) { + if (stepsInfo[index].showStep) { + return index; + } + index -= 1; + } + return index; + }; + const handleNext = async () => { - setActiveStep(activeStep + 1); + setCompletedSteps((prevCompletedSteps) => [...prevCompletedSteps, activeStep]); + setActiveStep((prevActiveStep) => getNextValidStep(prevActiveStep + 1)); window.scrollTo(0, 0, { behavior: 'smooth' }); }; - const handlePrev = () => { - setActiveStep(activeStep - 1); + const handlePrev = async () => { + const isValidStep = await form.trigger(); + if (!isValidStep && completedSteps.includes(activeStep)) { + setCompletedSteps((prevCompletedSteps) => + prevCompletedSteps.filter((step) => step !== activeStep), + ); + } + + setActiveStep((prevActiveStep) => getPreviousValidStep(prevActiveStep - 1)); window.scrollTo(0, 0, { behavior: 'smooth' }); }; const validateAndAct = async (action, ...actionArgs) => { @@ -79,7 +137,7 @@ const TotalLayout = ({ } }; - // Set final actions to pass validation before execution + // Set final actions to be validated before execution const finalActions = []; if (footerFinalActions?.length) { footerFinalActions.forEach(({ label, action }) => { @@ -99,6 +157,8 @@ const TotalLayout = ({ stepsInfo={stepsInfo} activeStep={activeStep} scrollRef={bodyRef} + completedSteps={completedSteps} + lastValidStep={lastValidStep} > {Steps[activeStep]} @@ -106,7 +166,6 @@ const TotalLayout = ({