Skip to content

Commit 5515dcb

Browse files
authored
docs(Wizard/next): Recreate all legacy wizard examples (#8422)
* docs(Wizard/next): Recreate all legacy wizard examples * add event params to wizard component callback props, add focus logic to drawer example, replace lodash findLastIndex in Wizard * fix demo app build issue with MenuDemo props * address feedback * updating v2 'validate on button press' example * rename isCollapsible to isExpandable * change default value of WizardStep's isExpandable prop to false, remove another lodash usage
1 parent be07a04 commit 5515dcb

39 files changed

+1486
-590
lines changed

packages/react-core/src/components/Menu/MenuGroup.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export interface MenuGroupProps extends Omit<React.HTMLProps<HTMLElement>, 'labe
88
/** Additional classes added to the MenuGroup */
99
className?: string;
1010
/** Group label */
11-
label?: React.ReactNode | React.FC;
11+
label?: React.ReactNode;
1212
/** ID for title label */
1313
titleId?: string;
1414
/** Forwarded ref */
@@ -32,7 +32,7 @@ const MenuGroupBase: React.FunctionComponent<MenuGroupProps> = ({
3232
<>
3333
{['function', 'string'].includes(typeof label) ? (
3434
<Wrapper className={css(styles.menuGroupTitle)} id={titleId}>
35-
{label as React.ReactNode}
35+
{label}
3636
</Wrapper>
3737
) : (
3838
label

packages/react-core/src/next/components/Wizard/Wizard.tsx

Lines changed: 71 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,18 @@
11
import React from 'react';
2-
import findLastIndex from 'lodash/findLastIndex';
32

43
import { css } from '@patternfly/react-styles';
54
import styles from '@patternfly/react-styles/css/components/Wizard/wizard';
65

76
import {
87
isWizardParentStep,
9-
WizardNavStepFunction,
10-
WizardControlStep,
8+
WizardStepType,
119
isCustomWizardNav,
1210
WizardFooterType,
13-
WizardNavType
11+
WizardNavType,
12+
WizardStepChangeScope
1413
} from './types';
15-
import { buildSteps, normalizeNavStep } from './utils';
14+
import { buildSteps } from './utils';
1615
import { useWizardContext, WizardContextProvider } from './WizardContext';
17-
import { WizardStepProps } from './WizardStep';
1816
import { WizardToggle } from './WizardToggle';
1917
import { WizardNavInternal } from './WizardNavInternal';
2018

@@ -25,7 +23,7 @@ import { WizardNavInternal } from './WizardNavInternal';
2523

2624
export interface WizardProps extends React.HTMLProps<HTMLDivElement> {
2725
/** Step components */
28-
children: React.ReactElement<WizardStepProps> | React.ReactElement<WizardStepProps>[];
26+
children: React.ReactNode | React.ReactNode[];
2927
/** Wizard header */
3028
header?: React.ReactNode;
3129
/** Wizard footer */
@@ -40,18 +38,21 @@ export interface WizardProps extends React.HTMLProps<HTMLDivElement> {
4038
width?: number | string;
4139
/** Custom height of the wizard */
4240
height?: number | string;
43-
/** Disables navigation items that haven't been visited. Defaults to false */
44-
isStepVisitRequired?: boolean;
45-
/** Callback function when a step in the navigation is clicked */
46-
onNavByIndex?: WizardNavStepFunction;
47-
/** Callback function after next button is clicked */
48-
onNext?: WizardNavStepFunction;
49-
/** Callback function after back button is clicked */
50-
onBack?: WizardNavStepFunction;
41+
/** Disables steps that haven't been visited. Defaults to false. */
42+
isVisitRequired?: boolean;
43+
/** Progressively shows steps, where all steps following the active step are hidden. Defaults to false. */
44+
isProgressive?: boolean;
45+
/** Callback function when navigating between steps */
46+
onStepChange?: (
47+
event: React.MouseEvent<HTMLButtonElement>,
48+
currentStep: WizardStepType,
49+
prevStep: WizardStepType,
50+
scope: WizardStepChangeScope
51+
) => void | Promise<void>;
5152
/** Callback function to save at the end of the wizard, if not specified uses onClose */
52-
onSave?: () => void | Promise<void>;
53+
onSave?: (event: React.MouseEvent<HTMLButtonElement>) => void | Promise<void>;
5354
/** Callback function to close the wizard */
54-
onClose?: () => void;
55+
onClose?: (event: React.MouseEvent<HTMLButtonElement>) => void;
5556
}
5657

5758
export const Wizard = ({
@@ -63,48 +64,61 @@ export const Wizard = ({
6364
header,
6465
nav,
6566
startIndex = 1,
66-
isStepVisitRequired = false,
67-
onNavByIndex,
68-
onNext,
69-
onBack,
67+
isVisitRequired = false,
68+
isProgressive = false,
69+
onStepChange,
7070
onSave,
7171
onClose,
7272
...wrapperProps
7373
}: WizardProps) => {
7474
const [activeStepIndex, setActiveStepIndex] = React.useState(startIndex);
7575
const initialSteps = buildSteps(children);
76+
const firstStepRef = React.useRef(initialSteps[startIndex - 1]);
7677

77-
const goToNextStep = (steps: WizardControlStep[] = initialSteps) => {
78-
const newStepIndex = steps.find(step => step.index > activeStepIndex && !step.isHidden && !isWizardParentStep(step))
79-
?.index;
78+
// When the startIndex maps to a parent step, focus on the first sub-step
79+
React.useEffect(() => {
80+
if (isWizardParentStep(firstStepRef.current)) {
81+
setActiveStepIndex(startIndex + 1);
82+
}
83+
}, [startIndex]);
84+
85+
const goToNextStep = (event: React.MouseEvent<HTMLButtonElement>, steps: WizardStepType[] = initialSteps) => {
86+
const newStep = steps.find(
87+
step => step.index > activeStepIndex && !step.isHidden && !step.isDisabled && !isWizardParentStep(step)
88+
);
8089

81-
if (activeStepIndex >= steps.length || !newStepIndex) {
82-
return onSave ? onSave() : onClose?.();
90+
if (activeStepIndex >= steps.length || !newStep?.index) {
91+
return onSave ? onSave(event) : onClose?.(event);
8392
}
8493

8594
const currStep = isWizardParentStep(steps[activeStepIndex]) ? steps[activeStepIndex + 1] : steps[activeStepIndex];
8695
const prevStep = steps[activeStepIndex - 1];
8796

88-
setActiveStepIndex(newStepIndex);
89-
return onNext?.(normalizeNavStep(currStep), normalizeNavStep(prevStep));
97+
setActiveStepIndex(newStep?.index);
98+
onStepChange?.(event, currStep, prevStep, WizardStepChangeScope.Next);
9099
};
91100

92-
const goToPrevStep = (steps: WizardControlStep[] = initialSteps) => {
93-
const newStepIndex =
94-
findLastIndex(
95-
steps,
96-
(step: WizardControlStep) => step.index < activeStepIndex && !step.isHidden && !isWizardParentStep(step)
97-
) + 1;
101+
const goToPrevStep = (event: React.MouseEvent<HTMLButtonElement>, steps: WizardStepType[] = initialSteps) => {
102+
const newStep = [...steps]
103+
.reverse()
104+
.find(
105+
(step: WizardStepType) =>
106+
step.index < activeStepIndex && !step.isHidden && !step.isDisabled && !isWizardParentStep(step)
107+
);
98108
const currStep = isWizardParentStep(steps[activeStepIndex - 2])
99109
? steps[activeStepIndex - 3]
100110
: steps[activeStepIndex - 2];
101111
const prevStep = steps[activeStepIndex - 1];
102112

103-
setActiveStepIndex(newStepIndex);
104-
return onBack?.(normalizeNavStep(currStep), normalizeNavStep(prevStep));
113+
setActiveStepIndex(newStep?.index);
114+
onStepChange?.(event, currStep, prevStep, WizardStepChangeScope.Back);
105115
};
106116

107-
const goToStepByIndex = (steps: WizardControlStep[] = initialSteps, index: number) => {
117+
const goToStepByIndex = (
118+
event: React.MouseEvent<HTMLButtonElement>,
119+
steps: WizardStepType[] = initialSteps,
120+
index: number
121+
) => {
108122
const lastStepIndex = steps.length + 1;
109123

110124
// Handle index when out of bounds or hidden
@@ -118,25 +132,25 @@ export const Wizard = ({
118132
const prevStep = steps[activeStepIndex - 1];
119133

120134
setActiveStepIndex(index);
121-
return onNavByIndex?.(normalizeNavStep(currStep), normalizeNavStep(prevStep));
135+
onStepChange?.(event, currStep, prevStep, WizardStepChangeScope.Nav);
122136
};
123137

124-
const goToStepById = (steps: WizardControlStep[] = initialSteps, id: number | string) => {
138+
const goToStepById = (steps: WizardStepType[] = initialSteps, id: number | string) => {
125139
const step = steps.find(step => step.id === id);
126140
const stepIndex = step?.index;
127141
const lastStepIndex = steps.length + 1;
128142

129-
if (stepIndex > 0 && stepIndex < lastStepIndex && !step.isHidden) {
143+
if (stepIndex > 0 && stepIndex < lastStepIndex && !step.isDisabled && !step.isHidden) {
130144
setActiveStepIndex(stepIndex);
131145
}
132146
};
133147

134-
const goToStepByName = (steps: WizardControlStep[] = initialSteps, name: string) => {
148+
const goToStepByName = (steps: WizardStepType[] = initialSteps, name: string) => {
135149
const step = steps.find(step => step.name === name);
136150
const stepIndex = step?.index;
137151
const lastStepIndex = steps.length + 1;
138152

139-
if (stepIndex > 0 && stepIndex < lastStepIndex && !step.isHidden) {
153+
if (stepIndex > 0 && stepIndex < lastStepIndex && !step.isDisabled && !step.isHidden) {
140154
setActiveStepIndex(stepIndex);
141155
}
142156
};
@@ -162,13 +176,17 @@ export const Wizard = ({
162176
{...wrapperProps}
163177
>
164178
{header}
165-
<WizardInternal nav={nav} isStepVisitRequired={isStepVisitRequired} />
179+
<WizardInternal nav={nav} isVisitRequired={isVisitRequired} isProgressive={isProgressive} />
166180
</div>
167181
</WizardContextProvider>
168182
);
169183
};
170184

171-
const WizardInternal = ({ nav, isStepVisitRequired }: Pick<WizardProps, 'nav' | 'isStepVisitRequired'>) => {
185+
const WizardInternal = ({
186+
nav,
187+
isVisitRequired,
188+
isProgressive
189+
}: Pick<WizardProps, 'nav' | 'isVisitRequired' | 'isProgressive'>) => {
172190
const { activeStep, steps, footer, goToStepByIndex } = useWizardContext();
173191
const [isNavExpanded, setIsNavExpanded] = React.useState(false);
174192

@@ -177,8 +195,15 @@ const WizardInternal = ({ nav, isStepVisitRequired }: Pick<WizardProps, 'nav' |
177195
return typeof nav === 'function' ? nav(isNavExpanded, steps, activeStep, goToStepByIndex) : nav;
178196
}
179197

180-
return <WizardNavInternal nav={nav} isNavExpanded={isNavExpanded} isStepVisitRequired={isStepVisitRequired} />;
181-
}, [activeStep, isStepVisitRequired, goToStepByIndex, isNavExpanded, nav, steps]);
198+
return (
199+
<WizardNavInternal
200+
nav={nav}
201+
isNavExpanded={isNavExpanded}
202+
isVisitRequired={isVisitRequired}
203+
isProgressive={isProgressive}
204+
/>
205+
);
206+
}, [activeStep, isVisitRequired, isProgressive, goToStepByIndex, isNavExpanded, nav, steps]);
182207

183208
return (
184209
<WizardToggle

packages/react-core/src/next/components/Wizard/WizardContext.tsx

Lines changed: 38 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,23 @@
11
import React from 'react';
22

3-
import { isCustomWizardFooter, WizardControlStep, WizardFooterType } from './types';
3+
import { isCustomWizardFooter, isWizardSubStep, WizardStepType, WizardFooterType } from './types';
44
import { getActiveStep } from './utils';
5+
import { useGetMergedSteps } from './hooks';
56
import { WizardFooter, WizardFooterProps } from './WizardFooter';
67

78
export interface WizardContextProps {
89
/** List of steps */
9-
steps: WizardControlStep[];
10+
steps: WizardStepType[];
1011
/** Current step */
11-
activeStep: WizardControlStep;
12+
activeStep: WizardStepType;
1213
/** Footer element */
1314
footer: React.ReactElement;
15+
/** Close the wizard */
16+
close: () => void;
1417
/** Navigate to the next step */
15-
onNext: () => void | Promise<void>;
18+
goToNextStep: () => void | Promise<void>;
1619
/** Navigate to the previous step */
17-
onBack: () => void | Promise<void>;
18-
/** Close the wizard */
19-
onClose: () => void;
20+
goToPrevStep: () => void | Promise<void>;
2021
/** Navigate to step by ID */
2122
goToStepById: (id: number | string) => void;
2223
/** Navigate to step by name */
@@ -26,24 +27,28 @@ export interface WizardContextProps {
2627
/** Update the footer with any react element */
2728
setFooter: (footer: React.ReactElement | Partial<WizardFooterProps>) => void;
2829
/** Get step by ID */
29-
getStep: (stepId: number | string) => WizardControlStep;
30+
getStep: (stepId: number | string) => WizardStepType;
3031
/** Set step by ID */
31-
setStep: (step: Pick<WizardControlStep, 'id'> & Partial<WizardControlStep>) => void;
32+
setStep: (step: Pick<WizardStepType, 'id'> & Partial<WizardStepType>) => void;
3233
}
3334

3435
export const WizardContext = React.createContext({} as WizardContextProps);
3536

3637
export interface WizardContextProviderProps {
37-
steps: WizardControlStep[];
38+
steps: WizardStepType[];
3839
activeStepIndex: number;
3940
footer: WizardFooterType;
4041
children: React.ReactElement;
41-
onNext(steps: WizardControlStep[]): void;
42-
onBack(steps: WizardControlStep[]): void;
43-
onClose(): void;
44-
goToStepById(steps: WizardControlStep[], id: number | string): void;
45-
goToStepByName(steps: WizardControlStep[], name: string): void;
46-
goToStepByIndex(steps: WizardControlStep[], index: number): void;
42+
onNext(event: React.MouseEvent<HTMLButtonElement>, steps: WizardStepType[]): void;
43+
onBack(event: React.MouseEvent<HTMLButtonElement>, steps: WizardStepType[]): void;
44+
onClose(event: React.MouseEvent<HTMLButtonElement>): void;
45+
goToStepById(steps: WizardStepType[], id: number | string): void;
46+
goToStepByName(steps: WizardStepType[], name: string): void;
47+
goToStepByIndex(
48+
event: React.MouseEvent<HTMLButtonElement> | React.MouseEvent<HTMLAnchorElement>,
49+
steps: WizardStepType[],
50+
index: number
51+
): void;
4752
}
4853

4954
export const WizardContextProvider: React.FunctionComponent<WizardContextProviderProps> = ({
@@ -62,25 +67,12 @@ export const WizardContextProvider: React.FunctionComponent<WizardContextProvide
6267
const [currentFooter, setCurrentFooter] = React.useState(
6368
typeof initialFooter !== 'function' ? initialFooter : undefined
6469
);
70+
const steps = useGetMergedSteps(initialSteps, currentSteps);
71+
const activeStep = React.useMemo(() => getActiveStep(steps, activeStepIndex), [activeStepIndex, steps]);
6572

66-
// Combined initial and current state steps
67-
const steps = React.useMemo(
68-
() =>
69-
currentSteps.map((currentStepProps, index) => {
70-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
71-
const { isVisited, ...initialStepProps } = initialSteps[index];
72-
73-
return {
74-
...currentStepProps,
75-
...initialStepProps
76-
};
77-
}),
78-
[initialSteps, currentSteps]
79-
);
80-
const activeStep = getActiveStep(steps, activeStepIndex);
81-
82-
const goToNextStep = React.useCallback(() => onNext(steps), [onNext, steps]);
83-
const goToPrevStep = React.useCallback(() => onBack(steps), [onBack, steps]);
73+
const close = React.useCallback(() => onClose(null), [onClose]);
74+
const goToNextStep = React.useCallback(() => onNext(null, steps), [onNext, steps]);
75+
const goToPrevStep = React.useCallback(() => onBack(null, steps), [onBack, steps]);
8476

8577
const footer = React.useMemo(() => {
8678
const wizardFooter = activeStep?.footer || currentFooter || initialFooter;
@@ -89,7 +81,7 @@ export const WizardContextProvider: React.FunctionComponent<WizardContextProvide
8981
const customFooter = wizardFooter;
9082

9183
return typeof customFooter === 'function'
92-
? customFooter(activeStep, goToNextStep, goToPrevStep, onClose)
84+
? customFooter(activeStep, goToNextStep, goToPrevStep, close)
9385
: customFooter;
9486
}
9587

@@ -98,17 +90,17 @@ export const WizardContextProvider: React.FunctionComponent<WizardContextProvide
9890
activeStep={activeStep}
9991
onNext={goToNextStep}
10092
onBack={goToPrevStep}
101-
onClose={onClose}
102-
isBackDisabled={activeStep?.id === steps[0]?.id}
93+
onClose={close}
94+
isBackDisabled={activeStep?.index === 1 || (isWizardSubStep(activeStep) && activeStep?.index === 2)}
10395
{...wizardFooter}
10496
/>
10597
);
106-
}, [currentFooter, initialFooter, activeStep, goToNextStep, goToPrevStep, onClose, steps]);
98+
}, [currentFooter, initialFooter, activeStep, goToNextStep, goToPrevStep, close]);
10799

108100
const getStep = React.useCallback((stepId: string | number) => steps.find(step => step.id === stepId), [steps]);
109101

110102
const setStep = React.useCallback(
111-
(step: Pick<WizardControlStep, 'id'> & Partial<WizardControlStep>) =>
103+
(step: Pick<WizardStepType, 'id'> & Partial<WizardStepType>) =>
112104
setCurrentSteps(prevSteps =>
113105
prevSteps.map(prevStep => {
114106
if (prevStep.id === step.id) {
@@ -127,15 +119,18 @@ export const WizardContextProvider: React.FunctionComponent<WizardContextProvide
127119
steps,
128120
activeStep,
129121
footer,
130-
onClose,
122+
close,
131123
getStep,
132124
setStep,
125+
goToNextStep,
126+
goToPrevStep,
133127
setFooter: setCurrentFooter,
134-
onNext: goToNextStep,
135-
onBack: goToPrevStep,
136128
goToStepById: React.useCallback(id => goToStepById(steps, id), [goToStepById, steps]),
137129
goToStepByName: React.useCallback(name => goToStepByName(steps, name), [goToStepByName, steps]),
138-
goToStepByIndex: React.useCallback(index => goToStepByIndex(steps, index), [goToStepByIndex, steps])
130+
goToStepByIndex: React.useCallback((index: number) => goToStepByIndex(null, steps, index), [
131+
goToStepByIndex,
132+
steps
133+
])
139134
}}
140135
>
141136
{children}

0 commit comments

Comments
 (0)