/** * GraphControls — floating toolbar over the canvas. * Positioned below system buttons (top-10) to avoid overlap on macOS. */ import { useCallback, useEffect, useRef, useState } from 'react'; import { Columns3, Expand, Settings2, Eye, EyeOff, Maximize2, Pause, Pin, Play, Server, X, ZoomIn, ZoomOut, } from 'lucide-react'; export interface GraphFilterState { showTasks: boolean; showProcesses: boolean; showEdges: boolean; paused: boolean; } export interface GraphControlsProps { filters: GraphFilterState; onFiltersChange: (filters: GraphFilterState) => void; onZoomIn: () => void; onZoomOut: () => void; onZoomToFit: () => void; onRequestClose?: () => void; onRequestPinAsTab?: () => void; onRequestFullscreen?: () => void; teamName: string; teamColor?: string; isAlive?: boolean; } export function GraphControls({ filters, onFiltersChange, onZoomIn, onZoomOut, onZoomToFit, onRequestClose, onRequestPinAsTab, onRequestFullscreen, teamName, teamColor, isAlive, }: GraphControlsProps): React.JSX.Element { const [isSettingsOpen, setIsSettingsOpen] = useState(false); const settingsRef = useRef(null); const toggle = useCallback( (key: keyof GraphFilterState) => { onFiltersChange({ ...filters, [key]: !filters[key] }); }, [filters, onFiltersChange], ); useEffect(() => { if (!isSettingsOpen) return; const handlePointerDown = (event: MouseEvent): void => { const target = event.target as Node | null; if (!target) return; if (settingsRef.current?.contains(target)) return; setIsSettingsOpen(false); }; const handleKeyDown = (event: KeyboardEvent): void => { if (event.key === 'Escape') { setIsSettingsOpen(false); } }; window.addEventListener('mousedown', handlePointerDown); window.addEventListener('keydown', handleKeyDown); return () => { window.removeEventListener('mousedown', handlePointerDown); window.removeEventListener('keydown', handleKeyDown); }; }, [isSettingsOpen]); const nameColor = teamColor ?? '#aaeeff'; return ( <>
{isAlive && (
)} {teamName}
setIsSettingsOpen((value) => !value)} icon={} label="View" active={isSettingsOpen} />
{isSettingsOpen && (
toggle('showTasks')} icon={} label="Tasks" block /> toggle('showProcesses')} icon={} label="Processes" block /> toggle('showEdges')} icon={filters.showEdges ? : } label="Edges" block />
toggle('paused')} icon={filters.paused ? : } label={filters.paused ? 'Resume' : 'Pause'} block />
)}
{onRequestPinAsTab && } />} {onRequestFullscreen && ( } label="Fullscreen" /> )} {onRequestClose && } />}
} /> } label="Fit" /> } />
); } // ─── Primitives ───────────────────────────────────────────────────────────── function ToolbarButton({ onClick, icon, label, active = false, }: { onClick?: () => void; icon: React.ReactNode; label?: string; active?: boolean; }): React.JSX.Element { return ( ); } function ToolbarToggle({ active, onClick, icon, label, block = false, }: { active: boolean; onClick: () => void; icon: React.ReactNode; label: string; block?: boolean; }): React.JSX.Element { return ( ); }