Skip to content

Commit

Permalink
Add footer, slim footer story example (#107)
Browse files Browse the repository at this point in the history
* Add footer, slim footer story example
* Add Logo, static file loading
* Add Address
  • Loading branch information
haworku authored May 5, 2020
1 parent 8408d61 commit 4907181
Show file tree
Hide file tree
Showing 11 changed files with 394 additions and 1 deletion.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage",
"storybook": "start-storybook",
"storybook": "start-storybook -s ./static -p 9001",
"storybook:deploy": "storybook-to-ghpages",
"build": "webpack",
"build:watch": "webpack --watch",
Expand Down
26 changes: 26 additions & 0 deletions src/components/Footer/Address/Address.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import React from 'react'
import { Address } from './Address'

export default {
title: 'Address',
parameters: {
info: `
Used within USWDS 2.0 Footer component
Source: https://designsystem.digital.gov/components/form-controls/#footer
`,
},
}

export const WithLinks = (): React.ReactElement => (
<Address
items={[
<a key="phone" href="tel:123-456-7890">
(123) 456 - 7890
</a>,
<a key="email" href="mailto:[email protected]">
[email protected]
</a>,
]}
/>
)
26 changes: 26 additions & 0 deletions src/components/Footer/Address/Address.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 { Address } from './Address'

const addressItems = [
<a key="phone" href="tel:123-456-7890">
(123) 456 - 7890
</a>,
<a key="email" href="mailto:[email protected]">
[email protected]
</a>,
]

describe('Address component', () => {
it('renders without errors', () => {
const { container } = render(<Address items={addressItems} />)
expect(container.querySelector('address')).toBeInTheDocument()
})

it('renders address items', () => {
const { getByText } = render(<Address items={addressItems} />)
expect(getByText('(123) 456 - 7890')).toBeInTheDocument()
expect(getByText('[email protected]')).toBeInTheDocument()
})
})
24 changes: 24 additions & 0 deletions src/components/Footer/Address/Address.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React from 'react'

type AddressProps = {
/*
Contact info items - e.g. anchor tags or text for email, phone, website, etc.
*/
items: React.ReactNode[]
}

export const Address = ({
items,
}: AddressProps & React.HTMLAttributes<HTMLElement>): React.ReactElement => (
<address className="usa-footer__address">
<div className="grid-row grid-gap">
{items.map((item, i) => (
<div
className="grid-col-auto mobile-lg:grid-col-12 desktop:grid-col-auto"
key={`addressItem-${i}`}>
<div className="usa-footer__contact-info">{item}</div>
</div>
))}
</div>
</address>
)
102 changes: 102 additions & 0 deletions src/components/Footer/Footer.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/* eslint-disable jsx-a11y/anchor-is-valid */
import React from 'react'
import classnames from 'classnames'

import { Address } from './Address/Address'
import { Button } from '../Button/Button'
import { Footer } from './Footer'
import { Logo } from './Logo/Logo'

export default {
title: 'Footer',
parameters: {
info: `
USWDS 2.0 Footer component
Source: https://designsystem.digital.gov/components/form-controls/#footer
`,
},
}

type SizeProps = {
big?: boolean
medium?: boolean
slim?: boolean
}

// Placeholder until a dynamic Nav is built out and can be used
const MockNav = ({ medium, slim }: SizeProps): React.ReactElement => {
const itemClasses = classnames(
'desktop:grid-col-auto usa-footer__primary-content',
{
'mobile-lg:grid-col-4': medium,
'mobile-lg:grid-col-6': slim,
}
)

const items = Array(4).fill({
href: 'javascript:void(0);',
heading: 'Primary Link',
})

return (
<nav className="usa-footer__nav" aria-label="Footer navigation">
<ul className="grid-row grid-gap">
{items.map((item, i) => (
<li key={`navItem-${i}`} className={itemClasses}>
<a className="usa-footer__primary-link" href={item.href}>
{item.heading}
</a>
</li>
))}
</ul>
</nav>
)
}

const returnToTop = (
<div className="grid-container usa-footer__return-to-top">
<Button type="button" unstyled>
Return to top
</Button>
</div>
)

export const SlimFooter = (): React.ReactElement => (
<Footer
slim
returnToTop={returnToTop}
primary={
<div className="usa-footer__primary-container grid-row">
<div className="mobile-lg:grid-col-8">
<MockNav slim />
</div>
<div className="tablet:grid-col-4">
<Address
items={[
<a key="telephone" href="tel:1-800-555-5555">
(800) CALL-GOVT
</a>,
<a key="email" href="mailto:[email protected]">
[email protected]
</a>,
]}
/>
</div>
</div>
}
secondary={
<Logo
slim
image={
<img
className="usa-footer__logo-img"
src="/logo-img.png"
alt="img alt text"
/>
}
heading={<h3 className="usa-footer__logo-heading">Name of Agency</h3>}
/>
}
/>
)
48 changes: 48 additions & 0 deletions src/components/Footer/Footer.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import React from 'react'
import { render } from '@testing-library/react'

import { Footer } from './Footer'

const nav = (
<nav>
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
</nav>
)

const address = (
<address>
A mythical address. More address information. You can def find us online.
</address>
)

const returnToTop = (
<div className="grid-container usa-footer__return-to-top">Return To Top</div>
)

describe('Footer component', () => {
it('renders without errors', () => {
const { container } = render(<Footer primary={nav} secondary={address} />)
expect(container.querySelector('footer')).toBeInTheDocument()
})

it('renders primary content', () => {
const { getByRole } = render(<Footer primary={nav} secondary={address} />)
expect(getByRole('navigation')).toBeInTheDocument()
})

it('renders secondary content', () => {
const { container } = render(<Footer primary={nav} secondary={address} />)
expect(container.querySelector('address')).toBeInTheDocument()
})

it('renders return to top component', () => {
const { getByText } = render(
<Footer primary={nav} secondary={address} returnToTop={returnToTop} />
)
expect(getByText('Return To Top')).toBeInTheDocument()
})
})
56 changes: 56 additions & 0 deletions src/components/Footer/Footer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import React from 'react'
import classnames from 'classnames'

type FooterProps = {
big?: boolean
medium?: boolean
slim?: boolean
/**
* Component for "return to top" button/handling
*/
returnToTop?: React.ReactNode
/**
* Content in upper footer section, e.g. navigation, searchbar, signup form
*/
primary: React.ReactNode
/**
* Content in lower footer section, e.g. contact information
*/
secondary: React.ReactNode
}

// TODO: Add in "Return to Top" handling
export const Footer = (
props: FooterProps & React.HTMLAttributes<HTMLElement>
): React.ReactElement => {
const {
big,
medium,
slim,
returnToTop,
primary,
secondary,
...footerAttributes
} = props

const classes = classnames(
'usa-footer',
{
'usa-footer--big': big,
'usa-footer--medium': medium,
'usa-footer--slim': slim,
},
footerAttributes.className
)

return (
<footer {...footerAttributes} className={classes}>
{returnToTop && returnToTop}
<div className="usa-footer__primary-section">{primary}</div>

<div className="usa-footer__secondary-section">
<div className="grid-container">{secondary}</div>
</div>
</footer>
)
}
44 changes: 44 additions & 0 deletions src/components/Footer/Logo/Logo.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import React from 'react'

import { Logo } from './Logo'

export default {
title: 'Logo',
parameters: {
info: `
Used within USWDS 2.0 Footer component
Source: https://designsystem.digital.gov/components/form-controls/#footer
`,
},
}

export const Slim = (): React.ReactElement => (
<div className="usa-footer__secondary-section">
<Logo
slim
image={
<img
className="usa-footer__logo-img"
src="/logo-img.png"
alt="Mock logo"
/>
}
heading={<h3 className="usa-footer__logo-heading">Name of Agency</h3>}
/>
</div>
)

export const NoHeading = (): React.ReactElement => (
<div className="usa-footer__secondary-section">
<Logo
image={
<img
className="usa-footer__logo-img"
src="/logo-img.png"
alt="Mock logo"
/>
}
/>
</div>
)
30 changes: 30 additions & 0 deletions src/components/Footer/Logo/Logo.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from 'react'
import { render } from '@testing-library/react'

import { Logo } from './Logo'

const heading = <h3 className="usa-footer__logo-heading">Swoosh Branding</h3>
const logoImage = (
<img
className="usa-footer__logo-img"
src="src/components/Footer/Logo/logo-img.png"
alt="Nike logo"
/>
)

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

it('renders logo image', () => {
const { container } = render(<Logo image={logoImage} />)
expect(container.querySelector('img')).toBeInTheDocument()
})

it('renders heading when present', () => {
const { getByText } = render(<Logo image={logoImage} heading={heading} />)
expect(getByText('Swoosh Branding')).toBeInTheDocument()
})
})
Loading

0 comments on commit 4907181

Please sign in to comment.