-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Showing
20 changed files
with
486 additions
and
9 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
Large diffs are not rendered by default.
Oops, something went wrong.
Large diffs are not rendered by default.
Oops, something went wrong.
Large diffs are not rendered by default.
Oops, something went wrong.
Large diffs are not rendered by default.
Oops, something went wrong.
Large diffs are not rendered by default.
Oops, something went wrong.
Large diffs are not rendered by default.
Oops, something went wrong.
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
|
||
```jsx | ||
import React from 'react'; | ||
|
||
const ExampleStepper = () => { | ||
const handleFinish = () => { | ||
console.log('All steps completed!'); | ||
}; | ||
|
||
const validateStep = (stepId) => { | ||
return true; | ||
}; | ||
|
||
return ( | ||
<Stepper | ||
onFinish={handleFinish} | ||
stepValidator={validateStep} | ||
stepErrMessage="Please complete all required fields before proceeding." | ||
> | ||
<Stepper.Step id="personal-info" title="Step-1" description="Enter your name"> | ||
<input type="text" placeholder="Full Name" /> | ||
</Stepper.Step> | ||
|
||
<Stepper.Step id="address" title="Step-2" description="Provide your address"> | ||
<input type="text" placeholder="Street Address" /> | ||
</Stepper.Step> | ||
|
||
<Stepper.Step id="review" title="Step-3" description="Review your information"> | ||
<p>Please review your entered information before submitting.</p> | ||
</Stepper.Step> | ||
</Stepper> | ||
); | ||
}; | ||
|
||
<ExampleStepper /> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
import React from "react"; | ||
import Stepper from "./index"; | ||
|
||
function StepperComponent({ validator, onFinish: mockOnFinish }) { | ||
const onFinish = () => { | ||
console.log("Stepper finished"); | ||
}; | ||
|
||
const stepValidator = (stepId) => { | ||
return stepId !== "step2"; | ||
}; | ||
|
||
return ( | ||
<div className="stepper-wrapper"> | ||
<Stepper | ||
onFinish={mockOnFinish || onFinish} | ||
stepValidator={validator || stepValidator} | ||
stepErrMessage="Validation failed" | ||
> | ||
<Stepper.Step id="step1" title="Step 1" description="First step"> | ||
Step 1 content | ||
</Stepper.Step> | ||
<Stepper.Step id="step2" title="Step 2" description="Second step"> | ||
Step 2 content | ||
</Stepper.Step> | ||
<Stepper.Step id="step3" title="Step 3" description="Third step"> | ||
Step 3 content | ||
</Stepper.Step> | ||
</Stepper> | ||
</div> | ||
); | ||
} | ||
|
||
describe("Stepper", () => { | ||
it("should render the initial step correctly", () => { | ||
cy.mount(<StepperComponent />) | ||
.get(".step-container") | ||
.should("have.length", 3) | ||
.get(".step-number.active") | ||
.should("contain", "1") | ||
.get(".step-title") | ||
.first() | ||
.should("have.text", "Step 1"); | ||
}); | ||
|
||
it("should navigate to the next step when Continue is clicked", () => { | ||
cy.mount(<StepperComponent />) | ||
.get(".stepper-buttons button") | ||
.contains("Continue") | ||
.click() | ||
.get(".step-number.active") | ||
.should("contain", "2"); | ||
}); | ||
|
||
it("should show error message when validation fails", () => { | ||
cy.mount(<StepperComponent />) | ||
.get(".stepper-buttons button") | ||
.contains("Continue") | ||
.click() | ||
.get(".stepper-buttons button") | ||
.contains("Continue") | ||
.click() | ||
.get(".error-message") | ||
.should("be.visible") | ||
.and("have.text", "Validation failed"); | ||
}); | ||
|
||
it("should allow navigation back to previous step", () => { | ||
cy.mount(<StepperComponent />) | ||
.get(".stepper-buttons button") | ||
.contains("Continue") | ||
.click() | ||
.get(".stepper-buttons button") | ||
.contains("Back") | ||
.click() | ||
.get(".step-number.active") | ||
.should("contain", "1"); | ||
}); | ||
|
||
it("should mark completed steps correctly", () => { | ||
cy.mount(<StepperComponent />) | ||
.get(".stepper-buttons button") | ||
.contains("Continue") | ||
.click() | ||
.get(".step-number.completed") | ||
.should("have.length", 1) | ||
.and("contain", "1"); | ||
}); | ||
|
||
it("should show Finish button on last step", () => { | ||
const onFinish = cy.stub().as("onFinish"); | ||
cy.mount(<StepperComponent validator={() => true} onFinish={onFinish} />) | ||
.get(".stepper-buttons button") | ||
.contains("Continue") | ||
.click() | ||
.get(".stepper-buttons button") | ||
.contains("Continue") | ||
.click() | ||
.get(".stepper-buttons button") | ||
.contains("Finish") | ||
.should("be.visible") | ||
.click() | ||
.get('button:contains("Finish")') | ||
.click() | ||
.get("@onFinish") | ||
.should("have.been.called"); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import React, { createContext, useContext } from "react"; | ||
|
||
const StepperContext = createContext(); | ||
|
||
export const StepperProvider = StepperContext.Provider; | ||
|
||
export const useStepper = () => { | ||
const context = useContext(StepperContext); | ||
if (!context) { | ||
throw new Error("useStepper must be used within a Stepper component"); | ||
} | ||
return context; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
import React, { useState, useMemo } from "react"; | ||
import PropTypes from 'prop-types'; | ||
import { StepperProvider } from "./StepperContext"; | ||
import StepList from "./js/StepList"; | ||
import StepperButtons from "./js/StepperButtons"; | ||
import "./stepper.css"; | ||
|
||
export const Stepper = ({ | ||
children, | ||
onFinish, | ||
stepValidator, | ||
stepErrMessage = "ERROR", | ||
}) => { | ||
const [activeStep, setActiveStep] = useState(0); | ||
const [errors, setErrors] = useState({}); | ||
const [validationAttempted, setValidationAttempted] = useState(false); | ||
|
||
const steps = useMemo(() => { | ||
return React.Children.toArray(children).filter( | ||
(child) => child.type.name === "Step" | ||
); | ||
}, [children]); | ||
|
||
const contextValue = { | ||
activeStep, | ||
setActiveStep, | ||
errors, | ||
setErrors, | ||
steps, | ||
onFinish, | ||
stepValidator, | ||
stepErrMessage, | ||
validationAttempted, | ||
setValidationAttempted, | ||
}; | ||
|
||
return ( | ||
<StepperProvider value={contextValue}> | ||
<div className="stepper-container"> | ||
<StepList /> | ||
<StepperButtons /> | ||
</div> | ||
</StepperProvider> | ||
); | ||
}; | ||
|
||
export const Step = ({ children }) => { | ||
return <>{children}</>; | ||
}; | ||
|
||
Stepper.Step = Step; | ||
|
||
|
||
Stepper.propTypes = { | ||
/** | ||
* The steps of the stepper. Should be Stepper.Step components. | ||
*/ | ||
children: PropTypes.oneOfType([ | ||
PropTypes.arrayOf(PropTypes.element), | ||
PropTypes.element | ||
]).isRequired, | ||
|
||
/** | ||
* Function to be called when the stepper is finished. | ||
*/ | ||
onFinish: PropTypes.func.isRequired, | ||
|
||
/** | ||
* Function to validate each step. Should return true if valid, false otherwise. | ||
*/ | ||
stepValidator: PropTypes.func, | ||
|
||
/** | ||
* Error message to display when a step is invalid. | ||
*/ | ||
stepErrMessage: PropTypes.string | ||
}; | ||
|
||
Stepper.defaultProps = { | ||
stepErrMessage: 'ERROR' | ||
}; | ||
|
||
Step.propTypes = { | ||
children: PropTypes.node.isRequired | ||
}; | ||
|
||
|
||
export default Stepper; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import React from 'react'; | ||
import { useStepper } from '../StepperContext'; | ||
import StepNumber from './StepNumber'; | ||
|
||
const StepItem = ({ | ||
step, | ||
index, | ||
isActive, | ||
isCompleted, | ||
hasError, | ||
isLastStep | ||
}) => { | ||
const { errors } = useStepper(); | ||
const stepError = errors[index]; | ||
|
||
return ( | ||
<div className="tyk-stepper"> | ||
<div className={`step-container ${hasError ? 'step-error' : ''}`}> | ||
{!isLastStep && <div className={`stepper-line `} />} | ||
<StepNumber | ||
number={index + 1} | ||
isCompleted={isCompleted} | ||
isActive={isActive} | ||
hasError={hasError} | ||
/> | ||
<div className="step-content"> | ||
<h3 className="step-title">{step.props.title}</h3> | ||
<p className="step-description">{step.props.description}</p> | ||
{isActive && React.cloneElement(step, { stepIndex: index })} | ||
{stepError && <p className="error-message">{stepError}</p>} | ||
</div> | ||
</div> | ||
</div> | ||
); | ||
}; | ||
|
||
export default StepItem; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import React from "react"; | ||
import { useStepper } from '../StepperContext'; | ||
import StepItem from './StepItem'; | ||
|
||
const StepList = () => { | ||
const { steps, activeStep, errors } = useStepper(); | ||
|
||
return ( | ||
<div> | ||
{steps.map((step, index) => ( | ||
<StepItem | ||
key={step} | ||
step={step} | ||
index={index} | ||
isActive={index === activeStep} | ||
isCompleted={index < activeStep} | ||
hasError={!!errors[index]} | ||
isLastStep={index === steps.length - 1} | ||
/> | ||
))} | ||
</div> | ||
); | ||
}; | ||
|
||
export default StepList; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import React from 'react'; | ||
|
||
const StepNumber = ({ number, isCompleted, isActive, hasError }) => { | ||
const classNames = ['step-number']; | ||
|
||
if (hasError) { | ||
classNames.push('error'); | ||
} else if (isCompleted) { | ||
classNames.push('completed'); | ||
} else if (isActive) { | ||
classNames.push('active'); | ||
} | ||
|
||
return <div className={classNames.join(' ')}>{hasError ? '!' : number}</div>; | ||
}; | ||
|
||
export default StepNumber; |
Oops, something went wrong.