From 7493ec48e453749cc48c3c8a55a81feb57996114 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Thu, 8 Oct 2026 16:20:26 +0200 Subject: [PATCH] Fit the sidebar rail to short windows and hide its native scrollbar On a 13-inch laptop the rail's list overflowed and a classic scrollbar with arrow buttons appeared inside it. The rows now step down by window height (gaps close at 940 px, 36 px rows at 820 px, 32 px at 740 px), so 1366 x 768 fits every entry collapsed and expanded. Where the list still scrolls, the native scrollbar is hidden, fades mark the edge that has more, and a focused row scrolls clear of the fades. --- e2e/sidebar-expand-no-scrollbar.spec.ts | 36 ++++++ src/components/layout/sidebar-nav.tsx | 142 ++++++++++++++++++++---- 2 files changed, 156 insertions(+), 22 deletions(-) diff --git a/e2e/sidebar-expand-no-scrollbar.spec.ts b/e2e/sidebar-expand-no-scrollbar.spec.ts index 71b8d73e8..cb136e1df 100644 --- a/e2e/sidebar-expand-no-scrollbar.spec.ts +++ b/e2e/sidebar-expand-no-scrollbar.spec.ts @@ -133,7 +133,43 @@ test.describe("sidebar width transition", () => { }); // With room to spare there is no edge: the default rail is unchanged. + // No native scrollbar in the rail (a classic one with arrow buttons + // read as a foreign body); a fade marks the edge that has more. + expect( + await nav.evaluate((el) => getComputedStyle(el).scrollbarWidth), + ).toBe("none"); + expect( + await nav.evaluate( + (el) => (el as HTMLElement).offsetWidth - el.clientWidth, + ), + ).toBe(0); + await expect(nav).toHaveAttribute("data-fade-top", "true"); + await expect(nav).not.toHaveAttribute("data-fade-bottom", "true"); + await nav.evaluate((el) => { + el.scrollTop = 0; + }); + await expect(nav).toHaveAttribute("data-fade-bottom", "true"); + await expect(nav).not.toHaveAttribute("data-fade-top", "true"); + await page.setViewportSize({ width: 1280, height: 1400 }); await expect(footer).not.toHaveAttribute("data-nav-overflows", "true"); + await expect(nav).not.toHaveAttribute("data-fade-bottom", "true"); + }); + + test("a 1366 × 768 window fits the rail without scrolling, collapsed and expanded", async ({ + page, + }) => { + await page.setViewportSize({ width: 1366, height: 768 }); + await page.goto("/", { waitUntil: "domcontentloaded" }); + const nav = page.locator("aside nav").first(); + const toggle = page.locator('[data-slot="sidebar-collapse-toggle"]'); + await expect(nav).toBeVisible(); + for (let i = 0; i < 2; i++) { + await expect + .poll(() => nav.evaluate((el) => el.scrollHeight - el.clientHeight)) + .toBeLessThanOrEqual(0); + await toggle.click(); + await page.waitForTimeout(300); + } }); }); diff --git a/src/components/layout/sidebar-nav.tsx b/src/components/layout/sidebar-nav.tsx index 033882f4d..2e2bd4af9 100644 --- a/src/components/layout/sidebar-nav.tsx +++ b/src/components/layout/sidebar-nav.tsx @@ -248,7 +248,12 @@ function SidebarUserSection({ collapsed }: { collapsed: boolean }) { if (collapsed) { return ( -
+