"use client"; import { useState, useEffect, useRef, type ReactNode } from "react"; import { motion, AnimatePresence } from "motion/react"; import { Search, Map as MapIcon, Settings, Keyboard, X, Github, Info, Clock, } from "lucide-react"; import type { City } from "@/lib/cities"; import type { MapStyle } from "@/lib/map-styles"; import type { FlightState } from "@/lib/opensky"; import { SearchContent } from "@/components/ui/control-panel-search"; import { StyleContent } from "@/components/ui/control-panel-styles"; import { SettingsContent, ShortcutsContent, AboutContent, ChangelogContent, } from "@/components/ui/control-panel-settings"; type TabId = | "search" | "style" | "settings" | "shortcuts" | "changelog" | "about"; const MAIN_TABS: { id: TabId; icon: typeof Search; label: string; }[] = [ { id: "search", icon: Search, label: "Search" }, { id: "style", icon: MapIcon, label: "Map Style" }, ]; const PANEL_TABS = [ ...MAIN_TABS, { id: "settings" as TabId, icon: Settings, label: "Settings" }, { id: "shortcuts" as TabId, icon: Keyboard, label: "Shortcuts" }, { id: "changelog" as TabId, icon: Clock, label: "Changelog" }, { id: "about" as TabId, icon: Info, label: "About" }, ]; type ControlPanelProps = { activeCity: City; onSelectCity: (city: City) => void; activeStyle: MapStyle; onSelectStyle: (style: MapStyle) => void; flights: FlightState[]; activeFlightIcao24: string | null; onLookupFlight: (query: string, enterFpv?: boolean) => Promise; }; export function ControlPanel({ activeCity, onSelectCity, activeStyle, onSelectStyle, flights, activeFlightIcao24, onLookupFlight, }: ControlPanelProps) { const [openTab, setOpenTab] = useState(null); useEffect(() => { function handleOpenSearch() { setOpenTab("search"); } function handleOpenShortcuts() { setOpenTab("shortcuts"); } window.addEventListener("aeris:open-search", handleOpenSearch); window.addEventListener("aeris:open-shortcuts", handleOpenShortcuts); return () => { window.removeEventListener("aeris:open-search", handleOpenSearch); window.removeEventListener("aeris:open-shortcuts", handleOpenShortcuts); }; }, []); const open = (tab: TabId) => setOpenTab(tab); const close = () => setOpenTab(null); return ( <> {MAIN_TABS.map(({ id, icon: Icon, label }) => ( open(id)} className="flex h-9 w-9 items-center justify-center rounded-xl backdrop-blur-2xl transition-colors" style={{ borderWidth: 1, borderColor: "rgb(var(--ui-fg) / 0.06)", backgroundColor: "rgb(var(--ui-fg) / 0.03)", color: "rgb(var(--ui-fg) / 0.5)", }} whileHover={{ scale: 1.05 }} whileTap={{ scale: 0.95 }} aria-label={label} > ))} open("settings")} className="flex h-9 w-9 items-center justify-center rounded-xl backdrop-blur-2xl transition-colors" style={{ borderWidth: 1, borderColor: "rgb(var(--ui-fg) / 0.06)", backgroundColor: "rgb(var(--ui-fg) / 0.03)", color: "rgb(var(--ui-fg) / 0.5)", }} whileHover={{ scale: 1.05 }} whileTap={{ scale: 0.95 }} aria-label="Settings" > {openTab && ( { onSelectCity(c); close(); }} activeStyle={activeStyle} onSelectStyle={onSelectStyle} flights={flights} activeFlightIcao24={activeFlightIcao24} onLookupFlight={onLookupFlight} /> )} ); } function PanelDialog({ activeTab, onTabChange, onClose, activeCity, onSelectCity, activeStyle, onSelectStyle, flights, activeFlightIcao24, onLookupFlight, }: { activeTab: TabId; onTabChange: (tab: TabId) => void; onClose: () => void; activeCity: City; onSelectCity: (city: City) => void; activeStyle: MapStyle; onSelectStyle: (style: MapStyle) => void; flights: FlightState[]; activeFlightIcao24: string | null; onLookupFlight: (query: string, enterFpv?: boolean) => Promise; }) { const dialogRef = useRef(null); useEffect(() => { function handleKey(e: KeyboardEvent) { if (e.key === "Escape") onClose(); } window.addEventListener("keydown", handleKey); return () => window.removeEventListener("keydown", handleKey); }, [onClose]); useEffect(() => { const dialog = dialogRef.current; if (!dialog) return; const focusable = dialog.querySelectorAll( 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])', ); if (focusable.length === 0) return; const first = focusable[0]; first.focus(); function trapFocus(e: KeyboardEvent) { if (e.key !== "Tab") return; const elements = dialog!.querySelectorAll( 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])', ); const f = elements[0]; const l = elements[elements.length - 1]; if (e.shiftKey) { if (document.activeElement === f) { e.preventDefault(); l.focus(); } } else { if (document.activeElement === l) { e.preventDefault(); f.focus(); } } } dialog.addEventListener("keydown", trapFocus); return () => dialog.removeEventListener("keydown", trapFocus); }, [activeTab]); return ( <>
{/* Desktop sidebar (hidden on mobile) */}

Controls

Powered by OpenSky Network

{/* Mobile header */}

{PANEL_TABS.find((t) => t.id === activeTab)?.label}

{/* Desktop header */}

{PANEL_TABS.find((t) => t.id === activeTab)?.label}

{activeTab === "search" && ( { const found = await onLookupFlight(query, enterFpv); if (found) onClose(); return found; }} /> )} {activeTab === "style" && ( )} {activeTab === "settings" && ( )} {activeTab === "shortcuts" && ( )} {activeTab === "changelog" && ( )} {activeTab === "about" && ( )}
{/* Mobile tab bar */}
); } function TabContent({ children }: { children: ReactNode }) { return ( {children} ); }