Skip to content

Commit

Permalink
feat: Add OutOfStock component (#1314)
Browse files Browse the repository at this point in the history
Co-authored-by: Filipe W. Lima <filipe.lima@vtex.com.br>
Co-authored-by: Eduardo Formiga <eduardo.formiga@gmail.com>
  • Loading branch information
3 people authored May 26, 2022
1 parent aa8eaee commit 37eac86
Show file tree
Hide file tree
Showing 10 changed files with 359 additions and 0 deletions.
10 changes: 10 additions & 0 deletions packages/ui/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,16 @@ export type {
} from './molecules/Dropdown'

// Organisms
export {
default as OutOfStock,
OutOfStockTitle,
OutOfStockMessage,
} from './organisms/OutOfStock'
export type {
OutOfStockProps,
OutOfStockMessageProps,
OutOfStockTitleProps,
} from './organisms/OutOfStock'

// Hooks
export { default as useSlider } from './hooks/useSlider'
Expand Down
122 changes: 122 additions & 0 deletions packages/ui/src/organisms/OutOfStock/OutOfStock.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { axe } from 'jest-axe'
import React from 'react'

import { OutOfStockMessage, OutOfStockTitle } from '.'
import Button from '../../atoms/Button'
import Input from '../../atoms/Input'
import Label from '../../atoms/Label'
import OutOfStock from './OutOfStock'

const SimpleOutOfStock = () => (
<OutOfStock>
<OutOfStockTitle>
Text <span>icon</span>
</OutOfStockTitle>
<OutOfStockMessage>Notify me when available</OutOfStockMessage>
<Label>
Email
<Input />
</Label>
<Button>Notify me</Button>
</OutOfStock>
)

describe('OutOfStock', () => {
it('`Out Of Stock` components should have corrects attributes', () => {
render(<SimpleOutOfStock />)

const outOfStock = screen.getByTestId('store-out-of-stock')
const outOfStockTitle = screen.getByTestId('store-out-of-stock-title')
const outOfStockMessage = screen.getByTestId('store-out-of-stock-message')
const outOfStockForm = screen.getByTestId('store-out-of-stock-form')

expect(outOfStock).toHaveAttribute('data-store-out-of-stock')
expect(outOfStockForm).toHaveAttribute('data-out-of-stock-form')
expect(outOfStockTitle).toHaveAttribute('data-out-of-stock-title')
expect(outOfStockMessage).toHaveAttribute('data-out-of-stock-message')
})

it('Should emit event', () => {
const onSubmitMock = jest.fn((e) => e.preventDefault())

render(
<OutOfStock onSubmit={onSubmitMock}>
<OutOfStockTitle>Out of Stock</OutOfStockTitle>
<Input name="email" />
<Button type="submit">Notify me</Button>
</OutOfStock>
)

const outOfStockEventButton = screen.getByTestId('store-button')

userEvent.click(outOfStockEventButton)

expect(onSubmitMock).toHaveBeenCalledTimes(1)
})

it('Should not render message', () => {
render(
<OutOfStock>
<OutOfStockTitle>Out of Stock</OutOfStockTitle>
<Input name="email" />
<Button type="submit">Notify me</Button>
</OutOfStock>
)

const message = screen.queryByTestId('store-out-of-stock-message')

expect(message).not.toBeInTheDocument()
})
})

describe('Accessibility', () => {
it('should not have violations or incompletes', async () => {
const { container } = render(<SimpleOutOfStock />)

expect(await axe(container)).toHaveNoViolations()
expect(await axe(container)).toHaveNoIncompletes()
})

it('Out of Stock component should be a `section`', () => {
render(<SimpleOutOfStock />)
const outOfStock = screen.getByTestId('store-out-of-stock')

expect(outOfStock.tagName).toEqual('SECTION')
})

it('Out of Stock `title` component should be a `heading 2` as default', () => {
render(<SimpleOutOfStock />)
const outOfStockTitle = screen.getByTestId('store-out-of-stock-title')

expect(outOfStockTitle.tagName).toEqual('H2')
})

it('Out of Stock `message` should be a `paragraph` as default', () => {
render(<SimpleOutOfStock />)
const outOfStockMessage = screen.getByTestId('store-out-of-stock-message')

expect(outOfStockMessage.tagName).toEqual('P')
})

it('Out of Stock should render `title` as heading 1 and `message` as span', () => {
render(
<OutOfStock>
<OutOfStockTitle as="h1">Head Out Os Stock</OutOfStockTitle>
<OutOfStockMessage as="span">Head Out Os Stock</OutOfStockMessage>
<Label>
Email
<Input />
</Label>
<Button>Notify me</Button>
</OutOfStock>
)

const outOfStockMessage = screen.getByTestId('store-out-of-stock-message')
const outOfStockTitle = screen.getByTestId('store-out-of-stock-title')

expect(outOfStockTitle.tagName).toEqual('H1')
expect(outOfStockMessage.tagName).toEqual('SPAN')
})
})
39 changes: 39 additions & 0 deletions packages/ui/src/organisms/OutOfStock/OutOfStock.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import React from 'react'
import type { ReactNode, FormHTMLAttributes } from 'react'

import Form from '../../molecules/Form'

export type OutOfStockBaseProps = {
/**
* ID to find this component in testing tools (e.g.: cypress,
* testing-library, and jest).
*/
testId?: string
/**
* Children for Out of Stock components.
*/
children: string | ReactNode
}

export type OutOfStockProps = OutOfStockBaseProps & {
/**
* Event emitted when form is submitted.
*/
onSubmit?: (event: React.FormEvent<HTMLFormElement>) => void
} & FormHTMLAttributes<HTMLFormElement>

const OutOfStock = ({
testId = 'store-out-of-stock',
children,
...otherProps
}: OutOfStockProps) => {
return (
<section data-store-out-of-stock data-testid={testId}>
<Form data-out-of-stock-form testId={`${testId}-form`} {...otherProps}>
{children}
</Form>
</section>
)
}

export default OutOfStock
22 changes: 22 additions & 0 deletions packages/ui/src/organisms/OutOfStock/OutOfStockMessage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React from 'react'

import type { OutOfStockBaseProps } from './OutOfStock'

export type OutOfStockMessageProps = {
/**
* Attribute used for polymorphic component.
*/
as?: 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'p' | 'div' | 'span'
} & OutOfStockBaseProps

export const OutOfStockMessage = ({
as: MessageComponent = 'p',
testId = 'store-out-of-stock-message',
children,
}: OutOfStockMessageProps) => {
return (
<MessageComponent data-out-of-stock-message data-testid={testId}>
{children}
</MessageComponent>
)
}
22 changes: 22 additions & 0 deletions packages/ui/src/organisms/OutOfStock/OutOfStockTitle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React from 'react'

import type { OutOfStockBaseProps } from './OutOfStock'

export type OutOfStockTitleProps = {
/**
* Attribute used for polymorphic component.
*/
as?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'p'
} & OutOfStockBaseProps

export const OutOfStockTitle = ({
as: TitleComponent = 'h2',
testId = 'store-out-of-stock-title',
children,
}: OutOfStockTitleProps) => {
return (
<TitleComponent data-out-of-stock-title data-testid={testId}>
{children}
</TitleComponent>
)
}
6 changes: 6 additions & 0 deletions packages/ui/src/organisms/OutOfStock/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export { default } from './OutOfStock'
export { OutOfStockMessage } from './OutOfStockMessage'
export { OutOfStockTitle } from './OutOfStockTitle'
export type { OutOfStockProps } from './OutOfStock'
export type { OutOfStockMessageProps } from './OutOfStockMessage'
export type { OutOfStockTitleProps } from './OutOfStockTitle'
37 changes: 37 additions & 0 deletions packages/ui/src/organisms/OutOfStock/stories/OutOfStock.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Canvas, Props, Story, ArgsTable } from '@storybook/addon-docs'

import OutOfStock from '../OutOfStock'

# OutOfStock

<Canvas>
<Story id="organisms-outofstock--out-of-stock" />
</Canvas>

The `OutOfStock` uses the [Compound Component](https://kentcdodds.com/blog/compound-components-with-react-hooks) pattern, its components are:

- `OutOfStock`: the form wrapper of the out of stock component with title and message;
- `OutOfStockTitle`: the main title component for out of stock component;
- `OutOfStockMessage`: the message component for out of stock component;

## Props

### `OutOfStock`

<ArgsTable of={ OutOfStock } />

## CSS Selectors

```css
[data-store-out-of-stock] {
}

[data-out-of-stock-form] {
}

[data-out-of-stock-title] {
}

[data-out-of-stock-message] {
}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import type { Story, Meta } from '@storybook/react'
import React, { useState } from 'react'

import type {
OutOfStockMessageProps,
OutOfStockTitleProps,
OutOfStockProps,
} from '..'
import Component, { OutOfStockTitle, OutOfStockMessage } from '..'
import Button from '../../../atoms/Button'
import Input from '../../../atoms/Input'
import mdx from './OutOfStock.mdx'

type OutOfStockTemplateProps = {
title: string
message: string
titleAs: OutOfStockTitleProps['as']
messageAs: OutOfStockMessageProps['as']
} & OutOfStockProps

const OutOfStockTemplate: Story<OutOfStockTemplateProps> = ({
title,
message,
titleAs,
messageAs,
...props
}) => {
const [value, setValue] = useState('')

const handlerSubmitForm = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault()

// eslint-disable-next-line no-alert
alert(value)
}

return (
<Component onSubmit={handlerSubmitForm} {...props}>
<OutOfStockTitle as={titleAs}>{title}</OutOfStockTitle>
<OutOfStockMessage as={messageAs}>{message}</OutOfStockMessage>
<Input value={value} onChange={(e) => setValue(e.target.value)} />
<Button>Notify me</Button>
</Component>
)
}

export const OutOfStock = OutOfStockTemplate.bind({})
OutOfStock.storyName = 'OutOfStock'

export default {
title: 'Organisms/OutOfStock',
parameters: {
docs: {
page: mdx,
},
},
args: {
title: 'Notify me',
message: 'Notify me when available',
},
argTypes: {
titleAs: {
defaultValue: 'h2',
options: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p'],
control: { type: 'select' },
},
messageAs: {
defaultValue: 'p',
options: ['h2', 'h3', 'h4', 'h5', 'h6', 'p', 'div', 'span'],
control: { type: 'select' },
},
},
} as Meta
1 change: 1 addition & 0 deletions themes/theme-b2c-tailwind/src/organisms/index.css
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@import './out-of-stock.css';
27 changes: 27 additions & 0 deletions themes/theme-b2c-tailwind/src/organisms/out-of-stock.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
[data-store-out-of-stock] [data-out-of-stock-form] {
display: flex;
align-items: center;
flex-direction: column;
}

[data-store-out-of-stock] [data-out-of-stock-title] {
margin-bottom: .25rem;
font-size: inherit;
font-weight: inherit;
}

[data-store-out-of-stock] [data-out-of-stock-message] {
align-items: center;
margin-bottom: 1rem;
}

[data-out-of-stock-form] [data-store-button] {
width: 100%;
margin-top: 1rem;
}

[data-out-of-stock-form] [data-store-input] {
width: 100%;
margin-top: 0.25rem;
max-width: initial;
}

1 comment on commit 37eac86

@vercel
Copy link

@vercel vercel bot commented on 37eac86 May 26, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.