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";
import { useEffect } from "react";
import { SidebarNav } from "./SidebarNav";
import { WatchlistSidebar } from "./WatchlistSidebar";
import { SymbolHeader } from "./SymbolHeader";
import { CollapsibleProvider, useCollapsibleContext } from "@/lib/collapsible-context";
/**
* Main layout shell for the redesigned terminal.
* - Desktop (>1024px): Sidebar + Watchlist + Header + Content (3-column)
* - Tablet (768-1024px): Compact watchlist bar + Header + Content (2-column, sidebar hidden, right column below center)
* - Mobile (<768px): Header + Content (single column)
* Inner component that attaches the global Escape listener.
* Wrapped in CollapsibleProvider so all CollapsibleSections can register.
*/
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 (
<div className="min-h-screen bg-[#0a0b0f] text-[#e6e7ec] flex flex-col">
{/* Symbol Header — always visible */}
<SymbolHeader />
{/* Tablet Compact Watchlist Bar — replaces sidebar on tablet */}
<CompactWatchlistBar />
{/* Main Content Area */}
<div className="flex flex-1 overflow-hidden">
{/* Left Sidebar Navigation — hidden on tablet/mobile */}
@@ -26,88 +39,27 @@ export function LayoutShell({ children }: { children: React.ReactNode }) {
{/* Watchlist Sidebar — hidden on mobile */}
<WatchlistSidebar />
{/* Content + Right Analytics Column */}
<div className="flex flex-1 min-w-0">
{/* Center Content — full width on tablet/mobile, narrower on desktop */}
{/* 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 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>
);
}
/**
* Right analytics column containing Filings and Options DD panels.
* Desktop: flex row with center + right columns side by side
* Tablet: right column drops below center content (flex column)
* 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)
* - Mobile (<768px): Header + Content (single column)
*/
function RightAnalyticsColumn() {
export function LayoutShell({ children }: { children: React.ReactNode }) {
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">
{/* Collapsible Filings 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]">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>
<CollapsibleProvider>
<LayoutShellInner>{children}</LayoutShellInner>
</CollapsibleProvider>
);
}
+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);
}