From 04398ffe387d15afc5256f36123af7948f6caa84 Mon Sep 17 00:00:00 2001 From: Ricardo Cataldi Date: Thu, 21 May 2026 14:30:00 -0300 Subject: [PATCH] fix mobile workspace sidebar drawer --- frontend/components/Layouts/DefaultLayout.tsx | 26 ++++-- frontend/components/Sidebar/index.tsx | 16 +++- frontend/e2e/route-shells.spec.ts | 80 +++++++++++++++++++ 3 files changed, 114 insertions(+), 8 deletions(-) diff --git a/frontend/components/Layouts/DefaultLayout.tsx b/frontend/components/Layouts/DefaultLayout.tsx index 0270ae6..0630aea 100644 --- a/frontend/components/Layouts/DefaultLayout.tsx +++ b/frontend/components/Layouts/DefaultLayout.tsx @@ -1,13 +1,14 @@ "use client"; import Header from "@/components/Header"; -import Sidebar from "@/components/Sidebar"; +import Sidebar, { type SidebarDisclosureState } from "@/components/Sidebar"; import type { Metadata } from "next"; import type React from "react"; -import { useRef, useState } from "react"; +import { useEffect, useRef, useState } from "react"; const WORKSPACE_SHELL_METRICS_CLASS = "[--workspace-header-offset:9.75rem] [--workspace-sidebar-width:15.5rem] sm:[--workspace-header-offset:8.75rem] lg:[--workspace-header-offset:4.5rem]"; const PUBLIC_SHELL_METRICS_CLASS = "[--workspace-header-offset:5rem]"; +const DESKTOP_SIDEBAR_QUERY = "(min-width: 1024px)"; export default function DefaultLayout({ children, @@ -18,9 +19,24 @@ export default function DefaultLayout({ metadata?: Metadata; variant?: "workspace" | "public"; }) { - const [sidebarOpen, setSidebarOpen] = useState(true); + // No GoF pattern applies; this layout tracks simple responsive disclosure state. + const [sidebarState, setSidebarState] = useState("responsive"); + const [isDesktopSidebarViewport, setIsDesktopSidebarViewport] = useState(false); const sidebarSwitcherRef = useRef(null); const isWorkspaceShell = variant === "workspace"; + const sidebarOpen = + sidebarState === "open" || (sidebarState === "responsive" && isDesktopSidebarViewport); + const setSidebarOpen = (open: boolean) => setSidebarState(open ? "open" : "closed"); + + useEffect(() => { + const desktopQuery = window.matchMedia(DESKTOP_SIDEBAR_QUERY); + const syncDesktopViewport = () => setIsDesktopSidebarViewport(desktopQuery.matches); + + syncDesktopViewport(); + desktopQuery.addEventListener("change", syncDesktopViewport); + + return () => desktopQuery.removeEventListener("change", syncDesktopViewport); + }, []); return (
{isWorkspaceShell && ( )}
void; exceptionRef?: React.RefObject; } +export type SidebarDisclosureState = "closed" | "open" | "responsive"; + +// No GoF pattern applies; this is a finite responsive disclosure state. +const sidebarTransformClass = { + closed: "-translate-x-full", + open: "translate-x-0", + responsive: "-translate-x-full lg:translate-x-0", +} as const satisfies Record; + interface SidebarHoverCardProps { description: string; eyebrow: string; @@ -59,11 +68,12 @@ const SidebarHoverCard = ({ ); }; -const Sidebar = ({ sidebarOpen, setSidebarOpen, exceptionRef }: SidebarProps) => { +const Sidebar = ({ sidebarState, setSidebarOpen, exceptionRef }: SidebarProps) => { const { currentContext, isLoading, roleConfig } = useWorkspace(); const contextNote = isLoading ? "Tutor is resolving the latest context note for this role." : currentContext.note; + const sidebarOpen = sidebarState === "open"; return ( <> @@ -79,7 +89,7 @@ const Sidebar = ({ sidebarOpen, setSidebarOpen, exceptionRef }: SidebarProps) =>