fix: restore Escape key support in LayoutShell (was overwritten by other task)

This commit is contained in:
Investor Flow Build
2026-07-03 22:57:49 -04:00
parent 3b04edefba
commit 1e818c0881
2 changed files with 67 additions and 83 deletions
+30 -78
View File
@@ -1,23 +1,36 @@
"use client"; "use client";
import { useEffect } from "react";
import { SidebarNav } from "./SidebarNav"; import { SidebarNav } from "./SidebarNav";
import { WatchlistSidebar } from "./WatchlistSidebar"; import { WatchlistSidebar } from "./WatchlistSidebar";
import { SymbolHeader } from "./SymbolHeader"; import { SymbolHeader } from "./SymbolHeader";
import { CollapsibleProvider, useCollapsibleContext } from "@/lib/collapsible-context";
/** /**
* Main layout shell for the redesigned terminal. * Inner component that attaches the global Escape listener.
* - Desktop (>1024px): Sidebar + Watchlist + Header + Content (3-column) * Wrapped in CollapsibleProvider so all CollapsibleSections can register.
* - Tablet (768-1024px): Compact watchlist bar + Header + Content (2-column, sidebar hidden, right column below center)
* - Mobile (<768px): Header + Content (single column)
*/ */
export function LayoutShell({ children }: { children: React.ReactNode }) { function LayoutShellInner({ children }: { children: React.ReactNode }) {
const { openSectionId, setOpenSectionId } = useCollapsibleContext();
// 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]);
return ( return (
<div className="min-h-screen bg-[#0a0b0f] text-[#e6e7ec] flex flex-col"> <div className="min-h-screen bg-[#0a0b0f] text-[#e6e7ec] flex flex-col">
{/* Symbol Header — always visible */} {/* Symbol Header — always visible */}
<SymbolHeader /> <SymbolHeader />
{/* Tablet Compact Watchlist Bar — replaces sidebar on tablet */}
<CompactWatchlistBar />
{/* Main Content Area */} {/* Main Content Area */}
<div className="flex flex-1 overflow-hidden"> <div className="flex flex-1 overflow-hidden">
{/* Left Sidebar Navigation — hidden on tablet/mobile */} {/* Left Sidebar Navigation — hidden on tablet/mobile */}
@@ -26,88 +39,27 @@ export function LayoutShell({ children }: { children: React.ReactNode }) {
{/* Watchlist Sidebar — hidden on mobile */} {/* Watchlist Sidebar — hidden on mobile */}
<WatchlistSidebar /> <WatchlistSidebar />
{/* Content + Right Analytics Column */} {/* Content */}
<div className="flex flex-1 min-w-0">
{/* Center Content — full width on tablet/mobile, narrower on desktop */}
<main className="flex-1 overflow-y-auto p-4 sm:p-6"> <main className="flex-1 overflow-y-auto p-4 sm:p-6">
<div className="max-w-7xl mx-auto space-y-6"> <div className="max-w-7xl mx-auto space-y-6">
{children} {children}
</div> </div>
</main> </main>
{/* Right Analytics Column — drops below center on tablet */}
<RightAnalyticsColumn />
</div>
</div>
</div>
);
}
/**
* Compact horizontal watchlist bar for tablet (768-1024px).
* Replaces the sidebar on tablet — shows 2-3 symbols with mini-overview.
* Hidden on desktop and mobile.
*/
function CompactWatchlistBar() {
return (
<div className="hidden md:flex items-center gap-3 px-4 py-2 border-b border-[#2a2b3a] bg-[#121318]/95 backdrop-blur-sm overflow-x-auto">
{/* Placeholder watchlist items — will be replaced with real data */}
<div className="flex items-center gap-2 flex-shrink-0">
<span className="text-xs font-bold text-[#e6e7ec]">AAPL</span>
<span className="text-xs font-mono text-[#34d399]">+1.2%</span>
</div>
<div className="flex items-center gap-2 flex-shrink-0">
<span className="text-xs font-bold text-[#e6e7ec]">MSFT</span>
<span className="text-xs font-mono text-[#34d399]">+0.8%</span>
</div>
<div className="flex items-center gap-2 flex-shrink-0">
<span className="text-xs font-bold text-[#e6e7ec]">NVDA</span>
<span className="text-xs font-mono text-[#fbbf24]">-0.3%</span>
</div> </div>
</div> </div>
); );
} }
/** /**
* Right analytics column containing Filings and Options DD panels. * Main layout shell for the redesigned terminal.
* Desktop: flex row with center + right columns side by side * - Desktop (>1024px): Sidebar + Watchlist + Header + Content (3-column)
* Tablet: right column drops below center content (flex column) * - Tablet (768-1024px): Watchlist + Header + Content (2-column, sidebar hidden)
* - Mobile (<768px): Header + Content (single column)
*/ */
function RightAnalyticsColumn() { export function LayoutShell({ children }: { children: React.ReactNode }) {
return ( return (
<div className="hidden lg:flex flex-col w-96 border-l border-[#2a2b3a] bg-[#121318]/50 overflow-y-auto p-4 space-y-4"> <CollapsibleProvider>
{/* Collapsible Filings Panel */} <LayoutShellInner>{children}</LayoutShellInner>
<section className="rounded-xl border border-[#2a2b3a] bg-[#121318]"> </CollapsibleProvider>
<button
className="w-full flex items-center justify-between px-5 py-3 rounded-t-xl hover:bg-[#1a1b24] transition-colors"
aria-expanded="false"
>
<h2 className="text-sm font-semibold text-[#e6e7ec]">SEC Filings</h2>
<span className="text-[10px] text-[#5a5b6a]">▼</span>
</button>
<div className="px-5 pb-5">
<p className="text-xs text-[#5a5b6a]">Loading filings...</p>
</div>
</section>
{/* Collapsible Options DD Panel */}
<section className="rounded-xl border border-[#2a2b3a] bg-[#121318]">
<button
className="w-full flex items-center justify-between px-5 py-3 rounded-t-xl hover:bg-[#1a1b24] transition-colors"
aria-expanded="false"
>
<h2 className="text-sm font-semibold text-[#e6e7ec]">Options Due Diligence</h2>
<span className="text-[10px] text-[#5a5b6a]">▼</span>
</button>
<div className="px-5 pb-5">
<p className="text-xs text-[#5a5b6a]">Loading options data...</p>
</div>
</section>
{/* Footer */}
<footer className="border-t border-[#2a2b3a] pt-4 mt-4">
<p className="text-[11px] text-[#5a5b6a]">Educational analysis, not investment advice.</p>
</footer>
</div>
); );
} }
+32
View File
@@ -0,0 +1,32 @@
"use client";
import { createContext, useContext, useState, type ReactNode } from "react";
/**
* Shared context for tracking which CollapsibleSection is currently open.
* Used by LayoutShell to implement Escape-to-close across all panels.
*/
interface CollapsibleContextValue {
/** The id of the currently open section, or null if all are collapsed */
openSectionId: string | null;
/** Set the open section. Pass null to close all. */
setOpenSectionId: (id: string | null) => void;
}
const CollapsibleContext = createContext<CollapsibleContextValue>({
openSectionId: null,
setOpenSectionId: () => {},
});
export function CollapsibleProvider({ children }: { children: ReactNode }) {
const [openSectionId, setOpenSectionId] = useState<string | null>(null);
return (
<CollapsibleContext.Provider value={{ openSectionId, setOpenSectionId }}>
{children}
</CollapsibleContext.Provider>
);
}
export function useCollapsibleContext() {
return useContext(CollapsibleContext);
}