import React, { PropsWithChildren } from 'react' import { WizardStepController } from './wizard-types' import * as logger from './logger' import WizardContext from './wizard-context' interface WizardProps extends PropsWithChildren { wrapper?: React.ReactElement } const Wizard: React.FC = ({ children, wrapper: Wrapper }) => { const [activeStep, setActiveStep] = React.useState(0) const [loading, setIsLoading] = React.useState(false) const hasNextStep = React.useRef(true) const hasPreviousStep = React.useRef(false) const nextStepHandler = React.useRef({ next: () => true }) const stepCount = React.Children.toArray(children).length hasNextStep.current = activeStep < stepCount - 1 hasPreviousStep.current = activeStep > 0 const goToNextStep = React.useRef(() => { if (hasNextStep.current) { setActiveStep((activeStep) => activeStep + 1) } }) const goToPreviousStep = React.useRef(() => { if (hasPreviousStep.current) { nextStepHandler.current = null setActiveStep((activeStep) => activeStep - 1) } }) const goToStep = React.useRef((stepIndex: number) => { if (stepIndex >= 0 && stepIndex < stepCount) { nextStepHandler.current = null setActiveStep(stepIndex) } else { if (process.env.NODE_ENV === 'development') { logger.log( 'warn', [ `Invalid step index [${stepIndex}] passed to 'goToStep'. `, `Ensure the given stepIndex is not out of boundaries.` ].join('') ) } } }) const registerStep = React.useRef((handler: WizardStepController) => { nextStepHandler.current = handler }) const doNextStep = React.useRef(async () => { if (hasNextStep.current && nextStepHandler.current) { try { setIsLoading(true) const result = await nextStepHandler.current.next() setIsLoading(false) if (result) { nextStepHandler.current = null goToNextStep.current() } } catch (error) { setIsLoading(false) throw error } } else { goToNextStep.current() } }) const wizardValue = React.useMemo( () => ({ nextStep: doNextStep.current, previousStep: goToPreviousStep.current, registerStep: registerStep.current, loading, activeStep, stepCount, isFirstStep: !hasPreviousStep.current, isLastStep: !hasNextStep.current, goToStep: goToStep.current }), [activeStep, stepCount, loading] ) const activeStepContent = React.useMemo(() => { const reactChildren = React.Children.toArray(children) if (process.env.NODE_ENV === 'development') { if (reactChildren.length === 0) { console.warn('Make sure to pass your steps as children in your ') } if (activeStep > reactChildren.length) { console.warn('An invalid startIndex is passed to ') } } return reactChildren[activeStep] }, [activeStep, children]) const enhancedActiveStepContent = React.useMemo( () => Wrapper ? React.cloneElement(Wrapper, { children: activeStepContent }) : activeStepContent, [Wrapper, activeStepContent] ) return ( {enhancedActiveStepContent} ) } export default Wizard