diff --git a/packages/docs/src/components/layout.js b/packages/docs/src/components/layout.js
index 66c620cb9..e261f3ae7 100644
--- a/packages/docs/src/components/layout.js
+++ b/packages/docs/src/components/layout.js
@@ -1,6 +1,6 @@
/** @jsx jsx */
-import { jsx, Themed, useColorMode } from 'theme-ui'
-import { useState, useRef } from 'react'
+import { css, jsx, Themed, useColorMode } from 'theme-ui'
+import { useState, useRef, useEffect } from 'react'
import { Flex, Box } from '@theme-ui/components'
import { AccordionNav } from '@theme-ui/sidenav'
import { Link } from 'gatsby'
@@ -45,10 +45,12 @@ export default function DocsLayout(props) {
const nav = useRef(null)
const [mode, setMode] = useColorMode()
+ const { pathname } = props.location
+ const isLanding = pathname === '/'
+
const fullwidth =
- (props.pageContext.frontmatter &&
- props.pageContext.frontmatter.fullwidth) ||
- props.location.pathname === '/'
+ isLanding ||
+ (props.pageContext.frontmatter && props.pageContext.frontmatter.fullwidth)
const showNav = !props.pageContext?.frontmatter?.hidenav
@@ -62,16 +64,22 @@ export default function DocsLayout(props) {
sx={{
flexDirection: 'column',
minHeight: '100vh',
- }}>
+ }}
+ >
{showNav && (
+ position: isLanding ? 'initial' : 'sticky',
+ top: 0,
+ background: 'background',
+ }}
+ >
{
@@ -99,7 +107,8 @@ export default function DocsLayout(props) {
ml: 2,
whiteSpace: 'pre',
}}
- onClick={() => setMode(nextColorMode)}>
+ onClick={() => setMode(nextColorMode)}
+ >
{getModeName(mode)}
@@ -112,7 +121,8 @@ export default function DocsLayout(props) {
alignItems: 'flex-start',
display: ['block', 'flex'],
height: '100%',
- }}>
+ }}
+ >
-
- {props.children}
-
- {!fullwidth && }
-
+
+ position: 'relative',
+ }}
+ >
+ {!isLanding && }
+
+ {props.children}
+
+ {!fullwidth && }
+
+
)
}
+
+function HeaderScrollShadow() {
+ const ref = useRef()
+
+ useEffect(() => {
+ const onScroll = () => {
+ const { current } = ref
+ if (current) {
+ current.style.opacity = window.scrollY > 0 ? 1 : 0
+ }
+ }
+
+ window.addEventListener('scroll', onScroll)
+ return () => window.removeEventListener('scroll', onScroll)
+ }, [])
+
+ return (
+
+ )
+}
diff --git a/packages/e2e/integration/docs-navigation.ts b/packages/e2e/integration/docs-navigation.ts
new file mode 100644
index 000000000..ea2576ad4
--- /dev/null
+++ b/packages/e2e/integration/docs-navigation.ts
@@ -0,0 +1,73 @@
+describe('docs navigation', () => {
+ it('works without 404', () => {
+ cy.visit('/')
+ cy.findByText('Documentation').click()
+ cy.location().should('have.property', 'pathname', '/getting-started')
+ cy.findByText('Theming').click()
+ cy.get('h1').should('have.text', 'Theming')
+ cy.findAllByRole('link').then(($links) => {
+ const links = $links.get()
+ const texts = links.map((link) => link.textContent)
+
+ const expectedLinkTexts = [
+ 'Hooks',
+ 'API',
+ 'Theme Specification',
+ 'Demo',
+ 'Resources',
+ 'Components',
+ 'Packages',
+ 'Guides',
+ 'Recipes',
+ 'Migrating',
+ 'Edit the page on GitHub',
+ 'Previous:Getting Started with Gatsby',
+ ]
+
+ for (const s of expectedLinkTexts) {
+ expect(texts).to.include(s)
+ }
+
+ const nextChapterLink = links.find(
+ (link) => link.textContent === 'Next:The sx Prop'
+ )!
+
+ nextChapterLink.click()
+
+ const packagesLink = links.find(
+ (link) => link.textContent === 'Packages'
+ )!
+
+ packagesLink.click()
+ })
+
+ for (const packageName of [
+ 'css',
+ 'core',
+ 'components',
+ 'presets',
+ 'color',
+ ]) {
+ cy.findAllByText('@theme-ui/' + packageName, { selector: 'li > a' })
+ .first()
+ .click()
+ cy.location().should(
+ 'have.property',
+ 'pathname',
+ `/packages/${packageName}`
+ )
+ }
+
+ cy.window().then((win) => win.scrollTo(0, 200))
+
+ cy.percySnapshot('@theme-ui/color docs')
+ })
+
+ it('displays 404 page', () => {
+ cy.visit(`/not-found-${Math.random()}`, { failOnStatusCode: false })
+ cy.findByRole('heading').should('have.text', '404')
+ cy.findByText('Page not found')
+ })
+})
+
+export {}