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);
+});

Reply via email to