From 27d5bbc80e03349ca4ce74f3f4240e986ace16a3 Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Sun, 2 Aug 2026 22:47:04 -0700 Subject: [PATCH] FN-8751: add depth to expanded sidebar Give the expanded desktop sidebar a floating visual layer above board content. - Elevate the expanded sidebar with tokenized shadow and stacking context. - Keep collapsed and mobile navigation visually flat. - Cover the depth behavior with CSS-focused component tests. Files changed: .../dashboard/app/components/LeftSidebarNav.css | 6 +++++ .../components/__tests__/LeftSidebarNav.test.tsx | 26 +++++++++++++++++++--- 2 files changed, 29 insertions(+), 3 deletions(-) Fusion-Task-Id: FN-8751 Fusion-Task-Lineage: 2c3197dc-f4f4-4d48-81bb-3fab67310e60 Co-authored-by: Fusion (runfusion.ai) --- .../app/components/LeftSidebarNav.css | 6 +++++ .../__tests__/LeftSidebarNav.test.tsx | 26 ++++++++++++++++--- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/packages/dashboard/app/components/LeftSidebarNav.css b/packages/dashboard/app/components/LeftSidebarNav.css index dae2fb7773..eb5c0aacfb 100644 --- a/packages/dashboard/app/components/LeftSidebarNav.css +++ b/packages/dashboard/app/components/LeftSidebarNav.css @@ -4,11 +4,15 @@ The experimental sidebar is a persistent desktop/tablet navigation replacement f FNXC:Navigation 2026-06-22-18:00: The border between left sidebar and main content should be invisible while the sidebar remains draggable. Keep the resize handle's invisible hit target and hover/focus accent, but remove the persistent vertical rule and footer divider so the shell reads as one continuous surface. + +FNXC:Navigation 2026-08-03-05:34: +Expanded desktop/tablet navigation needs the same tokenized floating depth as the unpinned right dock so board cards paint beneath its outer edge. Keep the shell above adjacent content without changing its flex sizing or resize hit target; rail mode explicitly removes the shadow and stays flat. */ .left-sidebar-nav { --left-sidebar-nav-width: calc(var(--space-2xl) * 7); --left-sidebar-nav-rail-width: calc(var(--space-2xl) * 2); position: relative; + z-index: 1; display: flex; flex-direction: column; box-sizing: border-box; @@ -17,6 +21,7 @@ The border between left sidebar and main content should be invisible while the s min-height: 0; background: var(--surface); color: var(--text); + box-shadow: var(--shadow-lg); font-family: var(--font-primary); } @@ -273,6 +278,7 @@ The footer has no top divider; it remains a functional footer cluster but should .left-sidebar-nav--collapsed { width: var(--left-sidebar-nav-rail-width); min-width: var(--left-sidebar-nav-rail-width); + box-shadow: none; } .left-sidebar-nav--collapsed .left-sidebar-nav__label, diff --git a/packages/dashboard/app/components/__tests__/LeftSidebarNav.test.tsx b/packages/dashboard/app/components/__tests__/LeftSidebarNav.test.tsx index 293a2a0f76..1e9e158e9e 100644 --- a/packages/dashboard/app/components/__tests__/LeftSidebarNav.test.tsx +++ b/packages/dashboard/app/components/__tests__/LeftSidebarNav.test.tsx @@ -1,11 +1,10 @@ -import { readFileSync } from "node:fs"; -import { resolve } from "node:path"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { fireEvent, render, screen, within } from "@testing-library/react"; import type { ComponentProps } from "react"; import { LeftSidebarNav } from "../LeftSidebarNav"; import type { PluginDashboardViewEntry, ProjectInfo } from "../../api"; import type { TaskView } from "../../hooks/useViewState"; +import { loadComponentCss } from "../../test/cssFixture"; const projects: ProjectInfo[] = [ { @@ -28,7 +27,7 @@ const projects: ProjectInfo[] = [ }, ]; -const leftSidebarNavCss = readFileSync(resolve(__dirname, "../LeftSidebarNav.css"), "utf8"); +const leftSidebarNavCss = loadComponentCss("LeftSidebarNav.css"); const obsoleteCollapseToggleFloatingClass = "left-sidebar-nav__collapse-toggle--" + "floating"; const newTaskSurfaceEnumeration = [ "[x] Components that render the affordance: Grep confirms LeftSidebarNav is the only persistent sidebar renderer and App.tsx mounts it once.", @@ -544,6 +543,27 @@ describe("LeftSidebarNav", () => { expect(within(sidebar).getAllByRole("button").at(-1)).toBe(screen.getByTestId("sidebar-nav-settings")); }); + it("keeps expanded depth above board content while collapsed and mobile navigation remain flat", () => { + const { unmount } = renderSidebar(); + const expandedSidebar = screen.getByTestId("left-sidebar-nav"); + const expandedRule = getCssRuleBlock(leftSidebarNavCss, ".left-sidebar-nav"); + const collapsedRule = getCssRuleBlock(leftSidebarNavCss, ".left-sidebar-nav--collapsed"); + + expect(expandedSidebar).not.toHaveClass("left-sidebar-nav--collapsed"); + expect(expandedRule).toContain("position: relative"); + expect(expandedRule).toContain("z-index: 1"); + expect(expandedRule).toContain("box-shadow: var(--shadow-lg)"); + expect(expandedRule).not.toMatch(/\d+px|#|rgb\(/i); + + unmount(); + window.localStorage.setItem("fusion:left-sidebar-collapsed", "true"); + renderSidebar(); + expect(screen.getByTestId("left-sidebar-nav")).toHaveClass("left-sidebar-nav--collapsed"); + expect(collapsedRule).toContain("box-shadow: none"); + expect(leftSidebarNavCss).toMatch(/@media \(max-width: 768px\)\s*\{\s*\.left-sidebar-nav\s*\{\s*display:\s*none;/); + expect(leftSidebarNavCss).toMatch(/html\[data-viewport-mode="mobile"\] \.left-sidebar-nav\s*\{\s*display:\s*none;/); + }); + it("keeps collapse toggle styling tokenized and removes the floating modifier", () => { expect(leftSidebarNavCss).not.toContain(obsoleteCollapseToggleFloatingClass);