From 7a213fd7376147f1b3220a29c28f5eed9be6a026 Mon Sep 17 00:00:00 2001 From: BabuPlk Date: Sat, 26 Sep 2026 16:04:48 +0000 Subject: [PATCH 1/8] Let the desktop sidebar fold to icons and be resized, and remember both A toggle in the sidebar's header folds it to an icon-only rail and back. Folded, every entry keeps its name for assistive technology and shows it as a tooltip on hover and keyboard focus (with its count or status, if it has one); counts become a bubble on the icon, section headings a short rule, and the footer shows the avatar, settings, the project's monogram and a logout icon. Open, its right edge can be dragged between 240 and 400px, or moved with the arrow keys, Home and End as a focusable separator; a double click resets it to 286px, and dragging well past the minimum folds it. Width and folded state are kept in localStorage per browser. The mobile drawer is unchanged. --- src/App.tsx | 10 +- src/components/layout/SideBar.tsx | 123 ++++++++++++++--- src/components/layout/SidebarNavLink.tsx | 86 +++++++++++- src/components/layout/SidebarResizeHandle.tsx | 129 ++++++++++++++++++ src/components/layout/useSidebarLayout.ts | 86 ++++++++++++ .../projects/components/ProjectSwitcher.tsx | 18 ++- src/styles/index.css | 19 +++ tests/unit/a11y/SideBar.a11y.test.tsx | 20 +++ tests/unit/components/layout/SideBar.test.tsx | 92 ++++++++++++- 9 files changed, 551 insertions(+), 32 deletions(-) create mode 100644 src/components/layout/SidebarResizeHandle.tsx create mode 100644 src/components/layout/useSidebarLayout.ts diff --git a/src/App.tsx b/src/App.tsx index c66affee8..3903feeed 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -111,11 +111,15 @@ function AppContent() { screen — see momentStage.ts in the moments feature. */}
diff --git a/src/components/layout/SideBar.tsx b/src/components/layout/SideBar.tsx index b26492a31..844ad9686 100644 --- a/src/components/layout/SideBar.tsx +++ b/src/components/layout/SideBar.tsx @@ -1,7 +1,7 @@ -import { useId, useState } from "react"; +import { useId, useLayoutEffect, useState } from "react"; import { motion, useMotionValue } from "framer-motion"; import { NavLink, useLocation } from "react-router-dom"; -import { LogOut, Menu, Settings, X } from "lucide-react"; +import { LogOut, Menu, PanelLeftClose, PanelLeftOpen, Settings, X } from "lucide-react"; import { UserAvatar } from "../common/UserAvatar"; import { useAuth } from "../../context/useAuth"; import { canAccessRoute, isOnboardingAccessible, type AppRoute } from "../../auth/accessPolicy"; @@ -29,6 +29,8 @@ import { } from "./SidebarNavIcons"; import { SidebarLogo } from "./SidebarLogo"; import { SidebarNavLink } from "./SidebarNavLink"; +import { SidebarResizeHandle } from "./SidebarResizeHandle"; +import { SIDEBAR_COLLAPSED_WIDTH, SIDEBAR_WIDTH_VAR, useSidebarLayout } from "./useSidebarLayout"; import { hoverSpringToken } from "../../styles/tokens"; type SidebarNavItem = { @@ -56,6 +58,10 @@ type SidebarContentProps = { openEscalationCount?: number; /** Skip requests the project manager answered that the member has not looked at yet. */ unseenSkipAnswerCount?: number; + /** Folded to icons -- the desktop sidebar only; the mobile drawer is always full width. */ + collapsed?: boolean; + /** Folds or unfolds the desktop sidebar. Without it there is no toggle (the mobile drawer). */ + onToggleCollapsed?: () => void; }; /** @@ -167,6 +173,8 @@ function SidebarContent({ hasPmAttentionItems = false, openEscalationCount = 0, unseenSkipAnswerCount = 0, + collapsed = false, + onToggleCollapsed, }: SidebarContentProps) { const { profile, logout, status } = useAuth(); const { canManageSelected } = useProjectContext(); @@ -258,10 +266,39 @@ function SidebarContent({ return (
-
+
-

SprintStart

+

+ SprintStart +

+ + {onToggleCollapsed && ( + + )}