@indico-data/design-system 2.58.2 → 2.59.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/components/index.d.ts +1 -0
- package/lib/components/stepper/Stepper.d.ts +2 -0
- package/lib/components/stepper/Stepper.stories.d.ts +9 -0
- package/lib/components/stepper/__tests__/Stepper.tests.d.ts +1 -0
- package/lib/components/stepper/components/BackNavigation.d.ts +6 -0
- package/lib/components/stepper/components/Legend.d.ts +2 -0
- package/lib/components/stepper/components/NextNavigation.d.ts +8 -0
- package/lib/components/stepper/examples/MixedExample.d.ts +1 -0
- package/lib/components/stepper/examples/OptionalStepsExample.d.ts +1 -0
- package/lib/components/stepper/examples/RequiredStepsExample.d.ts +1 -0
- package/lib/components/stepper/examples/commonExample/CommonExample.d.ts +1 -0
- package/lib/components/stepper/examples/commonExample/steps/StepOne.d.ts +3 -0
- package/lib/components/stepper/examples/commonExample/steps/StepThree.d.ts +3 -0
- package/lib/components/stepper/examples/commonExample/steps/StepTwo.d.ts +3 -0
- package/lib/components/stepper/examples/constants.d.ts +61 -0
- package/lib/components/stepper/index.d.ts +1 -0
- package/lib/components/stepper/types.d.ts +24 -0
- package/lib/index.css +100 -2
- package/lib/index.d.ts +23 -2
- package/lib/index.esm.css +100 -2
- package/lib/index.esm.js +95 -29
- package/lib/index.esm.js.map +1 -1
- package/lib/index.js +94 -27
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/index.ts +1 -0
- package/src/components/stepper/Stepper.mdx +140 -0
- package/src/components/stepper/Stepper.stories.tsx +196 -0
- package/src/components/stepper/Stepper.tsx +85 -0
- package/src/components/stepper/__tests__/Stepper.tests.tsx +213 -0
- package/src/components/stepper/components/BackNavigation.tsx +22 -0
- package/src/components/stepper/components/Legend.tsx +66 -0
- package/src/components/stepper/components/NextNavigation.tsx +38 -0
- package/src/components/stepper/examples/MixedExample.tsx +140 -0
- package/src/components/stepper/examples/OptionalStepsExample.tsx +139 -0
- package/src/components/stepper/examples/RequiredStepsExample.tsx +158 -0
- package/src/components/stepper/examples/commonExample/CommonExample.tsx +115 -0
- package/src/components/stepper/examples/commonExample/steps/StepOne.tsx +57 -0
- package/src/components/stepper/examples/commonExample/steps/StepThree.tsx +56 -0
- package/src/components/stepper/examples/commonExample/steps/StepTwo.tsx +52 -0
- package/src/components/stepper/examples/constants.ts +168 -0
- package/src/components/stepper/index.ts +1 -0
- package/src/components/stepper/styles/Stepper.scss +131 -0
- package/src/components/stepper/types.ts +27 -0
- package/src/components/tanstackTable/components/TableBody/TableBody.tsx +2 -1
- package/src/components/tanstackTable/styles/table.scss +2 -2
- package/src/components/toast/Toast.mdx +1 -1
- package/src/components/toast/Toast.stories.tsx +1 -1
- package/src/index.ts +4 -2
- package/src/styles/index.scss +1 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import classNames from 'classnames';
|
|
2
|
+
import { Icon } from '../../icons/Icon';
|
|
3
|
+
import { Col, Row } from '../../grid/';
|
|
4
|
+
import { Button } from '../../button';
|
|
5
|
+
import { StepperLegendProps } from '../types';
|
|
6
|
+
|
|
7
|
+
export const Legend = ({ currentStep, steps, onStepClick }: StepperLegendProps) => {
|
|
8
|
+
const totalSteps = steps?.length || 0;
|
|
9
|
+
return (
|
|
10
|
+
<div className="stepper-legend">
|
|
11
|
+
{steps?.map((step, index) => {
|
|
12
|
+
const isCompleted = steps[index].isCompleted;
|
|
13
|
+
const isCurrent = index === currentStep;
|
|
14
|
+
const isSidebarEnabled = step.isSidebarEnabled;
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<div key={index}>
|
|
18
|
+
<Row nogutter align="center">
|
|
19
|
+
<Col xs="content">
|
|
20
|
+
<div
|
|
21
|
+
className={classNames('stepper-legend-circle', {
|
|
22
|
+
completed: isCompleted,
|
|
23
|
+
current: isCurrent,
|
|
24
|
+
})}
|
|
25
|
+
>
|
|
26
|
+
{isCompleted ? <Icon name="check" /> : index + 1}
|
|
27
|
+
</div>
|
|
28
|
+
</Col>
|
|
29
|
+
<Col>
|
|
30
|
+
<div
|
|
31
|
+
className={classNames('stepper-legend-step', {
|
|
32
|
+
'stepper-legend-step--current-step': isCurrent,
|
|
33
|
+
'stepper-legend-step--completed-step': isCompleted,
|
|
34
|
+
'stepper-legend-step--disabled-step': !isSidebarEnabled,
|
|
35
|
+
})}
|
|
36
|
+
>
|
|
37
|
+
<Button
|
|
38
|
+
data-testid={`stepper-legend-step-${index}`}
|
|
39
|
+
onClick={() => onStepClick(index)}
|
|
40
|
+
isDisabled={!isSidebarEnabled}
|
|
41
|
+
ariaLabel={step.label}
|
|
42
|
+
size="sm"
|
|
43
|
+
variant="link"
|
|
44
|
+
>
|
|
45
|
+
{step.label}
|
|
46
|
+
</Button>
|
|
47
|
+
</div>
|
|
48
|
+
</Col>
|
|
49
|
+
</Row>
|
|
50
|
+
{index !== totalSteps - 1 && (
|
|
51
|
+
<Row>
|
|
52
|
+
<Col>
|
|
53
|
+
<div
|
|
54
|
+
className={classNames('stepper-legend-line', {
|
|
55
|
+
'stepper-legend-line--completed': isCompleted,
|
|
56
|
+
})}
|
|
57
|
+
></div>
|
|
58
|
+
</Col>
|
|
59
|
+
</Row>
|
|
60
|
+
)}
|
|
61
|
+
</div>
|
|
62
|
+
);
|
|
63
|
+
})}
|
|
64
|
+
</div>
|
|
65
|
+
);
|
|
66
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Button } from '../../button';
|
|
2
|
+
type Props = {
|
|
3
|
+
isLastStep: boolean;
|
|
4
|
+
onNextClick: () => void;
|
|
5
|
+
onFinishClick: () => void;
|
|
6
|
+
isDisabled: boolean;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export const NextNavigation = ({ isLastStep, onNextClick, onFinishClick, isDisabled }: Props) => {
|
|
10
|
+
return (
|
|
11
|
+
<div className="stepper-navigation">
|
|
12
|
+
{!isLastStep ? (
|
|
13
|
+
<div className="stepper-navigation-next">
|
|
14
|
+
<Button
|
|
15
|
+
data-testid="stepper-next-button"
|
|
16
|
+
iconRight="fa-arrow-right"
|
|
17
|
+
ariaLabel="Next Step"
|
|
18
|
+
onClick={onNextClick}
|
|
19
|
+
isDisabled={isDisabled}
|
|
20
|
+
>
|
|
21
|
+
Next Step
|
|
22
|
+
</Button>
|
|
23
|
+
</div>
|
|
24
|
+
) : (
|
|
25
|
+
<div className="stepper-navigation-finish">
|
|
26
|
+
<Button
|
|
27
|
+
data-testid="stepper-finish-button"
|
|
28
|
+
ariaLabel="Finish"
|
|
29
|
+
onClick={onFinishClick}
|
|
30
|
+
isDisabled={isDisabled}
|
|
31
|
+
>
|
|
32
|
+
Finish
|
|
33
|
+
</Button>
|
|
34
|
+
</div>
|
|
35
|
+
)}
|
|
36
|
+
</div>
|
|
37
|
+
);
|
|
38
|
+
};
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { Stepper } from '../Stepper';
|
|
3
|
+
import { Button } from '../../button';
|
|
4
|
+
import { MIXED_EXAMPLE_STEPS, STEP_CONTENT_DATA, INFO_BOX_STYLE } from './constants';
|
|
5
|
+
|
|
6
|
+
export const MixedExample = () => {
|
|
7
|
+
// Value of the current step.
|
|
8
|
+
const [currentStep, setCurrentStep] = useState(0);
|
|
9
|
+
|
|
10
|
+
// Array of all steps using the MIXED_EXAMPLE_STEPS constant
|
|
11
|
+
const [steps, setSteps] = useState(
|
|
12
|
+
MIXED_EXAMPLE_STEPS.map((step, index) => ({
|
|
13
|
+
...step,
|
|
14
|
+
label: `Step ${index + 1}: ${
|
|
15
|
+
index === 0
|
|
16
|
+
? 'Start'
|
|
17
|
+
: index === MIXED_EXAMPLE_STEPS.length - 1
|
|
18
|
+
? 'Complete'
|
|
19
|
+
: index === 1
|
|
20
|
+
? 'Details'
|
|
21
|
+
: index === 2
|
|
22
|
+
? 'Review'
|
|
23
|
+
: 'Confirm'
|
|
24
|
+
}`,
|
|
25
|
+
// Only override isNextDisabled for non-optional steps that aren't completed yet
|
|
26
|
+
isNextDisabled: !step.isOptional && !step.isCompleted,
|
|
27
|
+
})),
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
//=========Navigation Functions=========
|
|
31
|
+
|
|
32
|
+
// This function handles what happens when the user clicks the back button.
|
|
33
|
+
const handleBackClick = () => {
|
|
34
|
+
setCurrentStep(currentStep - 1);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// This function handles what happens when the user clicks the next button. This is important because it allows you to navigate the side legend.
|
|
38
|
+
const handleNextClick = () => {
|
|
39
|
+
const currentStepData = steps[currentStep];
|
|
40
|
+
// if step is optional, then enable next step
|
|
41
|
+
if (currentStepData?.isOptional) {
|
|
42
|
+
setSteps(
|
|
43
|
+
steps.map((step, index) =>
|
|
44
|
+
index === currentStep + 1 ? { ...step, isNextDisabled: false } : step,
|
|
45
|
+
),
|
|
46
|
+
);
|
|
47
|
+
setCurrentStep(currentStep + 1);
|
|
48
|
+
// If the current step is completed and not optional, then enable the next step
|
|
49
|
+
} else if (currentStepData && currentStepData.isCompleted) {
|
|
50
|
+
setSteps(
|
|
51
|
+
steps.map((step, index) =>
|
|
52
|
+
index === currentStep + 1 ? { ...step, isNextDisabled: false } : step,
|
|
53
|
+
),
|
|
54
|
+
);
|
|
55
|
+
setCurrentStep(currentStep + 1);
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// This function allows the legend to navigate to a specific step
|
|
60
|
+
const handleStepClick = (step: number) => {
|
|
61
|
+
setCurrentStep(step);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
//=========Step Completion Functions=========
|
|
65
|
+
|
|
66
|
+
// This function handles what happens when the user finishes the last step
|
|
67
|
+
const handleFinishClick = () => {
|
|
68
|
+
alert('Finished!');
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// This function fires when the conditions for the current step are met.
|
|
72
|
+
const handleCompleteStep = (step: number) => {
|
|
73
|
+
setSteps(
|
|
74
|
+
steps.map((currentStep, index) =>
|
|
75
|
+
index === step ? { ...currentStep, isCompleted: true, isNextDisabled: false } : currentStep,
|
|
76
|
+
),
|
|
77
|
+
);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<>
|
|
82
|
+
<div style={INFO_BOX_STYLE}>
|
|
83
|
+
<h3>Mixed Steps Example (Default)</h3>
|
|
84
|
+
<p>This is the default stepper configuration with mixed step requirements:</p>
|
|
85
|
+
<ul>
|
|
86
|
+
<li>The first step is required and must be completed before proceeding</li>
|
|
87
|
+
<li>Steps 2-5 are optional and can be skipped</li>
|
|
88
|
+
<li>All steps are accessible from the sidebar at any time</li>
|
|
89
|
+
<li>The Next button is enabled for optional steps or completed required steps</li>
|
|
90
|
+
</ul>
|
|
91
|
+
<p>This provides a flexible experience while ensuring critical steps are completed.</p>
|
|
92
|
+
</div>
|
|
93
|
+
|
|
94
|
+
<Stepper
|
|
95
|
+
steps={steps}
|
|
96
|
+
currentStep={currentStep}
|
|
97
|
+
onBackClick={handleBackClick}
|
|
98
|
+
onNextClick={handleNextClick}
|
|
99
|
+
onFinishClick={handleFinishClick}
|
|
100
|
+
onStepClick={handleStepClick}
|
|
101
|
+
>
|
|
102
|
+
<div className="step" id={steps[0].id}>
|
|
103
|
+
<h3>{STEP_CONTENT_DATA.step1.title}</h3>
|
|
104
|
+
<p>{STEP_CONTENT_DATA.step1.content}</p>
|
|
105
|
+
<Button onClick={() => handleCompleteStep(0)} ariaLabel="Complete Step 1">
|
|
106
|
+
Press To Complete Step
|
|
107
|
+
</Button>
|
|
108
|
+
</div>
|
|
109
|
+
<div className="step" id={steps[1].id}>
|
|
110
|
+
<h3>{STEP_CONTENT_DATA.step2.title}</h3>
|
|
111
|
+
<p>{STEP_CONTENT_DATA.step2.content}</p>
|
|
112
|
+
<Button onClick={() => handleCompleteStep(1)} ariaLabel="Complete Step 2">
|
|
113
|
+
Press To Complete Step
|
|
114
|
+
</Button>
|
|
115
|
+
</div>
|
|
116
|
+
<div className="step" id={steps[2].id}>
|
|
117
|
+
<h3>{STEP_CONTENT_DATA.step3.title}</h3>
|
|
118
|
+
<p>{STEP_CONTENT_DATA.step3.content}</p>
|
|
119
|
+
<Button onClick={() => handleCompleteStep(2)} ariaLabel="Complete Step 3">
|
|
120
|
+
Press To Complete Step
|
|
121
|
+
</Button>
|
|
122
|
+
</div>
|
|
123
|
+
<div className="step" id={steps[3].id}>
|
|
124
|
+
<h3>{STEP_CONTENT_DATA.step4.title}</h3>
|
|
125
|
+
<p>{STEP_CONTENT_DATA.step4.content}</p>
|
|
126
|
+
<Button onClick={() => handleCompleteStep(3)} ariaLabel="Complete Step 4">
|
|
127
|
+
Press To Complete Step
|
|
128
|
+
</Button>
|
|
129
|
+
</div>
|
|
130
|
+
<div className="step" id={steps[4].id}>
|
|
131
|
+
<h3>{STEP_CONTENT_DATA.step5.title}</h3>
|
|
132
|
+
<p>{STEP_CONTENT_DATA.step5.content}</p>
|
|
133
|
+
<Button onClick={() => handleCompleteStep(4)} ariaLabel="Complete Step 5">
|
|
134
|
+
Press To Complete Step
|
|
135
|
+
</Button>
|
|
136
|
+
</div>
|
|
137
|
+
</Stepper>
|
|
138
|
+
</>
|
|
139
|
+
);
|
|
140
|
+
};
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { Stepper } from '../Stepper';
|
|
3
|
+
import { Button } from '../../button';
|
|
4
|
+
import { OPTIONAL_STEPS, INFO_BOX_STYLE, STATUS_INDICATOR_STYLE } from './constants';
|
|
5
|
+
|
|
6
|
+
export const OptionalStepsExample = () => {
|
|
7
|
+
const [currentStep, setCurrentStep] = useState(0);
|
|
8
|
+
const [steps, setSteps] = useState(
|
|
9
|
+
OPTIONAL_STEPS.map((step, index) => ({
|
|
10
|
+
...step,
|
|
11
|
+
label: `Step ${index + 1}: Optional`,
|
|
12
|
+
// Optional steps keep the default isNextDisabled: false from constants
|
|
13
|
+
})),
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
// Count completed steps for summary
|
|
17
|
+
const completedCount = steps.filter((step) => step.isCompleted).length;
|
|
18
|
+
|
|
19
|
+
// Handles back button click
|
|
20
|
+
const handleBackClick = () => {
|
|
21
|
+
setCurrentStep(Math.max(0, currentStep - 1));
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// Handles next button click - always proceeds regardless of completion
|
|
25
|
+
const handleNextClick = () => {
|
|
26
|
+
// Move to the next step without checking completion status
|
|
27
|
+
setCurrentStep(Math.min(steps.length - 1, currentStep + 1));
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// Handle sidebar navigation - always allows any step
|
|
31
|
+
const handleStepClick = (step: number) => {
|
|
32
|
+
setCurrentStep(step);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// When the user completes a step
|
|
36
|
+
const handleCompleteStep = (step: number) => {
|
|
37
|
+
setSteps(
|
|
38
|
+
steps.map((currentStep, index) =>
|
|
39
|
+
index === step ? { ...currentStep, isCompleted: true } : currentStep,
|
|
40
|
+
),
|
|
41
|
+
);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// Handle finish button click - no validation needed
|
|
45
|
+
const handleFinishClick = () => {
|
|
46
|
+
// Show summary of what was completed
|
|
47
|
+
alert(`Process finished! You completed ${completedCount} out of ${steps.length} steps.`);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// Status indicator to show optional status
|
|
51
|
+
const StepStatus = ({ isCompleted }: { isCompleted: boolean }) => (
|
|
52
|
+
<div className="step-status">
|
|
53
|
+
<div style={STATUS_INDICATOR_STYLE(isCompleted ? 'green' : 'orange')} />
|
|
54
|
+
{isCompleted ? 'Completed' : 'Optional - Not Completed'}
|
|
55
|
+
</div>
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<>
|
|
60
|
+
<div style={INFO_BOX_STYLE}>
|
|
61
|
+
<h3>Optional Steps Example</h3>
|
|
62
|
+
<p>In this example, all steps are optional. You can:</p>
|
|
63
|
+
<ul>
|
|
64
|
+
<li>Navigate freely between steps using the sidebar</li>
|
|
65
|
+
<li>Skip any step by clicking Next without completing it</li>
|
|
66
|
+
<li>Finish the process at any time regardless of completion status</li>
|
|
67
|
+
</ul>
|
|
68
|
+
<p>
|
|
69
|
+
<strong>Completed steps:</strong> {completedCount} of {steps.length}
|
|
70
|
+
</p>
|
|
71
|
+
</div>
|
|
72
|
+
|
|
73
|
+
<Stepper
|
|
74
|
+
steps={steps}
|
|
75
|
+
currentStep={currentStep}
|
|
76
|
+
onBackClick={handleBackClick}
|
|
77
|
+
onNextClick={handleNextClick}
|
|
78
|
+
onFinishClick={handleFinishClick}
|
|
79
|
+
onStepClick={handleStepClick}
|
|
80
|
+
>
|
|
81
|
+
<div className="step" id={steps[0].id}>
|
|
82
|
+
<h3>Step 1: Profile Preferences (Optional)</h3>
|
|
83
|
+
<p>Set your profile preferences if you want. You can always skip this step.</p>
|
|
84
|
+
<StepStatus isCompleted={steps[0].isCompleted} />
|
|
85
|
+
<div style={{ marginTop: '20px' }}>
|
|
86
|
+
<Button onClick={() => handleCompleteStep(0)} ariaLabel="Complete Step 1">
|
|
87
|
+
Complete Optional Step
|
|
88
|
+
</Button>
|
|
89
|
+
<div style={{ marginTop: '10px' }}>
|
|
90
|
+
<em>Or click Next to skip this step</em>
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
|
|
95
|
+
<div className="step" id={steps[1].id}>
|
|
96
|
+
<h3>Step 2: Additional Information (Optional)</h3>
|
|
97
|
+
<p>Provide additional information if you want. This step is completely optional.</p>
|
|
98
|
+
<StepStatus isCompleted={steps[1].isCompleted} />
|
|
99
|
+
<div style={{ marginTop: '20px' }}>
|
|
100
|
+
<Button onClick={() => handleCompleteStep(1)} ariaLabel="Complete Step 2">
|
|
101
|
+
Complete Optional Step
|
|
102
|
+
</Button>
|
|
103
|
+
<div style={{ marginTop: '10px' }}>
|
|
104
|
+
<em>Or click Next to skip this step</em>
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
</div>
|
|
108
|
+
|
|
109
|
+
<div className="step" id={steps[2].id}>
|
|
110
|
+
<h3>Step 3: Notification Preferences (Optional)</h3>
|
|
111
|
+
<p>Configure your notification preferences. You can always set these later.</p>
|
|
112
|
+
<StepStatus isCompleted={steps[2].isCompleted} />
|
|
113
|
+
<div style={{ marginTop: '20px' }}>
|
|
114
|
+
<Button onClick={() => handleCompleteStep(2)} ariaLabel="Complete Step 3">
|
|
115
|
+
Complete Optional Step
|
|
116
|
+
</Button>
|
|
117
|
+
<div style={{ marginTop: '10px' }}>
|
|
118
|
+
<em>Or click Next to skip this step</em>
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
|
|
123
|
+
<div className="step" id={steps[3].id}>
|
|
124
|
+
<h3>Step 4: Feedback (Optional)</h3>
|
|
125
|
+
<p>Share your feedback with us. This information is optional but helps us improve.</p>
|
|
126
|
+
<StepStatus isCompleted={steps[3].isCompleted} />
|
|
127
|
+
<div style={{ marginTop: '20px' }}>
|
|
128
|
+
<Button onClick={() => handleCompleteStep(3)} ariaLabel="Complete Step 4">
|
|
129
|
+
Complete Optional Step
|
|
130
|
+
</Button>
|
|
131
|
+
<div style={{ marginTop: '10px' }}>
|
|
132
|
+
<em>Or click Finish to complete without feedback</em>
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
136
|
+
</Stepper>
|
|
137
|
+
</>
|
|
138
|
+
);
|
|
139
|
+
};
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { Stepper } from '../Stepper';
|
|
3
|
+
import { Button } from '../../button';
|
|
4
|
+
import { REQUIRED_STEPS, INFO_BOX_STYLE, STATUS_INDICATOR_STYLE } from './constants';
|
|
5
|
+
|
|
6
|
+
export const RequiredStepsExample = () => {
|
|
7
|
+
const [currentStep, setCurrentStep] = useState(0);
|
|
8
|
+
const [steps, setSteps] = useState(
|
|
9
|
+
REQUIRED_STEPS.map((step, index) => ({
|
|
10
|
+
...step,
|
|
11
|
+
label: `Step ${index + 1}: Required`,
|
|
12
|
+
// For required steps, override to true initially until completed
|
|
13
|
+
isNextDisabled: true,
|
|
14
|
+
})),
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
// Handles back button click
|
|
18
|
+
const handleBackClick = () => {
|
|
19
|
+
setCurrentStep(currentStep - 1);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// Handles next button click - only proceeds if current step is completed
|
|
23
|
+
const handleNextClick = () => {
|
|
24
|
+
const currentStepData = steps[currentStep];
|
|
25
|
+
|
|
26
|
+
// Can only proceed if the current step is completed
|
|
27
|
+
if (currentStepData && currentStepData.isCompleted) {
|
|
28
|
+
// Enable the next step in the sidebar
|
|
29
|
+
setSteps(
|
|
30
|
+
steps.map((step, index) =>
|
|
31
|
+
index === currentStep + 1
|
|
32
|
+
? { ...step, isSidebarEnabled: true, isNextDisabled: false }
|
|
33
|
+
: step,
|
|
34
|
+
),
|
|
35
|
+
);
|
|
36
|
+
// Move to the next step
|
|
37
|
+
setCurrentStep(currentStep + 1);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// Handle sidebar navigation
|
|
42
|
+
const handleStepClick = (step: number) => {
|
|
43
|
+
// Only allow clicking on enabled steps in the sidebar
|
|
44
|
+
if (steps[step].isSidebarEnabled) {
|
|
45
|
+
setCurrentStep(step);
|
|
46
|
+
} else {
|
|
47
|
+
alert('You must complete the previous steps first.');
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// When the user completes a step
|
|
52
|
+
const handleCompleteStep = (step: number) => {
|
|
53
|
+
setSteps(
|
|
54
|
+
steps.map((currentStep, index) =>
|
|
55
|
+
index === step ? { ...currentStep, isCompleted: true, isNextDisabled: false } : currentStep,
|
|
56
|
+
),
|
|
57
|
+
);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// Handle finish button click
|
|
61
|
+
const handleFinishClick = () => {
|
|
62
|
+
// Check if all steps are completed
|
|
63
|
+
const allCompleted = steps.every((step) => step.isCompleted);
|
|
64
|
+
|
|
65
|
+
if (allCompleted) {
|
|
66
|
+
alert('Successfully completed all required steps!');
|
|
67
|
+
} else {
|
|
68
|
+
alert('You must complete all steps before finishing.');
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// Status indicator to show what's required for each step
|
|
73
|
+
const StepStatus = ({ isCompleted }: { isCompleted: boolean }) => (
|
|
74
|
+
<div className="step-status">
|
|
75
|
+
<div style={STATUS_INDICATOR_STYLE(isCompleted ? 'green' : 'red')} />
|
|
76
|
+
{isCompleted ? 'Completed' : 'Required - Not Completed'}
|
|
77
|
+
</div>
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<>
|
|
82
|
+
<div style={INFO_BOX_STYLE}>
|
|
83
|
+
<h3>Required Steps Example</h3>
|
|
84
|
+
<p>In this example, all steps are required with strict validation:</p>
|
|
85
|
+
<ul>
|
|
86
|
+
<li>Every step must be completed before proceeding to the next</li>
|
|
87
|
+
<li>Only completed steps and the current step are accessible in the sidebar</li>
|
|
88
|
+
<li>The Next button is disabled until the current step is completed</li>
|
|
89
|
+
<li>All steps must be completed before the process can be finished</li>
|
|
90
|
+
</ul>
|
|
91
|
+
<p>This enforces a structured workflow where all information must be provided.</p>
|
|
92
|
+
</div>
|
|
93
|
+
|
|
94
|
+
<Stepper
|
|
95
|
+
steps={steps}
|
|
96
|
+
currentStep={currentStep}
|
|
97
|
+
onBackClick={handleBackClick}
|
|
98
|
+
onNextClick={handleNextClick}
|
|
99
|
+
onFinishClick={handleFinishClick}
|
|
100
|
+
onStepClick={handleStepClick}
|
|
101
|
+
>
|
|
102
|
+
<div className="step" id={steps[0].id}>
|
|
103
|
+
<h3>Step 1: Personal Information</h3>
|
|
104
|
+
<p>All fields in this form are required before you can proceed.</p>
|
|
105
|
+
<StepStatus isCompleted={steps[0].isCompleted} />
|
|
106
|
+
<div style={{ marginTop: '20px' }}>
|
|
107
|
+
<Button onClick={() => handleCompleteStep(0)} ariaLabel="Complete Step 1">
|
|
108
|
+
Complete Required Step
|
|
109
|
+
</Button>
|
|
110
|
+
</div>
|
|
111
|
+
</div>
|
|
112
|
+
|
|
113
|
+
<div className="step" id={steps[1].id}>
|
|
114
|
+
<h3>Step 2: Contact Details</h3>
|
|
115
|
+
<p>All contact information must be provided to continue.</p>
|
|
116
|
+
<StepStatus isCompleted={steps[1].isCompleted} />
|
|
117
|
+
<div style={{ marginTop: '20px' }}>
|
|
118
|
+
<Button onClick={() => handleCompleteStep(1)} ariaLabel="Complete Step 2">
|
|
119
|
+
Complete Required Step
|
|
120
|
+
</Button>
|
|
121
|
+
</div>
|
|
122
|
+
</div>
|
|
123
|
+
|
|
124
|
+
<div className="step" id={steps[2].id}>
|
|
125
|
+
<h3>Step 3: Security Questions</h3>
|
|
126
|
+
<p>You must set up security questions to protect your account.</p>
|
|
127
|
+
<StepStatus isCompleted={steps[2].isCompleted} />
|
|
128
|
+
<div style={{ marginTop: '20px' }}>
|
|
129
|
+
<Button onClick={() => handleCompleteStep(2)} ariaLabel="Complete Step 3">
|
|
130
|
+
Complete Required Step
|
|
131
|
+
</Button>
|
|
132
|
+
</div>
|
|
133
|
+
</div>
|
|
134
|
+
|
|
135
|
+
<div className="step" id={steps[3].id}>
|
|
136
|
+
<h3>Step 4: Terms & Conditions</h3>
|
|
137
|
+
<p>You must agree to the terms and conditions to complete registration.</p>
|
|
138
|
+
<StepStatus isCompleted={steps[3].isCompleted} />
|
|
139
|
+
<div style={{ marginTop: '20px' }}>
|
|
140
|
+
<Button onClick={() => handleCompleteStep(3)} ariaLabel="Complete Step 4">
|
|
141
|
+
Complete Required Step
|
|
142
|
+
</Button>
|
|
143
|
+
</div>
|
|
144
|
+
</div>
|
|
145
|
+
<div className="step" id={steps[4].id}>
|
|
146
|
+
<h3>Step 4: Terms & Conditions</h3>
|
|
147
|
+
<p>You must agree to the terms and conditions to complete registration.</p>
|
|
148
|
+
<StepStatus isCompleted={steps[4].isCompleted} />
|
|
149
|
+
<div style={{ marginTop: '20px' }}>
|
|
150
|
+
<Button onClick={() => handleCompleteStep(4)} ariaLabel="Complete Step 4">
|
|
151
|
+
Complete Required Step
|
|
152
|
+
</Button>
|
|
153
|
+
</div>
|
|
154
|
+
</div>
|
|
155
|
+
</Stepper>
|
|
156
|
+
</>
|
|
157
|
+
);
|
|
158
|
+
};
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { Stepper } from '../../Stepper';
|
|
3
|
+
import { StepOne } from './steps/StepOne';
|
|
4
|
+
import { StepTwo } from './steps/StepTwo';
|
|
5
|
+
import { StepThree } from './steps/StepThree';
|
|
6
|
+
|
|
7
|
+
export const CommonExample = () => {
|
|
8
|
+
const exampleSteps = [
|
|
9
|
+
{
|
|
10
|
+
id: 'step1',
|
|
11
|
+
label: 'Application Information',
|
|
12
|
+
isCompleted: false,
|
|
13
|
+
isOptional: false,
|
|
14
|
+
isSidebarEnabled: true,
|
|
15
|
+
isNextDisabled: false,
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
id: 'step2',
|
|
19
|
+
label: 'Agent Information',
|
|
20
|
+
isCompleted: false,
|
|
21
|
+
isOptional: false,
|
|
22
|
+
isSidebarEnabled: false,
|
|
23
|
+
isNextDisabled: true,
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
id: 'step3',
|
|
27
|
+
label: 'User Information',
|
|
28
|
+
isCompleted: false,
|
|
29
|
+
isOptional: false,
|
|
30
|
+
isSidebarEnabled: false,
|
|
31
|
+
isNextDisabled: true,
|
|
32
|
+
},
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
const [currentStep, setCurrentStep] = useState(0);
|
|
36
|
+
const [steps, setSteps] = useState(exampleSteps);
|
|
37
|
+
|
|
38
|
+
const handleBackClick = () => {
|
|
39
|
+
setCurrentStep(currentStep - 1);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// Handles next button click - only proceeds if current step is completed
|
|
43
|
+
const handleNextClick = () => {
|
|
44
|
+
const currentStepData = steps[currentStep];
|
|
45
|
+
if (currentStepData && currentStepData.isCompleted) {
|
|
46
|
+
setSteps(
|
|
47
|
+
steps.map((step, index) =>
|
|
48
|
+
index === currentStep + 1
|
|
49
|
+
? { ...step, isSidebarEnabled: true, isNextDisabled: false }
|
|
50
|
+
: step,
|
|
51
|
+
),
|
|
52
|
+
);
|
|
53
|
+
setCurrentStep(currentStep + 1);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const handleSidebarNavigationItemClick = (step: number) => {
|
|
58
|
+
// Only allow clicking on enabled steps in the sidebar
|
|
59
|
+
if (steps[step].isSidebarEnabled) {
|
|
60
|
+
setCurrentStep(step);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const handleOnStepCompletion = (step: number) => {
|
|
65
|
+
setSteps(
|
|
66
|
+
steps.map((currentStep, index) => {
|
|
67
|
+
if (index === step) {
|
|
68
|
+
// Mark current step as completed
|
|
69
|
+
return { ...currentStep, isCompleted: true, isNextDisabled: false };
|
|
70
|
+
} else if (index === step + 1) {
|
|
71
|
+
// Enable the next step in the sidebar
|
|
72
|
+
return { ...currentStep, isSidebarEnabled: true };
|
|
73
|
+
} else {
|
|
74
|
+
return currentStep;
|
|
75
|
+
}
|
|
76
|
+
}),
|
|
77
|
+
);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const handleCompleteStepperClick = () => {
|
|
81
|
+
// if all steps are completed, then show a success message
|
|
82
|
+
if (steps.every((step) => step.isCompleted)) {
|
|
83
|
+
alert('Successfully completed all required steps!');
|
|
84
|
+
} else {
|
|
85
|
+
alert('You must complete all steps before finishing.');
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<Stepper
|
|
91
|
+
steps={steps}
|
|
92
|
+
currentStep={currentStep}
|
|
93
|
+
legendHeader={
|
|
94
|
+
<div>
|
|
95
|
+
<h1>Publish Agent To Gallery</h1>
|
|
96
|
+
<p className="subtitle-2 color-tertiary-200 mt-2 mb-6">
|
|
97
|
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
|
|
98
|
+
incididunt ut labore et dolore.
|
|
99
|
+
</p>
|
|
100
|
+
</div>
|
|
101
|
+
}
|
|
102
|
+
legendFooter={
|
|
103
|
+
<p className="subtitle-2 color-tertiary-200 mt-6">Example of a legend footer</p>
|
|
104
|
+
}
|
|
105
|
+
onBackClick={handleBackClick}
|
|
106
|
+
onNextClick={handleNextClick}
|
|
107
|
+
onFinishClick={handleCompleteStepperClick}
|
|
108
|
+
onStepClick={handleSidebarNavigationItemClick}
|
|
109
|
+
>
|
|
110
|
+
<StepOne onCompletion={() => handleOnStepCompletion(0)} />
|
|
111
|
+
<StepTwo onCompletion={() => handleOnStepCompletion(1)} />
|
|
112
|
+
<StepThree onCompletion={() => handleOnStepCompletion(2)} />
|
|
113
|
+
</Stepper>
|
|
114
|
+
);
|
|
115
|
+
};
|