diff --git a/src/features.ts b/src/features.ts new file mode 100644 index 00000000..ec98abeb --- /dev/null +++ b/src/features.ts @@ -0,0 +1,7 @@ +export interface Features { + settings: () => boolean +} + +export const availableFeatures: Features = { + settings: () => false, +} diff --git a/src/pages/__tests__/settings.test.tsx b/src/pages/__tests__/settings.test.tsx new file mode 100644 index 00000000..63af036b --- /dev/null +++ b/src/pages/__tests__/settings.test.tsx @@ -0,0 +1,16 @@ +import React from 'react' +import { render } from '@testing-library/react' +import Settings from '../settings' +import { TEST_ID as sidebarNavigationTestId } from 'src/ui/SidebarNavigation' + +describe('Library page', () => { + it('is displayed in a layout', () => { + const { baseElement } = render() + expect(baseElement.querySelector('.layout')).not.toBeNull() + }) + + it('displays the sidebar navigation', () => { + const { queryByTestId } = render() + expect(queryByTestId(sidebarNavigationTestId)).not.toBeNull() + }) +}) diff --git a/src/pages/settings.tsx b/src/pages/settings.tsx new file mode 100644 index 00000000..fbf6948d --- /dev/null +++ b/src/pages/settings.tsx @@ -0,0 +1,26 @@ +import React, { FC } from 'react' +import { HeadFC } from 'gatsby' +import { SkipNavContent } from '@reach/skip-nav' +import { Heading, Layout, PageContent, Paragraph, Sidebar, SpacedContainer } from 'src/ui/Layout' +import { SidebarNavigation } from 'src/ui/SidebarNavigation' + +const SettingsPage: FC = () => { + return ( + + + + + + + + Settings + Coming soon. + + + + ) +} + +export default SettingsPage + +export const Head: HeadFC = () => Settings diff --git a/src/testHelpers.tsx b/src/testHelpers.tsx index bc0b1dfa..395c5030 100644 --- a/src/testHelpers.tsx +++ b/src/testHelpers.tsx @@ -109,3 +109,7 @@ export const expectEmailPartContentFor = (key: string, element: HTMLElement) => const li = ul!.querySelector(`[data-testid="${key}"]`) expect(li).not.toBeNull() } + +export const asMock = any>(func: T): jest.Mock> => { + return func as any +} diff --git a/src/ui/Layout.css b/src/ui/Layout.css index fb962c95..402784c8 100644 --- a/src/ui/Layout.css +++ b/src/ui/Layout.css @@ -10,7 +10,7 @@ display: flex; flex: 1; flex-direction: column; - padding: 3.25rem 0; + padding-top: 3.25rem; } .spaced-sidebar-container { @@ -18,6 +18,8 @@ } .sidebar nav { + display: flex; + flex: 1; padding-top: 2.5rem; } @@ -35,9 +37,14 @@ font-size: 1.35rem; font-weight: var(--weight-bold); gap: 1.25rem; + flex: 1; line-height: 2rem; } +.sidebar-spacer { + flex: 1; +} + .sidebar-list a { color: var(--black); text-decoration: none; @@ -47,6 +54,21 @@ text-decoration: underline; } +.sidebar-list-item-bottom { + background-color: #d9d9d9; + padding: 1.5rem 0; +} + +.sidebar-list-item-bottom:has(a.sidebar-active-link) { + background-color: var(--blue); +} + +.sidebar-list-item-bottom a.sidebar-active-link { + border-bottom: 2px solid var(--white); + color: var(--white); + text-decoration: none; +} + .page-content { flex: 4; } diff --git a/src/ui/Layout.tsx b/src/ui/Layout.tsx index fe536ac2..b67a5cca 100644 --- a/src/ui/Layout.tsx +++ b/src/ui/Layout.tsx @@ -58,10 +58,19 @@ export const SideBarList: FC = ({ children }) => { interface SidebarListItemProps { children: ReactNode + className?: string +} + +export const SideBarListItem: FC = ({ children, className }) => { + return
  • {children}
  • +} + +interface SideBarListItemBottomProps { + children: ReactNode } -export const SideBarListItem: FC = ({ children }) => { - return
  • {children}
  • +export const SideBarListItemBottom: FC = ({ children }) => { + return {children} } interface HeadingProps { diff --git a/src/ui/SidebarNavigation.tsx b/src/ui/SidebarNavigation.tsx index fcd7f5f1..9a33b55f 100644 --- a/src/ui/SidebarNavigation.tsx +++ b/src/ui/SidebarNavigation.tsx @@ -1,6 +1,12 @@ -import React, { FC } from 'react' -import { SideBarList, SideBarListItem, SpacedSidebarContainer } from './Layout' +import React, { FC, ReactNode } from 'react' +import { + SideBarList, + SideBarListItem, + SideBarListItemBottom, + SpacedSidebarContainer, +} from './Layout' import { Link } from 'gatsby' +import { availableFeatures } from 'src/features' interface Props {} @@ -8,26 +14,38 @@ export const TEST_ID = 'sidebar-navigation' export const SidebarNavigation: FC = () => { return ( - - - + + ) +} + +interface SpacedLinkProps { + bottom?: boolean + children: ReactNode + to: string +} + +const SpacedLink: FC = ({ bottom, children, to }) => { + const Comp = bottom ? SideBarListItemBottom : SideBarListItem + + return ( + + + + {children} + + + ) } diff --git a/src/ui/__tests__/Layout.test.tsx b/src/ui/__tests__/Layout.test.tsx index a12a8f99..210f6502 100644 --- a/src/ui/__tests__/Layout.test.tsx +++ b/src/ui/__tests__/Layout.test.tsx @@ -9,6 +9,7 @@ import { Sidebar, SideBarList, SideBarListItem, + SideBarListItemBottom, SpacedContainer, SpacedSidebarContainer, } from '../Layout' @@ -154,6 +155,27 @@ describe('SideBarListItem', () => { ) expect(baseElement).toContainHTML(`
    ${text}
    `) }) + + it('accepts a className', () => { + const { baseElement } = render( + +
    + , + ) + expect(baseElement).toContainHTML('') + }) +}) + +describe('SideBarListItemBottom', () => { + it('displays its children', () => { + const text = faker.lorem.paragraph() + const { baseElement } = render( + +
    {text}
    +
    , + ) + expect(baseElement).toContainHTML(`
    ${text}
    `) + }) }) describe('Heading', () => { diff --git a/src/ui/__tests__/SidebarNavigation.test.tsx b/src/ui/__tests__/SidebarNavigation.test.tsx index 40a6cf12..8417733b 100644 --- a/src/ui/__tests__/SidebarNavigation.test.tsx +++ b/src/ui/__tests__/SidebarNavigation.test.tsx @@ -1,7 +1,16 @@ import { render } from '@testing-library/react' import React from 'react' import { SidebarNavigation } from '../SidebarNavigation' -import { urlFor } from 'src/testHelpers' +import { asMock, urlFor } from 'src/testHelpers' +import { availableFeatures, Features } from 'src/features' + +jest.mock('src/features', () => { + return { + availableFeatures: { + settings: jest.fn().mockReturnValue(true), + } as Features, + } +}) describe('SidebarNavigation', () => { it('displays a home link', () => { @@ -24,4 +33,28 @@ describe('SidebarNavigation', () => { expect(link.tagName).toEqual('A') expect(link.href).toEqual(urlFor('/tips-and-tricks')) }) + + describe('settings is available', () => { + beforeEach(() => { + asMock(availableFeatures.settings).mockReturnValue(true) + }) + + it('displays a settings link', () => { + const { getByText } = render() + const link: HTMLAnchorElement = getByText('Settings') as any + expect(link.tagName).toEqual('A') + expect(link.href).toEqual(urlFor('/settings')) + }) + }) + + describe('settings is not available', () => { + beforeEach(() => { + asMock(availableFeatures.settings).mockReturnValue(false) + }) + + it('does not display a settings link', () => { + const { queryByText } = render() + expect(queryByText('Settings')).toBeNull() + }) + }) }) diff --git a/src/utils/__tests__/useEmailTemplatesData.test.tsx b/src/utils/__tests__/useEmailTemplatesData.test.tsx index 348dc186..b573efe2 100644 --- a/src/utils/__tests__/useEmailTemplatesData.test.tsx +++ b/src/utils/__tests__/useEmailTemplatesData.test.tsx @@ -1,12 +1,13 @@ import { renderHook } from '@testing-library/react' import { useStaticQuery } from 'gatsby' import { useEmailTemplatesData } from '../useEmailTemplatesData' +import { asMock } from 'src/testHelpers' jest.unmock('../useEmailTemplatesData') describe('useEmailTemplatesData', () => { beforeEach(() => { - ;(useStaticQuery as any).mockImplementation((): Queries.EmailTemplatesDataQuery => { + asMock(useStaticQuery).mockImplementation((): Queries.EmailTemplatesDataQuery => { return { emailTemplates: { edges: [