diff --git a/styleguide/src/Navigation/Stepper/Stepper.spec.tsx b/styleguide/src/Navigation/Stepper/Stepper.spec.tsx new file mode 100644 index 0000000..bd07140 --- /dev/null +++ b/styleguide/src/Navigation/Stepper/Stepper.spec.tsx @@ -0,0 +1,68 @@ +import * as React from "react" +import { composeStories } from "@storybook/testing-react" +import { mount } from "@cypress/react" +import * as stories from "./Stepper.stories" + +const { Default, NoText, WithText, HandleClick, CustomStepClassName } = composeStories(stories) + +const specTitle = require('cypress-sonarqube-reporter/specTitle'); +describe(specTitle('Stepper tests'), () => { + + it('Default', () => { + mount() + cy.get('#stepsContainer').within(() => { + cy.get('#stepCircle1').should('have.class', 'bg-primary').contains('1') + cy.get('#stepCircle2').should('have.class', 'bg-inverted-1').contains('2') + cy.get('#stepCircle3').should('have.class', 'bg-base-4').contains('3') + }) + }) + + it('NoText', () => { + mount() + cy.get('#stepsContainer').within(() => { + cy.get('#stepCircle1').should('have.class', 'bg-primary').contains('1') + cy.get('#rightLinkStep1').should('have.class', 'absolute').and('have.class', '-right-5').and('have.class', 'bg-primary') + cy.get('#stepCircle2').should('have.class', 'bg-inverted-1').contains('2') + cy.get('#leftLinkStep2').should('have.class', 'absolute').and('have.class', '-left-5').and('have.class', 'bg-primary') + cy.get('#rightLinkStep2').should('have.class', 'absolute').and('have.class', '-right-5').and('have.class', 'bg-base-4') + cy.get('#stepCircle3').should('have.class', 'bg-base-4').contains('3') + cy.get('#leftLinkStep3').should('have.class', 'absolute').and('have.class', '-left-5').and('have.class', 'bg-base-4') + }) + }) + + it('WithText', () => { + mount() + cy.get('#stepsContainer').within(() => { + cy.get('#stepCircle1').should('have.class', 'bg-primary').contains('1') + cy.get('#step1 span').should('have.class', 'text-center').contains('Step 1') + cy.get('#rightLinkStep1').should('not.exist') + cy.get('#step2 span').should('have.class', 'text-center').contains('Step 2') + cy.get('#leftLinkStep2').should('not.exist') + cy.get('#rightLinkStep2').should('not.exist') + cy.get('#step3 span').should('have.class', 'text-center').contains('Step 3') + cy.get('#leftLinkStep3').should('not.exist') + }) + }) + + it('HandleClick', () => { + mount() + cy.get('#stepsContainer').within(() => { + cy.get('#stepCircle1').should('have.class', 'bg-primary').contains('1') + cy.get('#stepCircle1').should(() => {alert(`Clicked on step 1!`)}) + cy.get('#stepCircle2').should('have.class', 'bg-inverted-1').contains('2') + cy.get('#stepCircle2').should(() => {alert(`Clicked on step 2!`)}) + cy.get('#stepCircle3').should('have.class', 'bg-base-4').contains('3') + cy.get('#stepCircle3').should(() => {alert(`Clicked on step 3!`)}) + }) + }) + + it('CustomStepClassName', () => { + mount() + cy.get('#stepsContainer').within(() => { + cy.get('#stepCircle1').should('have.class', '!bg-primary-light').and('have.class', '!border-primary').and('have.class', '!text-primary').contains('1') + cy.get('#stepCircle2').should('have.class', '!bg-warning-light').and('have.class', '!border-warning').and('have.class', '!text-warning').contains('2') + cy.get('#stepCircle3').should('have.class', '!bg-danger-light').and('have.class', '!border-danger').and('have.class', '!text-danger').contains('3') + }) + }) + +}) diff --git a/styleguide/src/Navigation/Stepper/Stepper.stories.tsx b/styleguide/src/Navigation/Stepper/Stepper.stories.tsx new file mode 100644 index 0000000..6c31f86 --- /dev/null +++ b/styleguide/src/Navigation/Stepper/Stepper.stories.tsx @@ -0,0 +1,68 @@ +import React from 'react' +import { Story, Meta } from '@storybook/react' + +import { Stepper, StepperProps } from '.' + +export default { + title: 'Navigation/Stepper', + component: Stepper, + parameters: { + layout: 'padded', + } +} as Meta + +const Template: Story = args => + +export const Default = Template.bind({}) +Default.args = { + steps: [ + {step: 1, text: 'Step 1'}, + {step: 2, text: 'Step 2'}, + {step: 3, text: 'Step 3'} + ], + currentStep: 2 +} + +export const NoText = Template.bind({}) +NoText.args = { + steps: [ + {step: 1, text: 'Step 1'}, + {step: 2, text: 'Step 2'}, + {step: 3, text: 'Step 3'} + ], + currentStep: 2, + showText: false +} + +export const WithText = Template.bind({}) +WithText.args = { + steps: [ + {step: 1, text: 'Step 1'}, + {step: 2, text: 'Step 2'}, + {step: 3, text: 'Step 3'} + ], + currentStep: 2, + showText: true +} + +export const HandleClick = Template.bind({}) +HandleClick.args = { + steps: [ + {step: 1, text: 'Step 1', handleClick: () => alert(`Clicked on step 1!`)}, + {step: 2, text: 'Step 2', handleClick: () => alert(`Clicked on step 2!`)}, + {step: 3, text: 'Step 3', handleClick: () => alert(`Clicked on step 3!`)} + ], + currentStep: 2, + showText: false, +} + +export const CustomStepClassName = Template.bind({}) +CustomStepClassName.args = { + steps: [ + {step: 1, text: 'Step 1', customClassName: '!text-primary !bg-primary-light !border-primary'}, + {step: 2, text: 'Step 2', customClassName: '!text-warning !bg-warning-light !border-warning'}, + {step: 3, text: 'Step 3', customClassName: '!text-danger !bg-danger-light !border-danger'} + ], + currentStep: 2, + showText: false +} \ No newline at end of file diff --git a/styleguide/src/Navigation/Stepper/index.tsx b/styleguide/src/Navigation/Stepper/index.tsx new file mode 100644 index 0000000..b16f242 --- /dev/null +++ b/styleguide/src/Navigation/Stepper/index.tsx @@ -0,0 +1,100 @@ +import React from 'react' + +export const Stepper: React.FC = React.memo( + ({ className = '', currentStep = 0, steps = [], showText = false }) => { + const getColors = (step: number) => { + if (currentStep > step) return 'bg-primary border-primary' + if (currentStep === step) return 'bg-inverted-1 border-inverted-1' + return 'bg-base-4 border-base-4' + } + return ( +
+ {steps.map((item, index) => { + return ( +
+ {!showText && index > 0 && ( +
= item.step ? 'bg-primary' : 'bg-base-4' + }`} + /> + )} +
+ item.handleClick?.()} + className={`font-bold ${ + item.handleClick ? 'cursor-pointer' : '' + }`} + > + {item.step} + +
+ {showText && ( + item.handleClick?.()} + className={`text-center my-auto text-sm ${showText ? '' : ''} + ${item.step < 2 ? 'cursor-pointer' : ''} + ${currentStep === item.step ? 'font-bold' : ''}`} + > + {item.text} + + )} + {!showText && index < steps.length - 1 && ( +
item.step ? 'bg-primary' : 'bg-base-4' + }`} + /> + )} +
+ ) + })} +
+ ) + } +) + +type StepType = { + step: number + text?: string + handleClick?: Function + customClassName?: string +} + +export interface StepperProps { + /** + * Custom class name + * */ + className?: string + /** + * Current page step + * */ + currentStep: number + /** + * Array of steps containing the step number (1, 2, 3, ..., n), + * text with step's title (optional) and a handler for the + * step's click (optional) + * */ + steps: StepType[] + /** + * Shows step text instead of linking lines + * */ + showText?: boolean + /** + * Function to handle step click + * */ +}