-
Notifications
You must be signed in to change notification settings - Fork 387
next(Wizard): Allow for WizardStep to better control state from props entry-point, include index #8218
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
next(Wizard): Allow for WizardStep to better control state from props entry-point, include index #8218
Changes from 2 commits
0ffa3fd
8f63e91
ade6abe
eac5447
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,16 +1,14 @@ | ||
| import React from 'react'; | ||
|
|
||
| import { isCustomWizardFooter, isWizardParentStep, WizardControlStep, WizardFooterType } from './types'; | ||
| import { getCurrentStep } from './utils'; | ||
| import { isCustomWizardFooter, WizardControlStep, WizardFooterType } from './types'; | ||
| import { getActiveStep } from './utils'; | ||
| import { WizardFooter, WizardFooterProps } from './WizardFooter'; | ||
|
|
||
| export interface WizardContextProps { | ||
| /** List of steps */ | ||
| steps: WizardControlStep[]; | ||
| /** Current step */ | ||
| currentStep: WizardControlStep; | ||
| /** Current step index */ | ||
| currentStepIndex: number; | ||
| activeStep: WizardControlStep; | ||
| /** Footer element */ | ||
| footer: React.ReactElement; | ||
| /** Navigate to the next step */ | ||
|
|
@@ -31,17 +29,14 @@ export interface WizardContextProps { | |
| getStep: (stepId: number | string) => WizardControlStep; | ||
| /** Set step by ID */ | ||
| setStep: (step: Pick<WizardControlStep, 'id'> & Partial<WizardControlStep>) => void; | ||
| /** Toggle step visibility by ID */ | ||
| toggleStep: (stepId: number | string, isHidden: boolean) => void; | ||
| } | ||
|
|
||
| export const WizardContext = React.createContext({} as WizardContextProps); | ||
|
|
||
| export interface WizardContextProviderProps { | ||
| steps: WizardControlStep[]; | ||
| currentStepIndex: number; | ||
| activeStepIndex: number; | ||
| footer: WizardFooterType; | ||
| isStepVisitRequired: boolean; | ||
| children: React.ReactElement; | ||
| onNext(steps: WizardControlStep[]): void; | ||
| onBack(steps: WizardControlStep[]): void; | ||
|
|
@@ -54,8 +49,7 @@ export interface WizardContextProviderProps { | |
| export const WizardContextProvider: React.FunctionComponent<WizardContextProviderProps> = ({ | ||
| steps: initialSteps, | ||
| footer: initialFooter, | ||
| currentStepIndex, | ||
| isStepVisitRequired, | ||
| activeStepIndex, | ||
| children, | ||
| onNext, | ||
| onBack, | ||
|
|
@@ -68,35 +62,52 @@ export const WizardContextProvider: React.FunctionComponent<WizardContextProvide | |
| const [currentFooter, setCurrentFooter] = React.useState( | ||
| typeof initialFooter !== 'function' ? initialFooter : undefined | ||
| ); | ||
| const currentStep = getCurrentStep(steps, currentStepIndex); | ||
|
|
||
| const goToNextStep = React.useCallback(() => onNext(steps), [onNext, steps]); | ||
| const goToPrevStep = React.useCallback(() => onBack(steps), [onBack, steps]); | ||
| // Combined initial and current state steps | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Cool application of If this is just an array of all the steps, I'm wondering if
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's a good suggestion. I agree, 'merged' doesn't tell much of a story. I'll update it to just be |
||
| const mergedSteps = React.useMemo( | ||
| () => | ||
| steps.map((currentStepProps, index) => { | ||
| // eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
| const { isVisited, ...initialStepProps } = initialSteps[index]; | ||
|
|
||
| return { | ||
| ...currentStepProps, | ||
| ...initialStepProps | ||
| }; | ||
| }), | ||
| [initialSteps, steps] | ||
| ); | ||
| const activeStep = getActiveStep(mergedSteps, activeStepIndex); | ||
|
|
||
| const goToNextStep = React.useCallback(() => onNext(mergedSteps), [onNext, mergedSteps]); | ||
| const goToPrevStep = React.useCallback(() => onBack(mergedSteps), [onBack, mergedSteps]); | ||
|
|
||
| const footer = React.useMemo(() => { | ||
| const wizardFooter = currentFooter || initialFooter; | ||
| const wizardFooter = activeStep?.footer || currentFooter || initialFooter; | ||
|
|
||
| if (isCustomWizardFooter(wizardFooter)) { | ||
| const customFooter = wizardFooter; | ||
|
|
||
| return typeof customFooter === 'function' | ||
| ? customFooter(currentStep, goToNextStep, goToPrevStep, onClose) | ||
| ? customFooter(activeStep, goToNextStep, goToPrevStep, onClose) | ||
| : customFooter; | ||
| } | ||
|
|
||
| return ( | ||
| <WizardFooter | ||
| currentStep={currentStep} | ||
| activeStep={activeStep} | ||
| onNext={goToNextStep} | ||
| onBack={goToPrevStep} | ||
| onClose={onClose} | ||
| isBackDisabled={currentStep?.id === steps[0]?.id} | ||
| isBackDisabled={activeStep?.id === mergedSteps[0]?.id} | ||
| {...wizardFooter} | ||
| /> | ||
| ); | ||
| }, [currentFooter, initialFooter, currentStep, goToNextStep, goToPrevStep, onClose, steps]); | ||
| }, [currentFooter, initialFooter, activeStep, goToNextStep, goToPrevStep, onClose, mergedSteps]); | ||
|
|
||
| const getStep = React.useCallback((stepId: string | number) => steps.find(step => step.id === stepId), [steps]); | ||
| const getStep = React.useCallback((stepId: string | number) => mergedSteps.find(step => step.id === stepId), [ | ||
| mergedSteps | ||
| ]); | ||
|
|
||
| const setStep = React.useCallback( | ||
| (step: Pick<WizardControlStep, 'id'> & Partial<WizardControlStep>) => | ||
|
|
@@ -112,62 +123,21 @@ export const WizardContextProvider: React.FunctionComponent<WizardContextProvide | |
| [] | ||
| ); | ||
|
|
||
| const toggleStep = React.useCallback( | ||
| (stepId: string | number, isHidden: boolean) => | ||
| setSteps(prevSteps => { | ||
| let stepToHide: WizardControlStep; | ||
|
|
||
| return prevSteps.map(prevStep => { | ||
| if (prevStep.id === stepId) { | ||
| // Don't hide the currently active step or its parent (if a sub-step). | ||
| if ( | ||
| isHidden && | ||
| (currentStep.id === prevStep.id || | ||
| (isWizardParentStep(prevStep) && prevStep.subStepIds.includes(currentStep.id))) | ||
| ) { | ||
| // eslint-disable-next-line no-console | ||
| console.error('Wizard: Unable to hide the current step or its parent.'); | ||
| return prevStep; | ||
| } | ||
|
|
||
| stepToHide = { ...prevStep, isHidden }; | ||
| return stepToHide; | ||
| } | ||
|
|
||
| // When isStepVisitRequired is enabled, if the step was previously hidden and not visited yet, | ||
| // when it is shown, all steps beyond it should be disabled to ensure it is visited. | ||
| if ( | ||
| isStepVisitRequired && | ||
| stepToHide?.isHidden === false && | ||
| !stepToHide?.isVisited && | ||
| prevSteps.indexOf(stepToHide) < prevSteps.indexOf(prevStep) | ||
| ) { | ||
| return { ...prevStep, isVisited: false }; | ||
| } | ||
|
|
||
| return prevStep; | ||
| }); | ||
| }), | ||
| [currentStep.id, isStepVisitRequired] | ||
| ); | ||
|
|
||
| return ( | ||
| <WizardContext.Provider | ||
| value={{ | ||
| steps, | ||
| currentStep, | ||
| currentStepIndex, | ||
| steps: mergedSteps, | ||
| activeStep, | ||
| footer, | ||
| onClose, | ||
| getStep, | ||
| setStep, | ||
| toggleStep, | ||
| setFooter: setCurrentFooter, | ||
| onNext: goToNextStep, | ||
| onBack: goToPrevStep, | ||
| goToStepById: React.useCallback(id => goToStepById(steps, id), [goToStepById, steps]), | ||
| goToStepByName: React.useCallback(name => goToStepByName(steps, name), [goToStepByName, steps]), | ||
| goToStepByIndex: React.useCallback(index => goToStepByIndex(steps, index), [goToStepByIndex, steps]) | ||
| goToStepById: React.useCallback(id => goToStepById(mergedSteps, id), [goToStepById, mergedSteps]), | ||
| goToStepByName: React.useCallback(name => goToStepByName(mergedSteps, name), [goToStepByName, mergedSteps]), | ||
| goToStepByIndex: React.useCallback(index => goToStepByIndex(mergedSteps, index), [goToStepByIndex, mergedSteps]) | ||
| }} | ||
| > | ||
| {children} | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👌