Skip to content

Commit

Permalink
add new stepper component
Browse files Browse the repository at this point in the history
jay-deshmukh committed Sep 27, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
1 parent f9582e8 commit db8d0de
Showing 15 changed files with 380 additions and 10 deletions.
2 changes: 1 addition & 1 deletion lib/index.css

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion lib/index.css.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion lib/index.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion lib/index.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion lib/tyk-ui.css

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion lib/tyk-ui.css.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion lib/tyk-ui.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion lib/tyk-ui.js.map

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/common/css/components.css
Original file line number Diff line number Diff line change
@@ -24,6 +24,7 @@
@import '../../components/Tabs/Tabs.css';
@import '../../components/Toast/Toast.css';
@import '../../components/Tooltip/Tooltip.css';
@import '../../components/Stepper/index.css';

/* -- Form Components */
@import '../../form/components/Combobox2/Combobox.css';
33 changes: 33 additions & 0 deletions src/components/Stepper/Readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
```js

const DefaultStepContent = ({ title }) => (
<div>
<h4>{title}</h4>
<p>Hello {title}</p>
</div>
);

const steps = [
{
title: 'Step 1',
description: 'This is the first step',
component: <DefaultStepContent title="Step 1" />,
},
{
title: 'Step 2',
description: 'This is the second step',
component: <DefaultStepContent title="Step 2" />,
},
{
title: 'Step 3',
description: 'This is the third step',
component: <DefaultStepContent title="Step 3" />,
},
];

<Stepper
steps={steps}
onFinish={() => console.log("LAST STEP")}
onStepChange={(s) => console.log("STEP CHANGED", s)}
/>
```
175 changes: 175 additions & 0 deletions src/components/Stepper/Stepper.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
import React from "react";
import Stepper from "./index";

describe("Stepper", () => {
const steps = [
{
title: "Step 1",
description: "Description 1",
component: <div>Step 1 Content</div>,
},
{
title: "Step 2",
description: "Description 2",
component: <div>Step 2 Content</div>,
},
{
title: "Step 3",
description: "Description 3",
component: <div>Step 3 Content</div>,
},
];

const selectors = {
stepperContainer: ".Stepper-container",
stepsContainer: ".steps-container",
step: ".step",
activeStep: ".step.active",
completedStep: ".step.completed",
stepIndicator: ".step-indicator",
stepNumber: ".step-number",
stepLine: ".step-line",
stepContent: ".step-content",
stepComponent: ".step-component",
navigationButtons: ".navigation-buttons",
backButton: 'button:contains("Back")',
continueButton: 'button:contains("Continue")',
finishButton: 'button:contains("Finish")',
};

it("renders the component with the correct number of steps", () => {
cy.mount(<Stepper steps={steps} />)
.get(selectors.stepperContainer)
.should("exist")
.get(selectors.stepsContainer)
.should("exist")
.get(selectors.step)
.should("have.length", steps.length);
});

it("displays the first step as active by default", () => {
cy.mount(<Stepper steps={steps} />)
.get(selectors.activeStep)
.should("have.length", 1)
.get(selectors.activeStep)
.find("h3")
.should("contain", steps[0].title);
});

it("starts with an initial step if specified", () => {
const stepsWithInitial = [
...steps.slice(0, 2),
{ ...steps[2], isInitial: true },
];
cy.mount(<Stepper steps={stepsWithInitial} />)
.get(selectors.activeStep)
.find("h3")
.should("contain", steps[2].title);
});

it("does not show the Back button on the first step", () => {
cy.mount(<Stepper steps={steps} />)
.get(selectors.backButton)
.should("not.exist");
});

it("moves to the next step when Continue is clicked", () => {
cy.mount(<Stepper steps={steps} />)
.get(selectors.continueButton)
.click()
.get(selectors.activeStep)
.find("h3")
.should("contain", steps[1].title)
.get(selectors.completedStep)
.should("have.length", 1);
});

it("moves to the previous step when Back is clicked", () => {
cy.mount(<Stepper steps={steps} />)
.get(selectors.continueButton)
.click()
.get(selectors.backButton)
.click()
.get(selectors.activeStep)
.find("h3")
.should("contain", steps[0].title)
.get(selectors.completedStep)
.should("have.length", 0);
});

it("shows Finish button on the last step", () => {
cy.mount(<Stepper steps={steps} />)
.get(selectors.continueButton)
.click()
.get(selectors.continueButton)
.click()
.get(selectors.finishButton)
.should("exist");
});

it("calls onFinish when Finish button is clicked", () => {
const onFinish = cy.stub().as("onFinish");
cy.mount(<Stepper steps={steps} onFinish={onFinish} />)
.get(selectors.continueButton)
.click()
.get(selectors.continueButton)
.click()
.get(selectors.finishButton)
.click()
.get("@onFinish")
.should("have.been.called");
});

it("calls onStepChange when moving between steps", () => {
const onStepChange = cy.stub().as("onStepChange");
cy.mount(<Stepper steps={steps} onStepChange={onStepChange} />)
.get(selectors.continueButton)
.click()
.get("@onStepChange")
.should("have.been.calledWith", steps[1])
.get(selectors.backButton)
.click()
.get("@onStepChange")
.should("have.been.calledWith", steps[0]);
});

it("disables Finish button when isDisableFinish is true", () => {
cy.mount(<Stepper steps={steps} isDisableFinish={true} />)
.get(selectors.continueButton)
.click()
.get(selectors.continueButton)
.click()
.get(selectors.finishButton)
.should("be.disabled");
});

it("renders the correct step component for the active step", () => {
cy.mount(<Stepper steps={steps} />)
.get(selectors.stepComponent)
.contains("Step 1 Content")
.get(selectors.continueButton)
.click()
.get(selectors.stepComponent)
.contains("Step 2 Content");
});

it("renders step lines between steps", () => {
cy.mount(<Stepper steps={steps} />)
.get(selectors.stepLine)
.should("have.length", steps.length - 1);
});

it("displays step titles and descriptions", () => {
cy.mount(<Stepper steps={steps} />);
steps.forEach((step, index) => {
cy.get(selectors.stepContent)
.eq(index)
.find("h3")
.should("contain", step.title);
cy.get(selectors.stepContent)
.eq(index)
.find("p")
.should("contain", step.description);
});
});
});
75 changes: 75 additions & 0 deletions src/components/Stepper/index.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
.wizard-container {
max-width: 600px;
margin: 0 auto;
padding: 20px;
}

.steps-container {
display: flex;
flex-direction: column;
}

.step {
display: flex;
margin-bottom: 20px;

}

.step-indicator {
display: flex;
flex-direction: column;
align-items: center;
margin-right: 20px;
}

.step-number {
/* background-color: var(--color-secondary-dark); */
width: 30px;
height: 30px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 10px;
border: 1px solid var(--color-secondary-dark);
}

.step-line {
width: 1px;
flex-grow: 1;
background-color: var(--color-secondary-dark);
}

.step.active .step-number {
/* background-color: var(--color-primary-base); */
/* color:var(--color-secondary-extra-dark); */
}

.step.completed .step-number {
/* background-color: var(--color-success-base); */
color: var(--color-success-base);
border-color: var(--color-success-base);
}

.step-content {
flex-grow: 1;
}

.step-content h3 {
margin: 0 0 5px 0;
}

.step-content p {
margin: 0 0 10px 0;
}

.step-component {
margin-top: 10px;
padding: 15px;
}

.navigation-buttons {
display: flex;
justify-content: right;
margin-top: 20px;
}
85 changes: 85 additions & 0 deletions src/components/Stepper/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import React, { useState, useEffect } from "react";
import Button from "../Button";
import Icon from "../Icon";

const Stepper = ({
steps,
isDisableFinish = false,
onFinish = () => {},
onStepChange = () => {},
finishBtnText = "Finish",
}) => {
const [currentStep, setCurrentStep] = useState(0);

useEffect(() => {
const initialStep = steps.findIndex((step) => step.isInitial);
setCurrentStep(initialStep !== -1 ? initialStep : 0);
}, [steps]);

const handleNext = () => {
if (currentStep < steps.length - 1) {
const nextStep = currentStep + 1;
setCurrentStep(nextStep);
onStepChange(steps[nextStep]);
}
};

const handleBack = () => {
if (currentStep > 0) {
const prevStep = currentStep - 1;
setCurrentStep(prevStep);
onStepChange(steps[prevStep]);
}
};

const isLastStep = currentStep === steps.length - 1;

return (
<div className="Stepper-container">
<div className="steps-container">
{steps.map((step, index) => {
const isStepComplete = index < currentStep;
return (
<div
key={index}
className={`step ${index === currentStep ? "active" : ""} ${
isStepComplete ? "completed" : ""
}`}
>
<div className="step-indicator">
<div className={`step-number`}>
{isStepComplete ? <Icon type="check" /> : index + 1}
</div>
{index < steps.length - 1 && <div className="step-line"></div>}
</div>
<div className="step-content">
<h3>{step.title}</h3>
<p>{step.description}</p>
{index === currentStep && (
<div className="step-component">{step.component}</div>
)}
</div>
</div>
);
})}
</div>
<div className="navigation-buttons">
{currentStep >= 1 && (
<Button theme="secondary" onClick={handleBack}>
Back
</Button>
)}
&nbsp;
<Button
theme="primary"
onClick={isLastStep ? onFinish : handleNext}
disabled={isDisableFinish && isLastStep}
>
{isLastStep ? finishBtnText : "Continue"}
</Button>
</div>
</div>
);
};

export default Stepper;
1 change: 1 addition & 0 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -28,6 +28,7 @@ export { default as TextEllipsis } from './components/TextEllipsis';
export { default as toast } from './components/Toast';
export { default as Tooltip } from './components/Tooltip';
export { default as Table } from './components/Table';
export { default as Stepper } from './components/Stepper';

// -- Layout
export { default as Column } from './layout/Column';

0 comments on commit db8d0de

Please sign in to comment.