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: [