forked from patternfly/patternfly-react
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathWizard.tsx
More file actions
195 lines (170 loc) · 6.26 KB
/
Copy pathWizard.tsx
File metadata and controls
195 lines (170 loc) · 6.26 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
import React from 'react';
import findLastIndex from 'lodash/findLastIndex';
import { css } from '@patternfly/react-styles';
import styles from '@patternfly/react-styles/css/components/Wizard/wizard';
import {
isWizardParentStep,
WizardNavStepFunction,
WizardControlStep,
isCustomWizardNav,
WizardFooterType,
WizardNavType
} from './types';
import { buildSteps, normalizeNavStep } from './utils';
import { useWizardContext, WizardContextProvider } from './WizardContext';
import { WizardStepProps } from './WizardStep';
import { WizardToggle } from './WizardToggle';
import { WizardNavInternal } from './WizardNavInternal';
/**
* Wrapper for all steps and hosts state, including navigation helpers, within context.
* The WizardContext provided by default gives any child of wizard access to those resources.
*/
export interface WizardProps extends React.HTMLProps<HTMLDivElement> {
/** Step components */
children: React.ReactElement<WizardStepProps> | React.ReactElement<WizardStepProps>[];
/** Wizard header */
header?: React.ReactNode;
/** Wizard footer */
footer?: WizardFooterType;
/** Wizard navigation */
nav?: WizardNavType;
/** The initial index the wizard is to start on (1 or higher). Defaults to 1. */
startIndex?: number;
/** Additional classes spread to the wizard */
className?: string;
/** Custom width of the wizard */
width?: number | string;
/** Custom height of the wizard */
height?: number | string;
/** Disables navigation items that haven't been visited. Defaults to false */
isStepVisitRequired?: boolean;
/** Callback function when a step in the navigation is clicked */
onNavByIndex?: WizardNavStepFunction;
/** Callback function after next button is clicked */
onNext?: WizardNavStepFunction;
/** Callback function after back button is clicked */
onBack?: WizardNavStepFunction;
/** Callback function to save at the end of the wizard, if not specified uses onClose */
onSave?: () => void;
/** Callback function to close the wizard */
onClose?: () => void;
}
export const Wizard = ({
children,
footer,
height,
width,
className,
header,
nav,
startIndex = 1,
isStepVisitRequired = false,
onNavByIndex,
onNext,
onBack,
onSave,
onClose,
...wrapperProps
}: WizardProps) => {
const [activeStepIndex, setActiveStepIndex] = React.useState(startIndex);
const initialSteps = buildSteps(children);
const goToNextStep = (steps: WizardControlStep[] = initialSteps) => {
const newStepIndex = steps.find(step => step.index > activeStepIndex && !step.isHidden && !isWizardParentStep(step))
?.index;
if (activeStepIndex >= steps.length || !newStepIndex) {
return onSave ? onSave() : onClose?.();
}
const currStep = isWizardParentStep(steps[activeStepIndex]) ? steps[activeStepIndex + 1] : steps[activeStepIndex];
const prevStep = steps[activeStepIndex - 1];
setActiveStepIndex(newStepIndex);
return onNext?.(normalizeNavStep(currStep), normalizeNavStep(prevStep));
};
const goToPrevStep = (steps: WizardControlStep[] = initialSteps) => {
const newStepIndex =
findLastIndex(
steps,
(step: WizardControlStep) => step.index < activeStepIndex && !step.isHidden && !isWizardParentStep(step)
) + 1;
const currStep = isWizardParentStep(steps[activeStepIndex - 2])
? steps[activeStepIndex - 3]
: steps[activeStepIndex - 2];
const prevStep = steps[activeStepIndex - 1];
setActiveStepIndex(newStepIndex);
return onBack?.(normalizeNavStep(currStep), normalizeNavStep(prevStep));
};
const goToStepByIndex = (steps: WizardControlStep[] = initialSteps, index: number) => {
const lastStepIndex = steps.length + 1;
// Handle index when out of bounds or hidden
if (index < 1) {
index = 1;
} else if (index > lastStepIndex) {
index = lastStepIndex;
}
const currStep = steps[index - 1];
const prevStep = steps[activeStepIndex - 1];
setActiveStepIndex(index);
return onNavByIndex?.(normalizeNavStep(currStep), normalizeNavStep(prevStep));
};
const goToStepById = (steps: WizardControlStep[] = initialSteps, id: number | string) => {
const step = steps.find(step => step.id === id);
const stepIndex = step?.index;
const lastStepIndex = steps.length + 1;
if (stepIndex > 0 && stepIndex < lastStepIndex && !step.isHidden) {
setActiveStepIndex(stepIndex);
}
};
const goToStepByName = (steps: WizardControlStep[] = initialSteps, name: string) => {
const step = steps.find(step => step.name === name);
const stepIndex = step?.index;
const lastStepIndex = steps.length + 1;
if (stepIndex > 0 && stepIndex < lastStepIndex && !step.isHidden) {
setActiveStepIndex(stepIndex);
}
};
return (
<WizardContextProvider
steps={initialSteps}
activeStepIndex={activeStepIndex}
footer={footer}
onNext={goToNextStep}
onBack={goToPrevStep}
onClose={onClose}
goToStepById={goToStepById}
goToStepByName={goToStepByName}
goToStepByIndex={goToStepByIndex}
>
<div
className={css(styles.wizard, className)}
style={{
...(height ? { height } : {}),
...(width ? { width } : {})
}}
{...wrapperProps}
>
{header}
<WizardInternal nav={nav} isStepVisitRequired={isStepVisitRequired} />
</div>
</WizardContextProvider>
);
};
const WizardInternal = ({ nav, isStepVisitRequired }: Pick<WizardProps, 'nav' | 'isStepVisitRequired'>) => {
const { activeStep, steps, footer, goToStepByIndex } = useWizardContext();
const [isNavExpanded, setIsNavExpanded] = React.useState(false);
const wizardNav = React.useMemo(() => {
if (isCustomWizardNav(nav)) {
return typeof nav === 'function' ? nav(isNavExpanded, steps, activeStep, goToStepByIndex) : nav;
}
return <WizardNavInternal nav={nav} isNavExpanded={isNavExpanded} isStepVisitRequired={isStepVisitRequired} />;
}, [activeStep, isStepVisitRequired, goToStepByIndex, isNavExpanded, nav, steps]);
return (
<WizardToggle
nav={wizardNav}
footer={footer}
steps={steps}
activeStep={activeStep}
isNavExpanded={isNavExpanded}
toggleNavExpanded={() => setIsNavExpanded(prevIsExpanded => !prevIsExpanded)}
/>
);
};
Wizard.displayName = 'Wizard';