2026-07-03 22:30:31 -04:00
|
|
|
"use client";
|
2026-07-05 08:50:12 -04:00
|
|
|
import { useEffect, useState } from "react";
|
2026-07-03 22:30:31 -04:00
|
|
|
import { SidebarNav } from "./SidebarNav";
|
|
|
|
|
import { WatchlistSidebar } from "./WatchlistSidebar";
|
|
|
|
|
import { SymbolHeader } from "./SymbolHeader";
|
2026-07-05 08:50:12 -04:00
|
|
|
import { MobileTabNav } from "./MobileTabNav";
|
2026-07-23 18:02:24 -04:00
|
|
|
import { CommandPalette } from "./CommandPalette";
|
2026-07-03 22:57:49 -04:00
|
|
|
import { CollapsibleProvider, useCollapsibleContext } from "@/lib/collapsible-context";
|
2026-07-05 08:50:12 -04:00
|
|
|
import { useKeyboardShortcuts } from "@/lib/useKeyboardShortcuts";
|
|
|
|
|
|
|
|
|
|
const MOBILE_BREAKPOINT = 768;
|
|
|
|
|
const TABLET_BREAKPOINT = 1024;
|
|
|
|
|
|
|
|
|
|
function useViewport() {
|
|
|
|
|
const [width, setWidth] = useState(typeof window !== "undefined" ? window.innerWidth : 1200);
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const handler = () => setWidth(window.innerWidth);
|
|
|
|
|
window.addEventListener("resize", handler);
|
|
|
|
|
return () => window.removeEventListener("resize", handler);
|
|
|
|
|
}, []);
|
|
|
|
|
return width;
|
|
|
|
|
}
|
2026-07-03 22:30:31 -04:00
|
|
|
|
|
|
|
|
/**
|
2026-07-03 22:57:49 -04:00
|
|
|
* Inner component that attaches the global Escape listener.
|
|
|
|
|
* Wrapped in CollapsibleProvider so all CollapsibleSections can register.
|
2026-07-03 22:30:31 -04:00
|
|
|
*/
|
2026-07-03 22:57:49 -04:00
|
|
|
function LayoutShellInner({ children }: { children: React.ReactNode }) {
|
|
|
|
|
const { openSectionId, setOpenSectionId } = useCollapsibleContext();
|
2026-07-05 08:50:12 -04:00
|
|
|
const viewportWidth = useViewport();
|
|
|
|
|
const isMobile = viewportWidth < MOBILE_BREAKPOINT;
|
|
|
|
|
const isTablet = viewportWidth >= MOBILE_BREAKPOINT && viewportWidth < TABLET_BREAKPOINT;
|
|
|
|
|
|
|
|
|
|
// Keyboard shortcuts: Escape closes open sections, modals
|
|
|
|
|
useKeyboardShortcuts([
|
|
|
|
|
{
|
|
|
|
|
key: "Escape",
|
|
|
|
|
handler: () => {
|
|
|
|
|
if (openSectionId) {
|
|
|
|
|
setOpenSectionId(null);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
]);
|
2026-07-03 22:57:49 -04:00
|
|
|
|
|
|
|
|
// Global Escape listener: collapse the currently open section.
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const handler = (e: KeyboardEvent) => {
|
|
|
|
|
if (e.key === "Escape" && openSectionId) {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
// Collapse the currently open section by clearing the shared state.
|
|
|
|
|
// Individual CollapsibleSection Escape handlers also respond to this.
|
|
|
|
|
setOpenSectionId(null);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
window.addEventListener("keydown", handler, true); // capture phase
|
|
|
|
|
return () => window.removeEventListener("keydown", handler, true);
|
|
|
|
|
}, [openSectionId, setOpenSectionId]);
|
|
|
|
|
|
2026-07-03 22:30:31 -04:00
|
|
|
return (
|
2026-07-23 18:02:24 -04:00
|
|
|
<div className="min-h-screen bg-surface text-fg flex flex-col">
|
2026-07-03 22:30:31 -04:00
|
|
|
{/* Symbol Header — always visible */}
|
|
|
|
|
<SymbolHeader />
|
|
|
|
|
|
2026-07-23 18:02:24 -04:00
|
|
|
{/* Global command palette (⌘K) */}
|
|
|
|
|
<CommandPalette />
|
|
|
|
|
|
2026-07-05 08:50:12 -04:00
|
|
|
{/* Main Content Area — responsive layout */}
|
|
|
|
|
{isMobile ? (
|
|
|
|
|
/* Mobile: single column, no sidebar */
|
|
|
|
|
<>
|
2026-07-23 18:02:24 -04:00
|
|
|
<main className="flex-1 overflow-y-auto overflow-x-hidden p-4 pb-20">
|
2026-07-05 08:50:12 -04:00
|
|
|
<div className="max-w-7xl mx-auto space-y-6">
|
|
|
|
|
{children}
|
|
|
|
|
</div>
|
|
|
|
|
</main>
|
|
|
|
|
<MobileTabNav />
|
|
|
|
|
</>
|
|
|
|
|
) : isTablet ? (
|
|
|
|
|
/* Tablet: 2-column layout, watchlist accessible via compact bar */
|
|
|
|
|
<div className="flex flex-1 overflow-hidden flex-col">
|
|
|
|
|
{/* Compact horizontal watchlist bar */}
|
2026-07-23 18:02:24 -04:00
|
|
|
<div className="hidden md:block border-b border-line-strong bg-surface-raised px-4 sm:px-6 py-2 overflow-x-auto">
|
2026-07-05 08:50:12 -04:00
|
|
|
<WatchlistSidebar compact />
|
2026-07-03 22:57:49 -04:00
|
|
|
</div>
|
2026-07-05 08:50:12 -04:00
|
|
|
<div className="flex flex-1 overflow-hidden">
|
|
|
|
|
{/* Center content */}
|
|
|
|
|
<main className="flex-1 overflow-y-auto p-4 sm:p-6">
|
|
|
|
|
<div className="max-w-7xl mx-auto space-y-6">
|
|
|
|
|
{children}
|
|
|
|
|
</div>
|
|
|
|
|
</main>
|
|
|
|
|
{/* Right analytics — drops below on tablet */}
|
2026-07-23 18:02:24 -04:00
|
|
|
<aside className="hidden lg:block w-80 border-l border-line-strong bg-surface-raised overflow-y-auto">
|
|
|
|
|
<div className="p-4 sm:p-6 space-y-4">
|
2026-07-05 08:50:12 -04:00
|
|
|
{/* Right column content placeholder */}
|
|
|
|
|
</div>
|
|
|
|
|
</aside>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
) : (
|
|
|
|
|
/* Desktop: 3-column layout */
|
|
|
|
|
<div className="flex flex-1 overflow-hidden">
|
|
|
|
|
{/* Left Sidebar Navigation */}
|
|
|
|
|
<SidebarNav />
|
|
|
|
|
|
|
|
|
|
{/* Watchlist Sidebar */}
|
|
|
|
|
<WatchlistSidebar />
|
|
|
|
|
|
|
|
|
|
{/* Content */}
|
2026-07-23 18:02:24 -04:00
|
|
|
<main className="flex-1 overflow-y-auto p-4 sm:p-6 lg:p-4 xl:p-4 2xl:p-4">
|
2026-07-05 08:50:12 -04:00
|
|
|
<div className="max-w-7xl mx-auto space-y-6">
|
|
|
|
|
{children}
|
|
|
|
|
</div>
|
|
|
|
|
</main>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
2026-07-03 22:52:21 -04:00
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2026-07-03 22:57:49 -04:00
|
|
|
* Main layout shell for the redesigned terminal.
|
|
|
|
|
* - Desktop (>1024px): Sidebar + Watchlist + Header + Content (3-column)
|
|
|
|
|
* - Tablet (768-1024px): Watchlist + Header + Content (2-column, sidebar hidden)
|
2026-07-05 08:50:12 -04:00
|
|
|
* - Mobile (<768px): Header + Content (single column) + MobileTabNav
|
2026-07-03 22:52:21 -04:00
|
|
|
*/
|
2026-07-03 22:57:49 -04:00
|
|
|
export function LayoutShell({ children }: { children: React.ReactNode }) {
|
2026-07-03 22:52:21 -04:00
|
|
|
return (
|
2026-07-03 22:57:49 -04:00
|
|
|
<CollapsibleProvider>
|
|
|
|
|
<LayoutShellInner>{children}</LayoutShellInner>
|
|
|
|
|
</CollapsibleProvider>
|
2026-07-03 22:30:31 -04:00
|
|
|
);
|
|
|
|
|
}
|