diff --git a/apps/docs/.vitepress/config.mts b/apps/docs/.vitepress/config.mts index da525f97..03ca975e 100644 --- a/apps/docs/.vitepress/config.mts +++ b/apps/docs/.vitepress/config.mts @@ -25,7 +25,6 @@ function prefixLinks(items: any[], locale: string): any[] { // with no mapping (e.g. the version item) pass through unchanged. The nav is a // flat list, so no recursion is needed here. const NAV_KEY: Record = { - Home: "nav.home", Guide: "nav.guide", Tools: "nav.tools", "API Reference": "nav.apiReference", @@ -286,7 +285,9 @@ function buildBaseTheme() { logo: "/logo.png", nav: [ - { text: "Home", link: "/" }, + // No "Home" item: the logo already links home, and dropping it keeps the + // nav within the viewport at 768px (see github-stars.css for the wider + // responsive-nav fix). { text: "Guide", link: "/guide/getting-started" }, { text: "Tools", link: "/tools/image/resize" }, { text: "API Reference", link: "/api/rest" }, diff --git a/apps/docs/.vitepress/theme/github-stars.css b/apps/docs/.vitepress/theme/github-stars.css index dccdc1bb..68530298 100644 --- a/apps/docs/.vitepress/theme/github-stars.css +++ b/apps/docs/.vitepress/theme/github-stars.css @@ -1,19 +1,40 @@ -/* Hide the three-dots extra menu - its contents are now in the navbar directly */ -.VPNavBarExtra { - display: none !important; -} - -/* Also hide the default appearance toggle since we render our own */ -.VPNavBarAppearance { - display: none !important; -} - -/* Right side of navbar: toggle + GitHub button */ +/* Right-side custom nav cluster: our own appearance toggle + Fund + GitHub Star. + VitePress's default theme collapses appearance/social/translations into a + compact three-dots flyout below 1280px and shows them inline at >=1280px, so + the stock nav never overflows. Our cluster is wider than that native inline + set, so forcing it inline at every width pushed the nav past the viewport: a + horizontal page scrollbar in the 768-959px band (the nav is in normal flow + there) and off-screen clipping of the buttons on 1280-1366px laptops (the nav + is position:fixed at >=960px, so it clips instead of scrolling). Fix: show our + cluster only once the layout reaches its max width (1440px), where it fits, and + defer to VitePress's native responsive nav below that (three-dots flyout under + 1280px, inline appearance + social from 1280-1439px). */ .nav-bar-right { - display: flex; - align-items: center; - gap: 12px; - margin-left: 16px; + display: none; +} + +@media (min-width: 1440px) { + .nav-bar-right { + display: flex; + align-items: center; + gap: 12px; + margin-left: 16px; + } + + /* Our cluster carries the appearance toggle at this width, so hide VitePress's + own inline toggle to avoid a duplicate. */ + .VPNavBarAppearance { + display: none !important; + } +} + +/* VitePress anchors flyout dropdowns with a physical `right: 0`, which does not + flip for RTL. In RTL the three-dots menu then opens off-screen to the left and + adds horizontal overflow even while closed (it is position:absolute). Anchor it + to the start edge so it opens inward instead. */ +:root[dir="rtl"] .VPNavBarExtra .menu { + right: auto; + left: 0; } /* GitHub button group */ @@ -118,11 +139,3 @@ background: rgba(244, 114, 182, 0.15); border-color: rgba(244, 114, 182, 0.4); } - -/* Hide stars and fund button on small screens, keep toggle */ -@media (max-width: 640px) { - .github-btn-wrapper, - .fund-btn { - display: none; - } -} diff --git a/tests/e2e-docs/search-and-theme.spec.ts b/tests/e2e-docs/search-and-theme.spec.ts index a9181740..4a484d3d 100644 --- a/tests/e2e-docs/search-and-theme.spec.ts +++ b/tests/e2e-docs/search-and-theme.spec.ts @@ -42,10 +42,11 @@ test.describe("Theme Toggle", () => { const html = page.locator("html"); const wasDark = ((await html.getAttribute("class")) ?? "").includes("dark"); - // Click the visible toggle inside our custom nav area (the hidden default - // VPNavBarAppearance is first in DOM order, so a bare querySelector would - // hit it instead). - await page.locator('.nav-bar-right button[role="switch"]').click(); + // Click whichever theme toggle is actually visible at this viewport: below + // 1440px the nav uses VitePress's native inline toggle, at >=1440px our + // custom cluster toggle takes over. Filtering to :visible skips the hidden + // duplicates that sit earlier in DOM order. + await page.locator('button[role="switch"]:visible').first().click(); // Assert the dark class actually toggled if (wasDark) { @@ -57,6 +58,11 @@ test.describe("Theme Toggle", () => { }); test.describe("GitHub Stars Component", () => { + // The custom nav cluster (toggle + Fund + GitHub Star) only renders at + // >=1440px, where it fits without overflowing the nav. Below that the nav + // defers to VitePress's native responsive layout, so pin a wide desktop here. + test.use({ viewport: { width: 1500, height: 900 } }); + test("GitHub star button is visible in navbar", async ({ page }) => { await page.goto("/"); const githubBtn = page.locator(".github-btn, .github-btn-wrapper").first(); @@ -70,3 +76,22 @@ test.describe("GitHub Stars Component", () => { await expect(starLink).toHaveAttribute("target", "_blank"); }); }); + +// Regression guard for the nav overflow reported in #556. The custom nav cluster +// used to render inline at every width, pushing the nav past narrow viewports +// (a horizontal scrollbar at 768-959px, off-screen clipping at 1280-1366px). +test.describe("Nav has no horizontal overflow", () => { + for (const width of [768, 834, 900, 1024, 1280, 1366]) { + test(`no horizontal scroll at ${width}px`, async ({ page }) => { + await page.setViewportSize({ width, height: 900 }); + await page.goto("/guide/getting-started"); + await waitForHydration(page); + const overflow = await page.evaluate(() => { + const de = document.documentElement; + return de.scrollWidth - de.clientWidth; + }); + // Allow a 1px sub-pixel rounding margin; the bug produced 300px+ scroll. + expect(overflow).toBeLessThanOrEqual(1); + }); + } +});