Skip to content

Commit

Permalink
feat(card): add card component
Browse files Browse the repository at this point in the history
* this PR adds the card component described at https://designsystem.digital.gov/components/card/
  • Loading branch information
eamahanna authored Jun 4, 2020
1 parent a7d6d85 commit 89cf241
Show file tree
Hide file tree
Showing 15 changed files with 923 additions and 3 deletions.
523 changes: 523 additions & 0 deletions src/components/card/Card.stories.tsx

Large diffs are not rendered by default.

35 changes: 35 additions & 0 deletions src/components/card/Card/Card.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React from 'react'
import { render } from '@testing-library/react'

import { Card } from './Card'

describe('Card component', () => {
it('renders without errors', () => {
const { queryByTestId } = render(<Card layout="standardDefault" />)
expect(queryByTestId('Card')).toBeInTheDocument()
})

it('renders its children', () => {
const { queryByText } = render(
<Card layout="standardDefault">My Content</Card>
)
expect(queryByText('My Content')).toBeInTheDocument()
})

it('renders the header first class when standardHeaderFirst is true', () => {
const { getByTestId } = render(
<Card layout="standardDefault" headerFirst={true} />
)
expect(getByTestId('Card')).toHaveClass('usa-card--header-first')
})

it('renders the flag class when layout is flag', () => {
const { getByTestId } = render(<Card layout="flagDefault" />)
expect(getByTestId('Card')).toHaveClass('usa-card--flag')
})

it('renders the media right class when layout is flag and mediaOrientation is right', () => {
const { getByTestId } = render(<Card layout="flagMediaRight" />)
expect(getByTestId('Card')).toHaveClass('usa-card--media-right')
})
})
52 changes: 52 additions & 0 deletions src/components/card/Card/Card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import React from 'react'
import classnames from 'classnames'

import { GridLayoutProp, applyGridClasses } from '../../grid/Grid/Grid'

interface CardProps {
layout?: 'standardDefault' | 'flagDefault' | 'flagMediaRight'
headerFirst?: boolean
containerProps?: React.HTMLAttributes<HTMLDivElement>
}

export const Card = (
props: CardProps & React.HTMLAttributes<HTMLLIElement> & GridLayoutProp
): React.ReactElement => {
const {
layout = 'standardDefault',
headerFirst,
children,
className,
gridLayout,
containerProps,
...liProps
} = props

const { className: containerClass, ...restContainerProps } =
containerProps || {}

const gridClasses = gridLayout && applyGridClasses(gridLayout)

const classes = classnames(
'usa-card',
{
'usa-card--header-first': headerFirst,
'usa-card--flag': layout === 'flagDefault' || layout === 'flagMediaRight',
'usa-card--media-right': layout === 'flagMediaRight',
},
gridClasses,
className
)

const containerClasses = classnames('usa-card__container', containerClass)

return (
<li className={classes} data-testid="Card" {...liProps}>
<div className={containerClasses} {...restContainerProps}>
{children}
</div>
</li>
)
}

export default Card
26 changes: 26 additions & 0 deletions src/components/card/CardBody/CardBody.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import React from 'react'
import { render } from '@testing-library/react'

import { CardBody } from './CardBody'

describe('CardBody component', () => {
it('renders without errors', () => {
const { queryByTestId } = render(<CardBody />)
expect(queryByTestId('CardBody')).toBeInTheDocument()
})

it('renders its children', () => {
const { queryByText } = render(<CardBody>Body Content</CardBody>)
expect(queryByText('Body Content')).toBeInTheDocument()
})

it('renders optional header props', () => {
const { queryByTestId } = render(<CardBody className="testClass" />)
expect(queryByTestId('CardBody')).toHaveClass('testClass')
})

it('renders proper class when exdent is true', () => {
const { queryByTestId } = render(<CardBody exdent>Content</CardBody>)
expect(queryByTestId('CardBody')).toHaveClass('usa-card__body--exdent')
})
})
24 changes: 24 additions & 0 deletions src/components/card/CardBody/CardBody.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React from 'react'
import classnames from 'classnames'

export const CardBody = (
props: { exdent?: boolean } & React.HTMLAttributes<HTMLDivElement>
): React.ReactElement => {
const { exdent, children, className, ...bodyProps } = props

const classes = classnames(
'usa-card__body',
{
'usa-card__body--exdent': exdent,
},
className
)

return (
<div className={classes} {...bodyProps} data-testid="CardBody">
{children}
</div>
)
}

export default CardBody
26 changes: 26 additions & 0 deletions src/components/card/CardFooter/CardFooter.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import React from 'react'
import { render } from '@testing-library/react'

import { CardFooter } from './CardFooter'

describe('CardFooter component', () => {
it('renders without errors', () => {
const { queryByTestId } = render(<CardFooter />)
expect(queryByTestId('CardFooter')).toBeInTheDocument()
})

it('renders its children', () => {
const { queryByText } = render(<CardFooter>My Header</CardFooter>)
expect(queryByText('My Header')).toBeInTheDocument()
})

it('renders optional header props', () => {
const { queryByTestId } = render(<CardFooter className="testClass" />)
expect(queryByTestId('CardFooter')).toHaveClass('testClass')
})

it('renders proper class when exdent is true', () => {
const { queryByTestId } = render(<CardFooter exdent>Content</CardFooter>)
expect(queryByTestId('CardFooter')).toHaveClass('usa-card__footer--exdent')
})
})
24 changes: 24 additions & 0 deletions src/components/card/CardFooter/CardFooter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React from 'react'
import classnames from 'classnames'

export const CardFooter = (
props: { exdent?: boolean } & React.HTMLAttributes<HTMLDivElement>
): React.ReactElement => {
const { exdent, children, className, ...footerProps } = props

const classes = classnames(
'usa-card__footer',
{
'usa-card__footer--exdent': exdent,
},
className
)

return (
<div className={classes} {...footerProps} data-testid="CardFooter">
{children}
</div>
)
}

export default CardFooter
20 changes: 20 additions & 0 deletions src/components/card/CardGroup/CardGroup.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React from 'react'
import { render } from '@testing-library/react'

import { CardGroup } from './CardGroup'

describe('CardGroup component', () => {
it('renders without errors', () => {
const { queryByTestId } = render(<CardGroup />)
expect(queryByTestId('CardGroup')).toBeInTheDocument()
})

it('renders its children', () => {
const { queryByText } = render(
<CardGroup>
<li>My list item</li>
</CardGroup>
)
expect(queryByText('My list item')).toBeInTheDocument()
})
})
18 changes: 18 additions & 0 deletions src/components/card/CardGroup/CardGroup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import React from 'react'
import classnames from 'classnames'

export const CardGroup = (
props: React.HTMLAttributes<HTMLUListElement>
): React.ReactElement => {
const { children, className, ...ulProps } = props

const classes = classnames('usa-card-group', className)

return (
<ul className={classes} data-testid="CardGroup" {...ulProps}>
{children}
</ul>
)
}

export default CardGroup
26 changes: 26 additions & 0 deletions src/components/card/CardHeader/CardHeader.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import React from 'react'
import { render } from '@testing-library/react'

import { CardHeader } from './CardHeader'

describe('CardHeader component', () => {
it('renders without errors', () => {
const { queryByTestId } = render(<CardHeader />)
expect(queryByTestId('CardHeader')).toBeInTheDocument()
})

it('renders its children', () => {
const { queryByText } = render(<CardHeader>My Header</CardHeader>)
expect(queryByText('My Header')).toBeInTheDocument()
})

it('renders optional header props', () => {
const { queryByTestId } = render(<CardHeader className="testClass" />)
expect(queryByTestId('CardHeader')).toHaveClass('testClass')
})

it('renders proper class when exdent is true', () => {
const { queryByTestId } = render(<CardHeader exdent>Content</CardHeader>)
expect(queryByTestId('CardHeader')).toHaveClass('usa-card__header--exdent')
})
})
24 changes: 24 additions & 0 deletions src/components/card/CardHeader/CardHeader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React from 'react'
import classnames from 'classnames'

export const CardHeader = (
props: { exdent?: boolean } & React.HTMLAttributes<HTMLElement>
): React.ReactElement => {
const { exdent, children, className, ...headerProps } = props

const classes = classnames(
'usa-card__header',
{
'usa-card__header--exdent': exdent,
},
className
)

return (
<header className={classes} {...headerProps} data-testid="CardHeader">
{children}
</header>
)
}

export default CardHeader
35 changes: 35 additions & 0 deletions src/components/card/CardMedia/CardMedia.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React from 'react'
import { render } from '@testing-library/react'

import { CardMedia } from './CardMedia'

describe('CardMedia component', () => {
it('renders without errors', () => {
const { queryByTestId } = render(<CardMedia>Media Content</CardMedia>)
expect(queryByTestId('CardMedia')).toBeInTheDocument()
})

it('renders its children', () => {
const { queryByText } = render(<CardMedia>Media Content</CardMedia>)
expect(queryByText('Media Content')).toBeInTheDocument()
})

it('renders optional media props', () => {
const { queryByTestId } = render(
<CardMedia className="testClass">Media Content</CardMedia>
)
expect(queryByTestId('CardMedia')).toHaveClass('testClass')
})

it('renders proper class when exdent is true', () => {
const { queryByTestId } = render(
<CardMedia exdent>Media Content</CardMedia>
)
expect(queryByTestId('CardMedia')).toHaveClass('usa-card__media--exdent')
})

it('renders proper class when inset is true', () => {
const { queryByTestId } = render(<CardMedia inset>Inset Content</CardMedia>)
expect(queryByTestId('CardMedia')).toHaveClass('usa-card__media--inset')
})
})
41 changes: 41 additions & 0 deletions src/components/card/CardMedia/CardMedia.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React from 'react'
import classnames from 'classnames'

interface CardMediaProps {
exdent?: boolean
inset?: boolean
imageClass?: string
children: React.ReactNode
}

export const CardMedia = (
props: CardMediaProps & React.HTMLAttributes<HTMLDivElement>
): React.ReactElement => {
const {
exdent,
inset,
imageClass,
children,
className,
...mediaProps
} = props

const classes = classnames(
'usa-card__media',
{
'usa-card__media--exdent': exdent,
'usa-card__media--inset': inset,
},
className
)

const imageClasses = classnames('usa-card__img', imageClass)

return (
<div className={classes} {...mediaProps} data-testid="CardMedia">
<div className={imageClasses}>{children}</div>
</div>
)
}

export default CardMedia
Loading

0 comments on commit 89cf241

Please sign in to comment.