diff --git a/src/App.tsx b/src/App.tsx index 7a320285f..d6b73a6d4 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -112,11 +112,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 9ce7e5088..7598ff095 100644 --- a/src/components/layout/SideBar.tsx +++ b/src/components/layout/SideBar.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useId, useState } from "react"; +import { useCallback, useEffect, 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"; @@ -35,6 +35,9 @@ import { } from "./SidebarNavIcons"; import { SidebarLogo } from "./SidebarLogo"; import { SidebarNavLink } from "./SidebarNavLink"; +import { SidebarResizeHandle } from "./SidebarResizeHandle"; +import { SidebarAccountFlyout } from "./SidebarAccountFlyout"; +import { SIDEBAR_COLLAPSED_WIDTH, SIDEBAR_WIDTH_VAR, useSidebarLayout } from "./useSidebarLayout"; import { hoverSpringToken } from "../../styles/tokens"; type SidebarNavItem = { @@ -62,6 +65,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; + /** Lets the logo fold and unfold the desktop sidebar. Left out on the mobile drawer. */ + onToggleCollapsed?: () => void; }; /** @@ -173,6 +180,8 @@ function SidebarContent({ hasPmAttentionItems = false, openEscalationCount = 0, unseenSkipAnswerCount = 0, + collapsed = false, + onToggleCollapsed, }: SidebarContentProps) { const { profile, logout, status } = useAuth(); const { canManageSelected } = useProjectContext(); @@ -268,12 +277,93 @@ function SidebarContent({ .flatMap((section) => section.items.map((item) => item.path)) .join("|")}`; + /** The footer card, the same in the open sidebar and in the folded rail's flyout. */ + const footerCard = ( +
+ {profile && ( +
+
+
+ +
+ +
+ + {profile.username} + + + + {profile.permissionGroup.replace("_", " ")} + +
+
+ + + `flex h-9 w-9 items-center justify-center rounded-[10px] transition-colors ${ + isActive + ? "bg-app-brand-soft text-app-brand" + : "text-app-text-muted hover:bg-app-surface-hover hover:text-app-text" + }` + } + title={settingsLabel} + aria-label={settingsLabel} + > + + + +
+ )} + + + + { + void logout(); + }} + disabled={status === "loading"} + whileHover={status === "loading" ? undefined : { scale: 1.02 }} + whileTap={status === "loading" ? undefined : { scale: 0.98 }} + transition={hoverSpringToken} + className="flex h-[40px] w-full items-center justify-center gap-[12px] rounded-[12px] border border-app-danger-border/40 bg-app-danger-bg/70 text-sm font-medium text-app-danger-text backdrop-blur-md transition-colors hover:border-app-danger-solid hover:bg-app-danger-solid hover:text-white focus-visible:ring-2 focus-visible:ring-app-focus focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50" + > + +
+ ); + return (
-
- +
+ -

SprintStart

+

+ SprintStart +