fix: restore Escape key support in LayoutShell (was overwritten by other task)
This commit is contained in:
@@ -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 */}
|
||||
<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>
|
||||
{/* 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>
|
||||
</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.
|
||||
* 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 CompactWatchlistBar() {
|
||||
export function LayoutShell({ children }: { children: React.ReactNode }) {
|
||||
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)
|
||||
*/
|
||||
function RightAnalyticsColumn() {
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user