Skip to content
This repository has been archived by the owner on Aug 21, 2023. It is now read-only.

Commit

Permalink
Merge pull request #749 from helpscout/DotStepper
Browse files Browse the repository at this point in the history
HSDS-81: DotStepper Component
  • Loading branch information
Charca committed Nov 15, 2019
2 parents 78c2d83 + 469d32a commit 8b48a3c
Show file tree
Hide file tree
Showing 9 changed files with 200 additions and 2 deletions.
8 changes: 6 additions & 2 deletions .storybook/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,12 @@ addParameters({

// automatically import all files ending in *.stories.js
function loadStories() {
const req = require.context('../stories', true, /.stories.(js|ts|tsx)$/)
req.keys().forEach(filename => req(filename))
const reqs = [
require.context('../stories', true, /.stories.(js|ts|tsx)$/),
require.context('../src', true, /.stories.(js|ts|tsx)$/),
]

reqs.forEach(req => req.keys().forEach(filename => req(filename)))
}

configure(loadStories, module)
1 change: 1 addition & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ module.exports = {
'src/**/*.{js,jsx,ts,tsx}',
'!src/**/?(*.)types.{js,jsx,ts,tsx}',
'!src/**/?(*.)helpers.{js,jsx,ts,tsx}',
'!src/**/?(*.)stories.{js,jsx,ts,tsx}',
'!src/**/?(*.)testHelpers.{js,jsx,ts,tsx}',
'!src/tests/helpers/**/*.{js,jsx,ts,tsx}',
'!src/styles/tests/helpers/**/*.{js,jsx,ts,tsx}',
Expand Down
51 changes: 51 additions & 0 deletions src/components/DotStepper/DotStepper.css.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import styled from '../styled'
import { getColor } from '../../styles/utilities/color'

const BULLET_SIZE = 6
const BULLET_MARGIN = 5
const getProgressWidth = step => step * BULLET_SIZE + (step - 1) * BULLET_MARGIN

export const DotStepperUI = styled('ol')`
display: flex;
list-style: none;
margin: 0;
padding: ${BULLET_MARGIN}px;
position: relative;
`

export const ProgressBulletUI = styled('div')`
position: absolute;
top: ${BULLET_MARGIN}px;
left: ${BULLET_MARGIN}px;
background: ${getColor('green.300')};
width: ${({ step }) => getProgressWidth(step)}px;
height: ${BULLET_SIZE}px;
border-radius: ${BULLET_SIZE}px;
transition: all 0.15s ease-out;
&::after {
content: '';
width: ${BULLET_SIZE}px;
height: ${BULLET_SIZE}px;
top: 0;
right: 0;
position: absolute;
border-radius: ${BULLET_SIZE}px;
background: ${getColor('green.500')};
}
`

export const BulletUI = styled('li')`
float: left;
width: ${BULLET_SIZE}px;
height: ${BULLET_SIZE}px;
border-radius: ${BULLET_SIZE}px;
background: ${getColor('grey.500')};
overflow: hidden;
margin: 0 0 0 ${BULLET_MARGIN}px;
text-indent: -9999px;
&:first-child {
margin: 0;
}
`
53 changes: 53 additions & 0 deletions src/components/DotStepper/DotStepper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import React from 'react'
import Tooltip from '../Tooltip'
import { DotStepperUI, BulletUI, ProgressBulletUI } from './DotStepper.css.js'
import { classNames } from '../../utilities/classNames'

class DotStepper extends React.Component {
static defaultProps = {
numSteps: 1,
step: 1,
}

getClassName() {
const { className } = this.props
return classNames('c-DotStepper', className)
}

getTitle() {
const { numSteps, step } = this.props
return `Step ${step} of ${numSteps}`
}

renderSteps() {
const { numSteps } = this.props
let markup = []

for (let i = 0; i < numSteps; i += 1) {
markup.push(<BulletUI key={i}></BulletUI>)
}

return markup
}

render() {
const { className, step, ...rest } = this.props
const title = this.getTitle()

return (
<Tooltip title={title} placement="bottom">
<DotStepperUI
data-cy="DotStepper"
aria-label={title}
className={this.getClassName()}
{...rest}
>
{this.renderSteps()}
<ProgressBulletUI step={step} />
</DotStepperUI>
</Tooltip>
)
}
}

export default DotStepper
26 changes: 26 additions & 0 deletions src/components/DotStepper/DotStepper.stories.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import React from 'react'
import { storiesOf } from '@storybook/react'
import DotStepper from './'
import { withKnobs, number } from '@storybook/addon-knobs'
import { withArtboard } from '@helpscout/artboard'

const stories = storiesOf('DotStepper', module)

stories.addDecorator(
withArtboard({
width: 500,
height: 300,
withCenterGuides: false,
showInterface: false,
})
)
stories.addDecorator(withKnobs)

stories.add('Default', () => {
const props = {
numSteps: number('numSteps', 4),
step: number('step', 2),
}

return <DotStepper {...props} />
})
43 changes: 43 additions & 0 deletions src/components/DotStepper/DotStepper.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React from 'react'
import { mount } from 'enzyme'
import { DotStepperUI, BulletUI, ProgressBulletUI } from './DotStepper.css.js'
import DotStepper from './'
import Tooltip from '../Tooltip'

describe('className', () => {
test('Has default className', () => {
const wrapper = mount(<DotStepper />)
expect(wrapper.find(DotStepperUI).hasClass('c-DotStepper')).toBeTruthy()
})

test('Can render custom className', () => {
const customClassName = 'clazz'
const wrapper = mount(<DotStepper className={customClassName} />)
expect(wrapper.find(DotStepperUI).hasClass(customClassName)).toBeTruthy()
})
})

describe('Steps', () => {
test('Renders a BulletUI component for each step', () => {
const numSteps = 8
const wrapper = mount(<DotStepper numSteps={numSteps} />)
expect(wrapper.find(BulletUI)).toHaveLength(numSteps)
})

test('Renders a single ProgressBulletUI component', () => {
const numSteps = 8
const wrapper = mount(<DotStepper numSteps={numSteps} />)
expect(wrapper.find(ProgressBulletUI)).toHaveLength(1)
})
})

describe('Tooltip', () => {
test('Should render a Tooltip component', () => {
const numSteps = 8
const step = 4
const wrapper = mount(<DotStepper numSteps={numSteps} step={step} />)
expect(wrapper.find(Tooltip).prop('title')).toEqual(
`Step ${step} of ${numSteps}`
)
})
})
16 changes: 16 additions & 0 deletions src/components/DotStepper/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# DotStepper

Minimal visual representation of current and total number of steps in a multi-step form.

## Example

```jsx
<DotStepper numSteps={4} step={2} />
```

## Props

| Prop | Type | Default | Description |
| -------- | -------- | ------- | -------------------------- |
| numSteps | `number` | `1` | The total number of steps. |
| step | `number` | `1` | The current step. |
3 changes: 3 additions & 0 deletions src/components/DotStepper/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import DotStepper from './DotStepper'

export default DotStepper
1 change: 1 addition & 0 deletions src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export { default as CopyButton } from './CopyButton'
export { default as CopyCode } from './CopyCode'
export { default as CopyInput } from './CopyInput'
export { default as DetailList } from './DetailList'
export { default as DotStepper } from './DotStepper'
export { default as Dropdown } from './Dropdown'
export { default as Emoticon } from './Emoticon'
export { default as EventListener } from './EventListener'
Expand Down

0 comments on commit 8b48a3c

Please sign in to comment.