import React, { useCallback, useEffect, useRef } from "react"; import { ChevronDown } from "lucide-react"; import { useBottomPanelStore, BottomPanelTabType, MIN_PANEL_HEIGHT, MAX_PANEL_HEIGHT, type BottomPanelTab, } from "@/stores/bottomPanelStore"; import { BottomPanelManager } from "./BottomPanelManager"; import { LogsTab, type LogsTabData } from "./dock/LogsTab"; import { TerminalTab, type TerminalTabData } from "./dock/TerminalTab"; import { YamlEditorTab, type YamlEditorTabData } from "./dock/YamlEditorTab"; import { cn } from "@/lib/utils"; /** * Bottom dock panel — DevTools-style. Houses tabs for pod logs, terminals, YAML * editing, resource creation, and Helm install/upgrade flows. * * Renders only when the store reports the panel as open and at least one tab * exists. Visibility, active tab, and tab list all live in the store; this * component owns drag-resize, keyboard shortcuts, and content dispatch. */ export function BottomPanel() { const isOpen = useBottomPanelStore((s) => s.isOpen); const height = useBottomPanelStore((s) => s.height); const tabs = useBottomPanelStore((s) => s.tabs); const activeTabId = useBottomPanelStore((s) => s.activeTabId); const setHeight = useBottomPanelStore((s) => s.setHeight); const closePanel = useBottomPanelStore((s) => s.closePanel); const closeActiveTab = useBottomPanelStore((s) => s.closeActiveTab); const closeTab = useBottomPanelStore((s) => s.closeTab); const nextTab = useBottomPanelStore((s) => s.nextTab); const previousTab = useBottomPanelStore((s) => s.previousTab); const dragStateRef = useRef<{ startY: number; startHeight: number } | null>(null); // ── Drag-to-resize ──────────────────────────────────────────────────────── const handleDragMouseDown = useCallback( (e: React.MouseEvent) => { e.preventDefault(); dragStateRef.current = { startY: e.clientY, startHeight: height }; const onMove = (ev: MouseEvent) => { if (!dragStateRef.current) return; const delta = dragStateRef.current.startY - ev.clientY; const next = dragStateRef.current.startHeight + delta; setHeight(next); }; const onUp = () => { dragStateRef.current = null; window.removeEventListener("mousemove", onMove); window.removeEventListener("mouseup", onUp); }; window.addEventListener("mousemove", onMove); window.addEventListener("mouseup", onUp); }, [height, setHeight] ); // ── Keyboard shortcuts ──────────────────────────────────────────────────── useEffect(() => { if (!isOpen) return; const onKey = (e: KeyboardEvent) => { // Ctrl+W — close active tab if (e.ctrlKey && !e.shiftKey && !e.altKey && e.key.toLowerCase() === "w") { e.preventDefault(); closeActiveTab(); return; } // Shift+Escape — hide the panel if (e.shiftKey && e.key === "Escape") { e.preventDefault(); closePanel(); return; } // Ctrl+. — next tab if (e.ctrlKey && !e.shiftKey && e.key === ".") { e.preventDefault(); nextTab(); return; } // Ctrl+, — previous tab if (e.ctrlKey && !e.shiftKey && e.key === ",") { e.preventDefault(); previousTab(); return; } }; window.addEventListener("keydown", onKey); return () => window.removeEventListener("keydown", onKey); }, [isOpen, closeActiveTab, closePanel, nextTab, previousTab]); if (!isOpen || tabs.length === 0) return null; const activeTab = tabs.find((t) => t.id === activeTabId) ?? tabs[0]!; const clampedHeight = Math.min(MAX_PANEL_HEIGHT, Math.max(MIN_PANEL_HEIGHT, height)); return (
{/* Drag handle */}
{/* Tab strip */}
{/* Active tab content */}
); } // ─── Tab dispatcher ─────────────────────────────────────────────────────────── interface TabContentProps { tab: BottomPanelTab; onClose: (id: string) => void; } function TabContent({ tab, onClose }: TabContentProps) { switch (tab.type) { case BottomPanelTabType.POD_LOGS: return ; case BottomPanelTabType.TERMINAL: return ; case BottomPanelTabType.EDIT_RESOURCE: case BottomPanelTabType.CREATE_RESOURCE: case BottomPanelTabType.INSTALL_CHART: case BottomPanelTabType.UPGRADE_CHART: return ( ); default: return (
Unsupported tab type.
); } }