From 4cc7684b1f3c91c199607a9486168be068d8b313 Mon Sep 17 00:00:00 2001 From: AmirReza Jamali Date: Mon, 8 Dec 2025 17:07:54 +0330 Subject: [PATCH] refactor(layouts): extract MainLayout component and improve sidebar mobile UX - Extract MainLayout component from ConditionalLayout to reduce nesting and improve code organization - Add useIsMobile hook to MainLayout for responsive behavior and prevent layout reflow on mobile - Implement collapsed sidebar state for mobile devices showing only icons (70px width) - Add MenuExpandIcon component to sidebar icons for collapsed state UI - Update sidebar to display favicon instead of full logo when collapsed on mobile - Remove menu toggle button and logo from header, consolidate in sidebar for better UX - Improve sidebar styling with conditional padding and alignment for collapsed state - Update sidebar navigation items to hide text labels when collapsed, showing only icons - Refactor header component to remove mobile-specific logic now handled in MainLayout - Enhance mobile navigation by maintaining sidebar space reservation to avoid layout reflow --- src/components/Layouts/conditional-layout.tsx | 30 +++- src/components/Layouts/header/index.tsx | 26 --- src/components/Layouts/sidebar/icons.tsx | 21 +++ src/components/Layouts/sidebar/index.tsx | 154 ++++++++++++------ src/components/Layouts/sidebar/menu-item.tsx | 22 ++- 5 files changed, 165 insertions(+), 88 deletions(-) diff --git a/src/components/Layouts/conditional-layout.tsx b/src/components/Layouts/conditional-layout.tsx index 430aa94..1c0732d 100644 --- a/src/components/Layouts/conditional-layout.tsx +++ b/src/components/Layouts/conditional-layout.tsx @@ -6,6 +6,26 @@ import type { PropsWithChildren } from "react"; import { Header } from "./header"; import Notification from "./notification"; import { Sidebar } from "./sidebar"; +import { useIsMobile } from "@/hooks/use-mobile"; + +function MainLayout({ children }: PropsWithChildren) { + const isMobile = useIsMobile(); + + return ( +
+ + {/* Spacer to reserve space for collapsed sidebar on mobile - avoids reflow */} + {isMobile &&
} +
+
+
+ {children} +
+
+
+ ); +} + export function ConditionalLayout({ children }: PropsWithChildren) { const pathname = usePathname(); const isAuthPage = pathname.startsWith("/auth"); @@ -19,15 +39,7 @@ export function ConditionalLayout({ children }: PropsWithChildren) { ) : ( -
- -
-
-
- {children} -
-
-
+ {children}
)} diff --git a/src/components/Layouts/header/index.tsx b/src/components/Layouts/header/index.tsx index a7a7dd3..2e565fa 100644 --- a/src/components/Layouts/header/index.tsx +++ b/src/components/Layouts/header/index.tsx @@ -1,38 +1,12 @@ "use client"; -import Image from "next/image"; -import Link from "next/link"; -import { useSidebarContext } from "../sidebar/sidebar-context"; -import { MenuIcon } from "./icons"; import { Notification } from "./notification"; import { ThemeToggleSwitch } from "./theme-toggle"; import { UserInfo } from "./user-info"; export function Header() { - const { toggleSidebar, isMobile } = useSidebarContext(); - return (
- - - {isMobile && ( - - - - )} -

Opportunity Lens diff --git a/src/components/Layouts/sidebar/icons.tsx b/src/components/Layouts/sidebar/icons.tsx index 1c9b50d..8d31e6c 100644 --- a/src/components/Layouts/sidebar/icons.tsx +++ b/src/components/Layouts/sidebar/icons.tsx @@ -240,6 +240,27 @@ export function ArrowLeftIcon(props: PropsType) { ); } + +export function MenuExpandIcon(props: PropsType) { + return ( + + + + + + + ); +} export function BriefcaseTimer(props: PropsType) { return ( {isMobile && isOpen && ( @@ -55,25 +59,41 @@ export function Sidebar() {
-
+
+
isMobile && toggleSidebar()} + onClick={() => isMobile && isOpen && toggleSidebar()} className="px-0 py-2.5 min-[850px]:py-0" > - + {isCollapsed ? ( + Logo + ) : ( + + )} - {isMobile && ( + {isMobile && isOpen && ( + )}
diff --git a/src/components/Layouts/sidebar/menu-item.tsx b/src/components/Layouts/sidebar/menu-item.tsx index e049c3f..ed1f60a 100644 --- a/src/components/Layouts/sidebar/menu-item.tsx +++ b/src/components/Layouts/sidebar/menu-item.tsx @@ -12,9 +12,14 @@ const menuItemBaseStyles = cva( false: "hover:bg-gray-100 hover:text-dark hover:dark:bg-[#FFFFFF1A] hover:dark:text-white", }, + isCollapsed: { + true: "px-2 justify-center", + false: "", + }, }, defaultVariants: { isActive: false, + isCollapsed: false, }, }, ); @@ -24,23 +29,27 @@ export function MenuItem( className?: string; children: React.ReactNode; isActive: boolean; + isCollapsed?: boolean; + title?: string; } & ({ as?: "button"; onClick: () => void } | { as: "link"; href: string }), ) { - const { toggleSidebar, isMobile } = useSidebarContext(); + const { toggleSidebar, isMobile, isOpen } = useSidebarContext(); if (props.as === "link") { return ( isMobile && toggleSidebar()} + // Close sidebar on clicking link if it's mobile and expanded + onClick={() => isMobile && isOpen && toggleSidebar()} className={cn( menuItemBaseStyles({ isActive: props.isActive, + isCollapsed: props.isCollapsed, className: "relative block py-2", }), props.className, )} + title={props.isCollapsed ? props.title : undefined} > {props.children} @@ -53,8 +62,13 @@ export function MenuItem( aria-expanded={props.isActive} className={menuItemBaseStyles({ isActive: props.isActive, - className: "flex w-full items-center gap-3 py-3", + isCollapsed: props.isCollapsed, + className: cn( + "flex w-full items-center gap-3 py-3", + props.isCollapsed && "justify-center", + ), })} + title={props.isCollapsed ? props.title : undefined} > {props.children}