-
Notifications
You must be signed in to change notification settings - Fork 389
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 1 commit
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,15 @@ | ||
| 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 { useGetMergedSteps } from './hooks/useGetMergedSteps'; | ||
| 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 +30,16 @@ 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; | ||
| /** Set multiple 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. what is this prop used for? Could you expand on the purpose/use case for
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. It's currently used in the WizardStep to update the state of multiple steps at once. In that particular case, if there is a step that was previously hidden and then shown, the steps beyond that newly shown step, if they were already visited, are set to not-visited. The purpose being when isStepVisitRequired is enabled, we want to make sure the newly shown step is not skipped over.
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. After thinking about this a bit more, I think the better default behavior is to not disable the newly shown step if a step proceeding it was already visited. In terms of automatic behavior, this makes more sense to me at least. As a result of this, I removed this prop as it's not needed for now at least as a part of this change. |
||
| setSteps: React.Dispatch<React.SetStateAction<WizardControlStep[]>>; | ||
| } | ||
|
|
||
| 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 +52,7 @@ export interface WizardContextProviderProps { | |
| export const WizardContextProvider: React.FunctionComponent<WizardContextProviderProps> = ({ | ||
| steps: initialSteps, | ||
| footer: initialFooter, | ||
| currentStepIndex, | ||
| isStepVisitRequired, | ||
| activeStepIndex, | ||
| children, | ||
| onNext, | ||
| onBack, | ||
|
|
@@ -68,35 +65,38 @@ export const WizardContextProvider: React.FunctionComponent<WizardContextProvide | |
| const [currentFooter, setCurrentFooter] = React.useState( | ||
| typeof initialFooter !== 'function' ? initialFooter : undefined | ||
| ); | ||
| const currentStep = getCurrentStep(steps, currentStepIndex); | ||
| const mergedSteps = useGetMergedSteps(initialSteps, steps); | ||
| const activeStep = getActiveStep(mergedSteps, activeStepIndex); | ||
|
|
||
| const goToNextStep = React.useCallback(() => onNext(steps), [onNext, steps]); | ||
| const goToPrevStep = React.useCallback(() => onBack(steps), [onBack, steps]); | ||
| 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 +112,22 @@ 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, | ||
| setSteps, | ||
| 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.
👌