2026-03-25 09:24:37 +08:00
|
|
|
import { FolderOpen, LayoutGrid, Menu, Settings as SettingsIcon, Workflow, X } from "lucide-react";
|
2026-03-22 03:01:23 +08:00
|
|
|
import { useState } from "react";
|
2026-03-22 04:42:31 +08:00
|
|
|
import { Link } from "react-router-dom";
|
|
|
|
|
import { useMobile } from "@/hooks/use-mobile";
|
|
|
|
|
import { cn } from "@/lib/utils";
|
2026-03-25 09:24:37 +08:00
|
|
|
import { Dropzone } from "../common/dropzone";
|
|
|
|
|
import { HelpDialog } from "../help/help-dialog";
|
|
|
|
|
import { SettingsDialog } from "../settings/settings-dialog";
|
|
|
|
|
import { Footer } from "./footer";
|
|
|
|
|
import { Sidebar } from "./sidebar";
|
|
|
|
|
import { ToolPanel } from "./tool-panel";
|
2026-03-22 03:01:23 +08:00
|
|
|
|
|
|
|
|
interface AppLayoutProps {
|
|
|
|
|
children?: React.ReactNode;
|
|
|
|
|
showToolPanel?: boolean;
|
2026-03-22 11:04:20 +08:00
|
|
|
onFiles?: (files: File[]) => void;
|
2026-03-22 03:01:23 +08:00
|
|
|
}
|
|
|
|
|
|
2026-03-22 11:04:20 +08:00
|
|
|
export function AppLayout({ children, showToolPanel = true, onFiles }: AppLayoutProps) {
|
2026-03-22 04:42:31 +08:00
|
|
|
const [settingsOpen, setSettingsOpen] = useState(false);
|
2026-03-22 21:25:14 +08:00
|
|
|
const [helpOpen, setHelpOpen] = useState(false);
|
2026-03-22 04:42:31 +08:00
|
|
|
const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false);
|
|
|
|
|
const isMobile = useMobile();
|
2026-03-22 03:01:23 +08:00
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="flex h-screen bg-background text-foreground overflow-hidden">
|
2026-03-22 04:42:31 +08:00
|
|
|
{/* Desktop sidebar */}
|
|
|
|
|
{!isMobile && (
|
2026-03-25 09:24:37 +08:00
|
|
|
<Sidebar
|
|
|
|
|
onSettingsClick={() => setSettingsOpen(true)}
|
|
|
|
|
onHelpClick={() => setHelpOpen(true)}
|
|
|
|
|
/>
|
2026-03-22 04:42:31 +08:00
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{/* Mobile sidebar overlay */}
|
|
|
|
|
{isMobile && mobileSidebarOpen && (
|
|
|
|
|
<>
|
|
|
|
|
<div
|
|
|
|
|
className="fixed inset-0 z-40 bg-black/50 backdrop-blur-sm"
|
|
|
|
|
onClick={() => setMobileSidebarOpen(false)}
|
|
|
|
|
/>
|
|
|
|
|
<div className="fixed inset-y-0 left-0 z-50 w-64 bg-background border-r border-border shadow-xl animate-in slide-in-from-left">
|
|
|
|
|
<div className="flex items-center justify-between p-3 border-b border-border">
|
|
|
|
|
<span className="text-sm font-bold text-foreground">
|
|
|
|
|
Stirling <span className="text-primary">Image</span>
|
|
|
|
|
</span>
|
|
|
|
|
<button
|
|
|
|
|
onClick={() => setMobileSidebarOpen(false)}
|
|
|
|
|
className="p-1.5 rounded-lg hover:bg-muted"
|
|
|
|
|
>
|
|
|
|
|
<X className="h-4 w-4" />
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
<div onClick={() => setMobileSidebarOpen(false)}>
|
|
|
|
|
<Sidebar
|
|
|
|
|
onSettingsClick={() => {
|
|
|
|
|
setMobileSidebarOpen(false);
|
|
|
|
|
setSettingsOpen(true);
|
|
|
|
|
}}
|
2026-03-22 21:25:14 +08:00
|
|
|
onHelpClick={() => {
|
|
|
|
|
setMobileSidebarOpen(false);
|
|
|
|
|
setHelpOpen(true);
|
|
|
|
|
}}
|
2026-03-22 04:42:31 +08:00
|
|
|
expanded
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{/* Mobile top bar */}
|
|
|
|
|
{isMobile && (
|
|
|
|
|
<div className="fixed top-0 left-0 right-0 z-30 bg-background/95 backdrop-blur-sm border-b border-border px-3 py-2 flex items-center gap-3">
|
|
|
|
|
<button
|
|
|
|
|
onClick={() => setMobileSidebarOpen(true)}
|
|
|
|
|
className="p-1.5 rounded-lg hover:bg-muted"
|
|
|
|
|
>
|
|
|
|
|
<Menu className="h-5 w-5" />
|
|
|
|
|
</button>
|
|
|
|
|
<span className="text-sm font-bold text-foreground">
|
|
|
|
|
Stirling <span className="text-primary">Image</span>
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{showToolPanel && !isMobile && <ToolPanel />}
|
|
|
|
|
|
2026-03-25 09:24:37 +08:00
|
|
|
<main className={cn("flex-1 flex flex-col overflow-hidden", isMobile && "pt-12 pb-16")}>
|
2026-03-22 03:01:23 +08:00
|
|
|
<div className="flex-1 overflow-y-auto p-6 flex items-center justify-center">
|
2026-03-22 11:04:20 +08:00
|
|
|
{children || <Dropzone onFiles={onFiles} accept="image/*" />}
|
2026-03-22 03:01:23 +08:00
|
|
|
</div>
|
2026-03-22 04:42:31 +08:00
|
|
|
{!isMobile && (
|
|
|
|
|
<div className="text-center text-xs text-muted-foreground py-2 border-t border-border">
|
|
|
|
|
Privacy Policy
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
2026-03-22 03:01:23 +08:00
|
|
|
</main>
|
2026-03-22 04:42:31 +08:00
|
|
|
|
|
|
|
|
{!isMobile && <Footer />}
|
|
|
|
|
|
|
|
|
|
{/* Mobile bottom nav */}
|
|
|
|
|
{isMobile && (
|
|
|
|
|
<nav className="fixed bottom-0 left-0 right-0 z-30 bg-background/95 backdrop-blur-sm border-t border-border flex items-center justify-around px-2 py-1.5">
|
|
|
|
|
<MobileNavItem icon={LayoutGrid} label="Tools" href="/" />
|
|
|
|
|
<MobileNavItem icon={Workflow} label="Automate" href="/automate" />
|
|
|
|
|
<MobileNavItem icon={FolderOpen} label="Files" href="/files" />
|
|
|
|
|
<button
|
|
|
|
|
onClick={() => setSettingsOpen(true)}
|
|
|
|
|
className="flex flex-col items-center gap-0.5 px-3 py-1 text-muted-foreground"
|
|
|
|
|
>
|
|
|
|
|
<SettingsIcon className="h-5 w-5" />
|
|
|
|
|
<span className="text-[10px]">Settings</span>
|
|
|
|
|
</button>
|
|
|
|
|
</nav>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{/* Settings dialog */}
|
2026-03-25 09:24:37 +08:00
|
|
|
<SettingsDialog open={settingsOpen} onClose={() => setSettingsOpen(false)} />
|
2026-03-22 21:25:14 +08:00
|
|
|
|
|
|
|
|
{/* Help dialog */}
|
2026-03-25 09:24:37 +08:00
|
|
|
<HelpDialog open={helpOpen} onClose={() => setHelpOpen(false)} />
|
2026-03-22 03:01:23 +08:00
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
2026-03-22 04:42:31 +08:00
|
|
|
|
|
|
|
|
function MobileNavItem({
|
|
|
|
|
icon: Icon,
|
|
|
|
|
label,
|
|
|
|
|
href,
|
|
|
|
|
}: {
|
|
|
|
|
icon: React.ComponentType<{ className?: string }>;
|
|
|
|
|
label: string;
|
|
|
|
|
href: string;
|
|
|
|
|
}) {
|
|
|
|
|
return (
|
|
|
|
|
<Link
|
|
|
|
|
to={href}
|
|
|
|
|
className="flex flex-col items-center gap-0.5 px-3 py-1 text-muted-foreground hover:text-foreground transition-colors"
|
|
|
|
|
>
|
|
|
|
|
<Icon className="h-5 w-5" />
|
|
|
|
|
<span className="text-[10px]">{label}</span>
|
|
|
|
|
</Link>
|
|
|
|
|
);
|
|
|
|
|
}
|