import { X } from 'lucide-react'; import { Fragment, type ReactNode } from 'react'; import { Button } from '@/components/ui/button.tsx'; import { ResizableHandle, ResizablePanel, ResizablePanelGroup, } from '@/components/ui/resizable.tsx'; import { Separator } from '@/components/ui/separator'; /** * Identifier for a dockable panel. Used both as the React key and to * look its descriptor up in {@link PanelDockProps.panels}. */ export type PanelKey = 'plan' | 'mcp' | 'skill' | 'permission' | 'knowledge'; /** * The presentation of a single panel: header chrome plus its already * data-bound content node. Built by the owner (ChatViewport) so that * the dock itself stays free of any business/data dependency. */ export interface PanelDescriptor { /** Header title shown in the panel's chrome. */ title: ReactNode; /** Optional leading icon shown next to the title. */ icon?: ReactNode; /** * Optional header-bar actions rendered to the left of the close * button — typically `
{children}
); }; /** * A pure layout engine for the right-hand dockable panel area. * * Knows nothing about MCP / tasks / skills — it just arranges whatever * panels {@link PanelDockProps.layout} describes into a grid of * horizontally-resizable columns, each containing up to two * vertically-resizable rows. The panel bodies arrive pre-built via * {@link PanelDockProps.panels}. * * Rendered as a sibling of the chat panel inside the outer horizontal * `ResizablePanelGroup`, so it emits one `ResizablePanel` per column * (with a `ResizableHandle` between columns) to share that group. * * @param layout - Controlled column/panel arrangement. * @param panels - Descriptor lookup for each panel key. * @param onClosePanel - Close-request callback. * @returns The column fragment, or `null` when no panels are open. */ export const PanelDock = ({ layout, panels, onClosePanel }: PanelDockProps) => { if (layout.length === 0) return null; return ( <> {layout.map((column, colIndex) => ( {colIndex > 0 && } {column.map((key, rowIndex) => { const descriptor = panels[key]; if (!descriptor) return null; return ( {rowIndex > 0 && ( )} onClosePanel(key)} > {descriptor.content} ); })} ))} ); };