Wizard.jsx
109 lines
| 1 | /* eslint-disable import/no-extraneous-dependencies */ |
| 2 | import { |
| 3 | cloneElement, |
| 4 | memo, |
| 5 | useMemo, |
| 6 | useRef, |
| 7 | useState, |
| 8 | Children, |
| 9 | } from '@wordpress/element'; |
| 10 | |
| 11 | /** |
| 12 | * Internal Dependencies |
| 13 | */ |
| 14 | import WizardContext from './wizardContext'; |
| 15 | |
| 16 | const Wizard = memo( |
| 17 | ({ header, footer, children, wrapper: Wrapper, startIndex = 1 }) => { |
| 18 | const [activeStep, setActiveStep] = useState(startIndex - 1); |
| 19 | const [isLoading, setIsLoading] = useState(false); |
| 20 | const hasNextStep = useRef(true); |
| 21 | const hasPreviousStep = useRef(false); |
| 22 | const nextStepHandler = useRef(() => {}); |
| 23 | const stepCount = Children.toArray(children).length; |
| 24 | |
| 25 | hasNextStep.current = activeStep < stepCount - 1; |
| 26 | hasPreviousStep.current = activeStep > 0; |
| 27 | |
| 28 | const goToNextStep = useRef(() => { |
| 29 | if (hasNextStep.current) { |
| 30 | setActiveStep((newActiveStep) => newActiveStep + 1); |
| 31 | } |
| 32 | }); |
| 33 | |
| 34 | const goToPreviousStep = useRef(() => { |
| 35 | if (hasPreviousStep.current) { |
| 36 | nextStepHandler.current = null; |
| 37 | setActiveStep((newActiveStep) => newActiveStep - 1); |
| 38 | } |
| 39 | }); |
| 40 | |
| 41 | const goToStep = useRef((stepIndex) => { |
| 42 | if (stepIndex >= 0 && stepIndex < stepCount) { |
| 43 | nextStepHandler.current = null; |
| 44 | setActiveStep(stepIndex); |
| 45 | } |
| 46 | }); |
| 47 | |
| 48 | // Callback to attach the step handler |
| 49 | const handleStep = useRef((handler) => { |
| 50 | nextStepHandler.current = handler; |
| 51 | }); |
| 52 | |
| 53 | const doNextStep = useRef(async () => { |
| 54 | if (hasNextStep.current && nextStepHandler.current) { |
| 55 | try { |
| 56 | setIsLoading(true); |
| 57 | await nextStepHandler.current(); |
| 58 | setIsLoading(false); |
| 59 | nextStepHandler.current = null; |
| 60 | goToNextStep.current(); |
| 61 | } catch (error) { |
| 62 | setIsLoading(false); |
| 63 | throw error; |
| 64 | } |
| 65 | } else { |
| 66 | goToNextStep.current(); |
| 67 | } |
| 68 | }); |
| 69 | |
| 70 | const wizardValue = useMemo( |
| 71 | () => ({ |
| 72 | nextStep: doNextStep.current, |
| 73 | previousStep: goToPreviousStep.current, |
| 74 | handleStep: handleStep.current, |
| 75 | isLoading, |
| 76 | activeStep, |
| 77 | stepCount, |
| 78 | isFirstStep: !hasPreviousStep.current, |
| 79 | isLastStep: !hasNextStep.current, |
| 80 | goToStep: goToStep.current, |
| 81 | }), |
| 82 | [activeStep, stepCount, isLoading] |
| 83 | ); |
| 84 | |
| 85 | const activeStepContent = useMemo(() => { |
| 86 | const reactChildren = Children.toArray(children); |
| 87 | return reactChildren[activeStep]; |
| 88 | }, [activeStep, children]); |
| 89 | |
| 90 | const enhancedActiveStepContent = useMemo( |
| 91 | () => |
| 92 | Wrapper |
| 93 | ? cloneElement(Wrapper, { children: activeStepContent }) |
| 94 | : activeStepContent, |
| 95 | [Wrapper, activeStepContent] |
| 96 | ); |
| 97 | |
| 98 | return ( |
| 99 | <WizardContext.Provider value={wizardValue}> |
| 100 | {header} |
| 101 | {enhancedActiveStepContent} |
| 102 | {footer} |
| 103 | </WizardContext.Provider> |
| 104 | ); |
| 105 | } |
| 106 | ); |
| 107 | |
| 108 | export default Wizard; |
| 109 |