Skip to content

Commit

Permalink
Stepper component (#209)
Browse files Browse the repository at this point in the history
* component implementation

* stepper stories

* stepper stories tests

* file linting
  • Loading branch information
HCarrer authored May 2, 2024
1 parent 2519662 commit 1a5aed2
Show file tree
Hide file tree
Showing 3 changed files with 236 additions and 0 deletions.
68 changes: 68 additions & 0 deletions styleguide/src/Navigation/Stepper/Stepper.spec.tsx
Original file line number Diff line number Diff line change
@@ -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(<Default />)
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(<NoText />)
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(<WithText />)
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(<HandleClick />)
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(<CustomStepClassName />)
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')
})
})

})
68 changes: 68 additions & 0 deletions styleguide/src/Navigation/Stepper/Stepper.stories.tsx
Original file line number Diff line number Diff line change
@@ -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<StepperProps> = args => <Stepper {...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
}
100 changes: 100 additions & 0 deletions styleguide/src/Navigation/Stepper/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import React from 'react'

export const Stepper: React.FC<StepperProps> = 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 (
<div
className={`w-full flex flex-row justify-center gap-x-10 mb-6 relative ${className}`}
id="stepsContainer"
>
{steps.map((item, index) => {
return (
<div
key={item.step}
id={`step${item.step}`}
className="flex flex-row items-center gap-y-2.5 gap-x-3.5 z-10 relative"
>
{!showText && index > 0 && (
<div
id={`leftLinkStep${item.step}`}
className={`w-5 h-0.5 absolute -left-5 ${
currentStep >= item.step ? 'bg-primary' : 'bg-base-4'
}`}
/>
)}
<div
id={`stepCircle${item.step}`}
className={`flex w-8 h-8 rounded-full border-2 justify-center items-center text-base-1
${getColors(item.step)}
${item.customClassName}`}
>
<span
onClick={() => item.handleClick?.()}
className={`font-bold ${
item.handleClick ? 'cursor-pointer' : ''
}`}
>
{item.step}
</span>
</div>
{showText && (
<span
onClick={() => item.handleClick?.()}
className={`text-center my-auto text-sm ${showText ? '' : ''}
${item.step < 2 ? 'cursor-pointer' : ''}
${currentStep === item.step ? 'font-bold' : ''}`}
>
{item.text}
</span>
)}
{!showText && index < steps.length - 1 && (
<div
id={`rightLinkStep${item.step}`}
className={`w-5 h-0.5 absolute -right-5 ${
currentStep > item.step ? 'bg-primary' : 'bg-base-4'
}`}
/>
)}
</div>
)
})}
</div>
)
}
)

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
* */
}

0 comments on commit 1a5aed2

Please sign in to comment.