This is an automated email from the ASF dual-hosted git repository.
guoqqqi pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix-website.git
The following commit(s) were added to refs/heads/master by this push:
new 00f1e9fc40d fix(css): restore container padding and surface the mobile
docs nav (#2096)
00f1e9fc40d is described below
commit 00f1e9fc40dcb3b289514a9b13ca82712a006cee
Author: Yuhan <[email protected]>
AuthorDate: Thu Aug 6 09:04:10 2026 +0800
fix(css): restore container padding and surface the mobile docs nav (#2096)
---
next/src/styles/global.css | 14 ++--
next/tests/e2e/docs-mobile-layout.spec.mjs | 115 +++++++++++++++++++++++++++++
2 files changed, 123 insertions(+), 6 deletions(-)
diff --git a/next/src/styles/global.css b/next/src/styles/global.css
index e5fbdf11c0e..320b015d8c6 100644
--- a/next/src/styles/global.css
+++ b/next/src/styles/global.css
@@ -206,7 +206,7 @@ a.tag:focus-visible { outline: 2px solid
var(--color-primary); outline-offset: 2
.pagination a:hover { border-color: var(--color-primary); text-decoration:
none; }
/* ---------- article / prose ---------- */
-.article-wrap { display: grid; grid-template-columns: minmax(0, 720px);
justify-content: center; padding: 2.5rem 0; }
+.article-wrap { display: grid; grid-template-columns: minmax(0, 720px);
justify-content: center; padding-block: 2.5rem; }
/* ---------- article side rails (wide screens) ---------- */
/* Left: tags + back. Right: on-this-page TOC. Both sticky, both hidden below
@@ -321,7 +321,7 @@ a.tag:focus-visible { outline: 2px solid
var(--color-primary); outline-offset: 2
.admonition-important { border-color: #a25ddc; background: #f4eefb; }
/* ---------- docs layout ---------- */
-.docs-layout { display: grid; grid-template-columns: 280px minmax(0, 1fr);
gap: 2.5rem; align-items: start; padding: 2rem 0; }
+.docs-layout { display: grid; grid-template-columns: 280px minmax(0, 1fr);
gap: 2.5rem; align-items: start; padding-block: 2rem; }
.docs-sidebar {
position: sticky; top: calc(var(--header-height) + 1rem);
max-height: calc(100vh - var(--header-height) - 2rem);
@@ -338,11 +338,13 @@ a.tag:focus-visible { outline: 2px solid
var(--color-primary); outline-offset: 2
.docs-content { min-width: 0; padding-bottom: 3rem; }
.docs-meta { border-top: 1px solid var(--color-border); margin-top: 2.5rem;
padding-top: 1rem; font-size: .85rem; color: var(--color-text-soft); display:
flex; gap: 1rem; flex-wrap: wrap; }
@media (max-width: 960px) {
- /* Content first on phones: the full link tree is ~7 screens tall, so it
- moves below the article (grid order) and scrolls within a capped box.
- minmax(0,1fr) keeps long code lines from inflating the column. */
+ /* The nav stays in document order, above the article. It used to be pushed
+ below it, which put the link tree — and the version picker inside it —
+ roughly seven screens down, with only ~4% of the tree visible once you got
+ there. Capping the box at 30vh keeps the article's own heading on the
+ first screen. minmax(0,1fr) keeps long code lines from inflating the
column. */
.docs-layout { grid-template-columns: minmax(0, 1fr); }
- .docs-sidebar { order: 2; position: static; max-height: 45vh; max-height:
45dvh; overflow-y: auto; border: 1px solid var(--color-border); border-radius:
8px; padding: .75rem; }
+ .docs-sidebar { position: static; max-height: 30vh; max-height: 30dvh;
overflow-y: auto; border: 1px solid var(--color-border); border-radius: 8px;
padding: .75rem; }
}
/* ---------- homepage extras ---------- */
diff --git a/next/tests/e2e/docs-mobile-layout.spec.mjs
b/next/tests/e2e/docs-mobile-layout.spec.mjs
new file mode 100644
index 00000000000..5f44e204020
--- /dev/null
+++ b/next/tests/e2e/docs-mobile-layout.spec.mjs
@@ -0,0 +1,115 @@
+import { expect, test } from '@playwright/test';
+
+/** Computed horizontal padding of the first element matching `selector`. */
+async function inlinePadding(page, selector) {
+ return page.locator(selector).first().evaluate((el) => {
+ const cs = getComputedStyle(el);
+ return { left: parseFloat(cs.paddingLeft), right:
parseFloat(cs.paddingRight) };
+ });
+}
+
+/**
+ * Discover a post from the blog index rather than naming one. A hardcoded URL
+ * turns an unrelated content change into a broken test; the index is the same
+ * thing a reader would follow. `/blog/20…` matches dated post URLs only —
+ * `/blog/page/`, `/blog/archive/` and `/blog/tags/` do not start that way.
+ */
+async function firstBlogPost(page) {
+ await page.goto('/blog/');
+ const href = await
page.locator('a[href^="/blog/20"]').first().getAttribute('href');
+ expect(href, 'the blog index must list at least one post').toBeTruthy();
+ return href;
+}
+
+/** Padding restored and nav ahead of the article, for any docs page. */
+async function assertDocsLayout(page, url) {
+ await page.goto(url);
+
+ const pad = await inlinePadding(page, '.docs-layout');
+ expect(pad.left, `${url}: .docs-layout must not zero out .container
padding`).toBeGreaterThan(0);
+ expect(pad.right).toBeGreaterThan(0);
+
+ // The header was always correct, so it is the reference the article should
+ // match — but only once the layout stacks; see the breakpoint note below.
+ const geom = await page.evaluate(() => ({
+ viewport: window.innerWidth,
+ h1Left: document.querySelector('.docs-content
h1').getBoundingClientRect().left,
+ brandLeft: document.querySelector('.site-header
.brand').getBoundingClientRect().left,
+ navTop: document.querySelector('.docs-sidebar').offsetTop,
+ articleTop: document.querySelector('.docs-content').offsetTop,
+ }));
+
+ expect(geom.h1Left, `${url}: article text must not touch the viewport
edge`).toBeGreaterThan(0);
+
+ // Both remaining checks are breakpoint-dependent, and 960px is the line
+ // where .docs-layout collapses to one column (the max-width: 960px media
+ // query in global.css).
+ if (geom.viewport <= 960) {
+ // Stacked: the article shares the container's inline padding with the
+ // header, so their left edges line up.
+ expect(Math.abs(geom.h1Left - geom.brandLeft),
+ `${url}: article should line up with the header
brand`).toBeLessThanOrEqual(1);
+ // Stacked: the nav must precede the article. This is the defect — `order:
2`
+ // used to push it below.
+ expect(geom.navTop, `${url}: the docs nav must come before the article`)
+ .toBeLessThan(geom.articleTop);
+ } else {
+ // Side by side: nav and article are grid items on the same row, so their
+ // offsetTop is EQUAL. Measured on production at 1440px: both 132.
+ // Asserting `toBeLessThan` here would be unsatisfiable — and asserting
+ // equality is the guard that catches `order` leaking out of the media
+ // query and stacking the desktop layout.
+ expect(geom.navTop, `${url}: nav and article should share a grid row`)
+ .toBe(geom.articleTop);
+ }
+}
+
+// docs/general/** ships from this repo, so it exists in the PR CI build too —
+// no gate, and the fix is verified before anything is deployed.
+test('general docs keep padding and put the nav above the article', async ({
page }) => {
+ await assertDocsLayout(page, '/docs/general/contributor-guide/');
+});
+
+// Same assertions over the 200-link apisix tree that motivated the report.
+// Gated: apisix docs need .sync/ checkouts only the deploy pipeline has.
+test('apisix docs keep padding and put the nav above the article', async ({
page }) => {
+ test.skip(
+ process.env.EXPECT_DOCUSARUS_ROUTES !== 'true',
+ 'apisix docs only exist in the final overlaid tree',
+ );
+ await assertDocsLayout(page, '/docs/apisix/getting-started/README/');
+});
+
+test('blog posts keep their horizontal padding', async ({ page }) => {
+ await page.goto(await firstBlogPost(page));
+ const pad = await inlinePadding(page, '.article-wrap');
+ expect(pad.left, 'blog posts share the .article-wrap
defect').toBeGreaterThan(0);
+ expect(pad.right).toBeGreaterThan(0);
+});
+
+test('desktop keeps the three-column article rails', async ({ page }) => {
+ test.skip(test.info().project.name !== 'desktop-chrome', 'Rails only exist
at >=1240px');
+
+ await page.goto(await firstBlogPost(page));
+
+ // Assert the rails exist before measuring them, so a post that legitimately
+ // has none fails loudly here instead of silently passing a vacuous check.
+ const rails = page.locator('.article-wrap.with-rails');
+ await expect(rails, 'the discovered post should render the rails
layout').toHaveCount(1);
+
+ const tracks = await rails.evaluate((el) =>
+
getComputedStyle(el).gridTemplateColumns.split(/\s+/).filter(Boolean).map(parseFloat));
+
+ expect(tracks.length, 'the rails grid must stay three columns').toBe(3);
+
+ // The count alone is VACUOUS and must not be the only assertion here.
+ // `.with-rails` uses an explicit template (190px minmax(0,760px) 230px), so
+ // computed gridTemplateColumns always reports three tracks no matter how
+ // narrow the container gets. Measured on production: forcing the wrapper to
+ // 600px still reports 3 tracks — as "190px 44px 230px", with the reading
+ // column crushed. Restoring the inline padding shrinks the middle track, it
+ // never removes one, so track WIDTH is the only thing worth guarding.
+ // Design target is 760px; the rule's own comment allows ~680px at 1240.
+ expect(tracks[1], 'the reading column must not be squeezed by the padding
fix')
+ .toBeGreaterThan(700);
+});