diff --git a/src/components/Footer/Address/Address.stories.tsx b/src/components/Footer/Address/Address.stories.tsx
index 19ad7d385a..d9cde1ae50 100644
--- a/src/components/Footer/Address/Address.stories.tsx
+++ b/src/components/Footer/Address/Address.stories.tsx
@@ -2,10 +2,10 @@ import React from 'react'
import { Address } from './Address'
export default {
- title: 'Address',
+ title: 'Footer/Address',
parameters: {
info: `
- Used within USWDS 2.0 Footer component
+ Display address items (most likely links or simple text) in a row, wrapped in address tag. Used in USWDS 2.0 Footer component.
Source: https://designsystem.digital.gov/components/form-controls/#footer
`,
diff --git a/src/components/Footer/Footer.stories.tsx b/src/components/Footer/Footer.stories.tsx
index c77187f5fe..219c172c01 100644
--- a/src/components/Footer/Footer.stories.tsx
+++ b/src/components/Footer/Footer.stories.tsx
@@ -6,10 +6,11 @@ import { Button } from '../Button/Button'
import { Footer } from './Footer'
import { FooterNav } from './FooterNav/FooterNav'
import { Logo } from './Logo/Logo'
+import { SignUpForm } from './SignUpForm/SignUpForm'
import { SocialLinks } from './SocialLinks/SocialLinks'
export default {
- title: 'Footer',
+ title: 'Footer/Footer',
parameters: {
info: `
USWDS 2.0 Footer component
@@ -19,6 +20,10 @@ export default {
},
}
+const mockSubmit = (): void => {
+ /* mock submit fn */
+}
+
const returnToTop = (
+ }
+ secondary={
+
+
+ }
+ heading={
Name of Agency
}
+ />
+
+
+ }
+ />
+)
diff --git a/src/components/Footer/FooterNav/FooterNav.stories.tsx b/src/components/Footer/FooterNav/FooterNav.stories.tsx
index 87f73408a3..d8861d64f2 100644
--- a/src/components/Footer/FooterNav/FooterNav.stories.tsx
+++ b/src/components/Footer/FooterNav/FooterNav.stories.tsx
@@ -4,10 +4,10 @@ import React from 'react'
import { FooterNav } from './FooterNav'
export default {
- title: 'FooterNav',
+ title: 'Footer/FooterNav',
parameters: {
info: `
- Used in USWDS 2.0 Footer component
+ Display single list of nav items, or grouped nav items in an extended nav. Used in USWDS 2.0 Footer component.
Source: https://designsystem.digital.gov/components/form-controls/#footer
`,
@@ -35,3 +35,44 @@ export const MediumFooterNav = (): React.ReactElement => (
)}
/>
)
+
+export const BigFooterNav = (): React.ReactElement => (
+
+ Topic
+ ,
+ ...Array(3).fill(
+
+ Secondary link
+
+ ),
+ ],
+ [
+
+ Topic
+
,
+
+ Secondary link that is pretty long
+ ,
+ ...Array(2).fill(
+
+ Secondary link
+
+ ),
+ ],
+ [
+
+ Topic
+
,
+ ...Array(3).fill(
+
+ Secondary link
+
+ ),
+ ],
+ ]}
+ />
+)
diff --git a/src/components/Footer/FooterNav/FooterNav.test.tsx b/src/components/Footer/FooterNav/FooterNav.test.tsx
index fb671f1f1a..1d5d64f438 100644
--- a/src/components/Footer/FooterNav/FooterNav.test.tsx
+++ b/src/components/Footer/FooterNav/FooterNav.test.tsx
@@ -1,4 +1,5 @@
-/* eslint-disable jsx-a11y/anchor-is-valid */
+/* eslint-disable jsx-a11y/anchor-is-valid, react/jsx-key */
+
import React from 'react'
import { render } from '@testing-library/react'
@@ -9,6 +10,26 @@ const links = Array(4).fill(
Primary Link
)
+
+const extendedLinks = [
+ [
+ 'Types of Cats',
+ ...Array(2).fill(
+
+ Cheetah
+
+ ),
+ ],
+ [
+ 'Musical Gifts',
+ ...Array(3).fill(
+
+ Purple Rain
+
+ ),
+ ],
+]
+
describe('FooterNav component', () => {
it('renders without errors', () => {
const { getByRole } = render()
@@ -20,4 +41,24 @@ describe('FooterNav component', () => {
expect(container.querySelectorAll('a').length).toBe(4)
expect(getAllByText('Primary Link').length).toBe(4)
})
+
+ it('renders links with "big" prop', () => {
+ const { container, getAllByText } = render()
+ expect(container.querySelectorAll('a').length).toBe(4)
+ expect(getAllByText('Primary Link').length).toBe(4)
+ })
+
+ it('renders extended links with "big" prop', () => {
+ const { container, getAllByText } = render(
+
+ )
+ expect(container.querySelectorAll('a').length).toBe(5)
+ expect(getAllByText('Purple Rain').length).toBe(3)
+ expect(getAllByText('Cheetah').length).toBe(2)
+ })
+
+ it('does not render extended nav links without "big" prop', () => {
+ const { container } = render()
+ expect(container.querySelectorAll('a').length).toBe(0)
+ })
})
diff --git a/src/components/Footer/FooterNav/FooterNav.tsx b/src/components/Footer/FooterNav/FooterNav.tsx
index 97f380b332..4997bf1b73 100644
--- a/src/components/Footer/FooterNav/FooterNav.tsx
+++ b/src/components/Footer/FooterNav/FooterNav.tsx
@@ -1,36 +1,95 @@
import React from 'react'
import classnames from 'classnames'
+type ExtendedNavLinks = [React.ReactNode[]]
+
type FooterNavProps = {
big?: boolean
medium?: boolean
slim?: boolean
- links: React.ReactNode[]
+ /*
+ Union type. Array of navigation links or multidimensional array of ExtendedNavLinks.
+ ExtendedNavLinks are ordered sub arrays that will be displayed as columns, with the first element used as the section heading.
+ ExtendedNavLinks can only be used with "big" prop size
+ */
+ links: React.ReactNode[] | ExtendedNavLinks
+}
+
+function isExtendedNavLinks(
+ links: React.ReactNode[] | ExtendedNavLinks
+): links is ExtendedNavLinks {
+ return (links as ExtendedNavLinks)[0].constructor === Array
}
export const FooterNav = (
props: FooterNavProps & React.HTMLAttributes
): React.ReactElement => {
- const { medium, slim, links, ...elementAttributes } = props
+ const { big, medium, slim, links, ...elementAttributes } = props
- const navClasses = classnames(`usa-footer__nav`, elementAttributes.className)
const listItemClasses = classnames(
'desktop:grid-col-auto usa-footer__primary-content',
{
- 'mobile-lg:grid-col-4': medium,
+ 'mobile-lg:grid-col-4': big || medium,
'mobile-lg:grid-col-6': slim,
}
)
return (
-
+
+ )
+}
+
+const ExtendedNav = ({
+ nestedLinks,
+}: {
+ nestedLinks: ExtendedNavLinks
+}): React.ReactElement => {
+ return (
+
+ {nestedLinks.map((links, i) => (
+
+
+
+ ))}
+
)
}
diff --git a/src/components/Footer/Logo/Logo.stories.tsx b/src/components/Footer/Logo/Logo.stories.tsx
index d07d818c45..220caaa86c 100644
--- a/src/components/Footer/Logo/Logo.stories.tsx
+++ b/src/components/Footer/Logo/Logo.stories.tsx
@@ -3,10 +3,10 @@ import React from 'react'
import { Logo } from './Logo'
export default {
- title: 'Logo',
+ title: 'Footer/Logo',
parameters: {
info: `
- Used within USWDS 2.0 Footer component
+ Display logo image with optional heading. Used in USWDS 2.0 Footer component.
Source: https://designsystem.digital.gov/components/form-controls/#footer
`,
diff --git a/src/components/Footer/SignUpForm/SignUpForm.stories.tsx b/src/components/Footer/SignUpForm/SignUpForm.stories.tsx
new file mode 100644
index 0000000000..9a6b1b1c23
--- /dev/null
+++ b/src/components/Footer/SignUpForm/SignUpForm.stories.tsx
@@ -0,0 +1,24 @@
+import React from 'react'
+import { SignUpForm } from './SignUpForm'
+
+export default {
+ title: 'Footer/SignUpForm',
+ parameters: {
+ info: `
+ Form with a single input and submit button. Used in USWDS 2.0 Footer component.
+
+ Source: https://designsystem.digital.gov/components/form-controls/#footer
+ `,
+ },
+}
+
+const mockSubmit = (): void => {
+ /* mock submit fn */
+}
+export const Example = (): React.ReactElement => (
+
+)
diff --git a/src/components/Footer/SignUpForm/SignUpForm.test.tsx b/src/components/Footer/SignUpForm/SignUpForm.test.tsx
new file mode 100644
index 0000000000..ace927a8ed
--- /dev/null
+++ b/src/components/Footer/SignUpForm/SignUpForm.test.tsx
@@ -0,0 +1,76 @@
+import React from 'react'
+import { render, fireEvent } from '@testing-library/react'
+
+import { SignUpForm } from './SignUpForm'
+
+describe('SignUpForm component', () => {
+ const emptySubmit = (): void => {
+ /* mock submit fn */
+ }
+
+ it('renders without errors', () => {
+ const { queryByTestId } = render(
+
+ )
+
+ expect(queryByTestId('form')).toBeInTheDocument()
+ })
+
+ it('renders expected form elements', () => {
+ const { getByRole } = render(
+
+ )
+ expect(getByRole('heading')).toBeInTheDocument()
+ expect(getByRole('button')).toBeInTheDocument()
+ expect(getByRole('textbox')).toBeInTheDocument()
+ })
+
+ it('renders email input by default', () => {
+ const { container } = render(
+
+ )
+
+ const input = container.querySelector('input')
+ expect(input && input.type).toBe('email')
+ })
+
+ it('renders input with type prop', () => {
+ const { container } = render(
+
+ )
+
+ const input = container.querySelector('input')
+ expect(input && input.type).toBe('number')
+ })
+
+ it('implements an onSubmit handler', () => {
+ const mockSubmit = jest.fn()
+ const { getByTestId } = render(
+
+ )
+
+ fireEvent.submit(getByTestId('form'))
+ expect(mockSubmit).toHaveBeenCalledTimes(1)
+ })
+})
diff --git a/src/components/Footer/SignUpForm/SignUpForm.tsx b/src/components/Footer/SignUpForm/SignUpForm.tsx
new file mode 100644
index 0000000000..33756cb00a
--- /dev/null
+++ b/src/components/Footer/SignUpForm/SignUpForm.tsx
@@ -0,0 +1,34 @@
+import React from 'react'
+
+import { Button } from '../../Button/Button'
+import { Form } from '../../forms/Form/Form'
+import { Label } from '../../forms/Label/Label'
+import { TextInput, TextInputProps } from '../../forms/TextInput/TextInput'
+
+type SignUpFormProps = {
+ heading: React.ReactNode
+ label: React.ReactNode
+ submitButtonText?: React.ReactNode
+ type?: TextInputProps['type']
+ onSubmit: () => void
+}
+
+export const SignUpForm = ({
+ heading,
+ label,
+ submitButtonText = 'Sign up',
+ type = 'email',
+ onSubmit,
+ ...elementAttributes
+}: SignUpFormProps & React.HTMLAttributes): React.ReactElement => {
+ return (
+
+
{heading}
+
+
+ )
+}
diff --git a/src/components/Footer/SocialLinks/SocialLinks.stories.tsx b/src/components/Footer/SocialLinks/SocialLinks.stories.tsx
index ac27efd544..ff146f55c9 100644
--- a/src/components/Footer/SocialLinks/SocialLinks.stories.tsx
+++ b/src/components/Footer/SocialLinks/SocialLinks.stories.tsx
@@ -2,6 +2,17 @@
import React from 'react'
import { SocialLinks } from './SocialLinks'
+export default {
+ title: 'Footer/SocialLinks',
+ parameters: {
+ info: `
+ Display social links in styled row. Used in USWDS 2.0 Footer component.
+
+ Source: https://designsystem.digital.gov/components/form-controls/#footer
+ `,
+ },
+}
+
const links = [