diff --git a/.lycheeignore b/.lycheeignore index 96201a040e..9d4e3c3667 100644 --- a/.lycheeignore +++ b/.lycheeignore @@ -27,3 +27,6 @@ dist/(.*/)?404((/index)?\.html|/)$ # Oracle pages return 403 when connected ^https://www\.mysql\.com/ ^https://www\.oracle\.com/ + +# NVD pages time out when requested from CI runners +^https://nvd\.nist\.gov/ diff --git a/docs/design-system.md b/docs/design-system.md index ea6d7bde81..52827a16af 100644 --- a/docs/design-system.md +++ b/docs/design-system.md @@ -91,3 +91,13 @@ Colors are defined using OKLCH color space with `light-dark()` for theme switchi | `xs` | < 768px | `--xs-only` | | `md` | 768px – 1439px | `--md-only`, `--md-up` | | `lg` | >= 1440px | `--lg-up`, `--lg-down` | + +## Doc Bottom Navigation + +`` closes a doc page with links to the previous and next page in a single +flex row. The row uses `flex-wrap: wrap-reverse`, so when the two titles cannot sit side +by side the _Next_ link wraps onto its own line above _Previous_ instead of overflowing +the page. On wider screens both links stay side by side on one line. + +The DOM order stays previous-then-next, so keyboard and screen reader order is unchanged +when the row wraps. diff --git a/src/components/patterns/DocBottomNav/DocBottomNav.css b/src/components/patterns/DocBottomNav/DocBottomNav.css index ea09543826..40d0c0585e 100644 --- a/src/components/patterns/DocBottomNav/DocBottomNav.css +++ b/src/components/patterns/DocBottomNav/DocBottomNav.css @@ -1,6 +1,10 @@ @layer patterns { .doc-nav { display: flex; + /* Wrap in reverse so that when the two titles cannot sit side by side the + "Next" link moves onto its own line above "Previous" instead of + overflowing the page on narrow viewports. */ + flex-wrap: wrap-reverse; justify-content: space-between; gap: var(--space-4); margin-top: var(--space-8); diff --git a/tests/e2e/doc-bottom-nav.spec.ts b/tests/e2e/doc-bottom-nav.spec.ts new file mode 100644 index 0000000000..91900c9681 --- /dev/null +++ b/tests/e2e/doc-bottom-nav.spec.ts @@ -0,0 +1,54 @@ +import { test, expect } from '@playwright/test'; + +// Regression coverage for https://github.com/expressjs/expressjs.com/issues/2486 +const DOC_PATH = '/en/guide/migrating-4/'; + +test.describe('Doc bottom navigation', () => { + test('should not overflow horizontally on a narrow viewport', async ({ page }) => { + await page.setViewportSize({ width: 400, height: 900 }); + await page.goto(DOC_PATH); + + const nav = page.locator('.doc-nav'); + await expect(nav).toBeVisible(); + + const overflow = await nav.evaluate((el) => el.scrollWidth - el.clientWidth); + expect(overflow).toBeLessThanOrEqual(1); + }); + + test('should move the next link above the previous link when they cannot share a line', async ({ + page, + }) => { + await page.setViewportSize({ width: 400, height: 900 }); + await page.goto(DOC_PATH); + + const prev = page.locator('.doc-nav__link--prev'); + const next = page.locator('.doc-nav__link--next'); + + await expect(prev).toBeVisible(); + await expect(next).toBeVisible(); + + const prevBox = await prev.boundingBox(); + const nextBox = await next.boundingBox(); + + if (!prevBox || !nextBox) { + throw new Error('expected both doc nav links to be laid out'); + } + + expect(nextBox.y + nextBox.height).toBeLessThanOrEqual(prevBox.y); + }); + + test('should keep both links on a single line on a wide viewport', async ({ page }) => { + await page.setViewportSize({ width: 1400, height: 900 }); + await page.goto(DOC_PATH); + + const prevBox = await page.locator('.doc-nav__link--prev').boundingBox(); + const nextBox = await page.locator('.doc-nav__link--next').boundingBox(); + + if (!prevBox || !nextBox) { + throw new Error('expected both doc nav links to be laid out'); + } + + expect(nextBox.y).toBeCloseTo(prevBox.y, 0); + expect(nextBox.x).toBeGreaterThan(prevBox.x); + }); +});