From 580208de83349362b27b3a8a6db3370fc2770d0e Mon Sep 17 00:00:00 2001 From: Siddharth Kumar Sah Date: Sun, 22 Mar 2026 04:42:31 +0800 Subject: [PATCH] feat(web): add mobile responsive layout with bottom navigation Add useMobile hook for viewport detection (<768px). Update AppLayout with hamburger menu, slide-over sidebar overlay, and fixed bottom navigation bar (Tools, Automate, Files, Settings) for mobile. Update Sidebar with expanded mode for mobile overlay. Update ToolPage to stack settings above dropzone on mobile with collapsible panel. Add new routes for /automate and /fullscreen. Integrate Settings dialog and keyboard shortcuts at the App root level. --- apps/web/src/App.tsx | 17 ++- apps/web/src/components/layout/app-layout.tsx | 132 ++++++++++++++++-- apps/web/src/components/layout/sidebar.tsx | 33 ++++- apps/web/src/hooks/use-mobile.ts | 22 +++ apps/web/src/pages/tool-page.tsx | 84 ++++++++++- 5 files changed, 271 insertions(+), 17 deletions(-) create mode 100644 apps/web/src/hooks/use-mobile.ts diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx index 53f49c95..2ff47cca 100644 --- a/apps/web/src/App.tsx +++ b/apps/web/src/App.tsx @@ -2,15 +2,22 @@ import { BrowserRouter, Routes, Route } from "react-router-dom"; import { HomePage } from "./pages/home-page"; import { LoginPage } from "./pages/login-page"; import { ToolPage } from "./pages/tool-page"; +import { AutomatePage } from "./pages/automate-page"; +import { FullscreenGridPage } from "./pages/fullscreen-grid-page"; +import { KeyboardShortcutProvider } from "./components/common/keyboard-shortcut-provider"; export function App() { return ( - - } /> - } /> - } /> - + + + } /> + } /> + } /> + } /> + } /> + + ); } diff --git a/apps/web/src/components/layout/app-layout.tsx b/apps/web/src/components/layout/app-layout.tsx index 57740a94..a8c16583 100644 --- a/apps/web/src/components/layout/app-layout.tsx +++ b/apps/web/src/components/layout/app-layout.tsx @@ -1,8 +1,20 @@ import { useState } from "react"; +import { Link } from "react-router-dom"; +import { + LayoutGrid, + Workflow, + FolderOpen, + Settings as SettingsIcon, + Menu, + X, +} from "lucide-react"; import { Sidebar } from "./sidebar"; import { ToolPanel } from "./tool-panel"; import { Footer } from "./footer"; import { Dropzone } from "../common/dropzone"; +import { SettingsDialog } from "../settings/settings-dialog"; +import { useMobile } from "@/hooks/use-mobile"; +import { cn } from "@/lib/utils"; interface AppLayoutProps { children?: React.ReactNode; @@ -10,21 +22,125 @@ interface AppLayoutProps { } export function AppLayout({ children, showToolPanel = true }: AppLayoutProps) { - const [_settingsOpen, setSettingsOpen] = useState(false); + const [settingsOpen, setSettingsOpen] = useState(false); + const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false); + const isMobile = useMobile(); return (
- setSettingsOpen(true)} /> - {showToolPanel && } -
+ {/* Desktop sidebar */} + {!isMobile && ( + setSettingsOpen(true)} /> + )} + + {/* Mobile sidebar overlay */} + {isMobile && mobileSidebarOpen && ( + <> +
setMobileSidebarOpen(false)} + /> +
+
+ + Stirling Image + + +
+
setMobileSidebarOpen(false)}> + { + setMobileSidebarOpen(false); + setSettingsOpen(true); + }} + expanded + /> +
+
+ + )} + + {/* Mobile top bar */} + {isMobile && ( +
+ + + Stirling Image + +
+ )} + + {showToolPanel && !isMobile && } + +
{children || }
-
- Privacy Policy -
+ {!isMobile && ( +
+ Privacy Policy +
+ )}
-
+ + {!isMobile &&
} + + {/* Mobile bottom nav */} + {isMobile && ( + + )} + + {/* Settings dialog */} + setSettingsOpen(false)} + />
); } + +function MobileNavItem({ + icon: Icon, + label, + href, +}: { + icon: React.ComponentType<{ className?: string }>; + label: string; + href: string; +}) { + return ( + + + {label} + + ); +} diff --git a/apps/web/src/components/layout/sidebar.tsx b/apps/web/src/components/layout/sidebar.tsx index 6e0a4bac..f5721633 100644 --- a/apps/web/src/components/layout/sidebar.tsx +++ b/apps/web/src/components/layout/sidebar.tsx @@ -7,6 +7,7 @@ import { FolderOpen, HelpCircle, Settings, + Grid3x3, } from "lucide-react"; import type { LucideIcon } from "lucide-react"; @@ -18,7 +19,7 @@ interface SidebarItem { const topItems: SidebarItem[] = [ { icon: LayoutGrid, label: "Tools", href: "/" }, - { icon: BookOpen, label: "Reader", href: "/reader" }, + { icon: Grid3x3, label: "Grid", href: "/fullscreen" }, { icon: Workflow, label: "Automate", href: "/automate" }, { icon: FolderOpen, label: "Files", href: "/files" }, ]; @@ -30,13 +31,27 @@ const bottomItems: SidebarItem[] = [ interface SidebarProps { onSettingsClick: () => void; + /** When true, renders in expanded mode (for mobile overlay). */ + expanded?: boolean; } -export function Sidebar({ onSettingsClick }: SidebarProps) { +export function Sidebar({ onSettingsClick, expanded = false }: SidebarProps) { const location = useLocation(); const renderItem = (item: SidebarItem, isActive: boolean) => { - const content = ( + const content = expanded ? ( +
+ + {item.label} +
+ ) : (
+ {topItems.map((item) => + renderItem(item, location.pathname === item.href) + )} +
+ {bottomItems.map((item) => renderItem(item, false))} +
+ ); + } + return (