/** * Terminal Tabs Component * * Manages multiple terminal tabs with add, rename, and close functionality. * Supports inline rename via double-click and context menu. */ import { useState, useRef, useEffect, useCallback } from 'react' import { Plus, X } from 'lucide-react' import type { TerminalInfo } from '@/lib/types' interface TerminalTabsProps { terminals: TerminalInfo[] activeTerminalId: string | null onSelect: (terminalId: string) => void onCreate: () => void onRename: (terminalId: string, newName: string) => void onClose: (terminalId: string) => void } interface ContextMenuState { visible: boolean x: number y: number terminalId: string | null } export function TerminalTabs({ terminals, activeTerminalId, onSelect, onCreate, onRename, onClose, }: TerminalTabsProps) { const [editingId, setEditingId] = useState(null) const [editValue, setEditValue] = useState('') const [contextMenu, setContextMenu] = useState({ visible: false, x: 0, y: 0, terminalId: null, }) const inputRef = useRef(null) const contextMenuRef = useRef(null) // Focus input when editing starts useEffect(() => { if (editingId && inputRef.current) { inputRef.current.focus() inputRef.current.select() } }, [editingId]) // Close context menu when clicking outside useEffect(() => { const handleClickOutside = (e: MouseEvent) => { if ( contextMenuRef.current && !contextMenuRef.current.contains(e.target as Node) ) { setContextMenu((prev) => ({ ...prev, visible: false })) } } if (contextMenu.visible) { document.addEventListener('mousedown', handleClickOutside) return () => document.removeEventListener('mousedown', handleClickOutside) } }, [contextMenu.visible]) // Start editing a terminal name const startEditing = useCallback((terminal: TerminalInfo) => { setEditingId(terminal.id) setEditValue(terminal.name) setContextMenu((prev) => ({ ...prev, visible: false })) }, []) // Handle edit submission const submitEdit = useCallback(() => { if (editingId && editValue.trim()) { onRename(editingId, editValue.trim()) } setEditingId(null) setEditValue('') }, [editingId, editValue, onRename]) // Cancel editing const cancelEdit = useCallback(() => { setEditingId(null) setEditValue('') }, []) // Handle key events during editing const handleKeyDown = useCallback( (e: React.KeyboardEvent) => { if (e.key === 'Enter') { e.preventDefault() submitEdit() } else if (e.key === 'Escape') { e.preventDefault() cancelEdit() } }, [submitEdit, cancelEdit] ) // Handle double-click to start editing const handleDoubleClick = useCallback( (terminal: TerminalInfo) => { startEditing(terminal) }, [startEditing] ) // Handle context menu const handleContextMenu = useCallback( (e: React.MouseEvent, terminalId: string) => { e.preventDefault() setContextMenu({ visible: true, x: e.clientX, y: e.clientY, terminalId, }) }, [] ) // Handle context menu actions const handleContextMenuRename = useCallback(() => { if (contextMenu.terminalId) { const terminal = terminals.find((t) => t.id === contextMenu.terminalId) if (terminal) { startEditing(terminal) } } }, [contextMenu.terminalId, terminals, startEditing]) const handleContextMenuClose = useCallback(() => { if (contextMenu.terminalId) { onClose(contextMenu.terminalId) } setContextMenu((prev) => ({ ...prev, visible: false })) }, [contextMenu.terminalId, onClose]) // Handle tab close with confirmation if needed const handleClose = useCallback( (e: React.MouseEvent, terminalId: string) => { e.stopPropagation() onClose(terminalId) }, [onClose] ) return (
{/* Terminal tabs */} {terminals.map((terminal) => (
onSelect(terminal.id)} onDoubleClick={() => handleDoubleClick(terminal)} onContextMenu={(e) => handleContextMenu(e, terminal.id)} > {editingId === terminal.id ? ( setEditValue(e.target.value)} onBlur={submitEdit} onKeyDown={handleKeyDown} className="bg-neo-card text-neo-text px-1 py-0 text-sm font-mono border-2 border-black w-24 outline-none" onClick={(e) => e.stopPropagation()} /> ) : ( {terminal.name} )} {/* Close button */} {terminals.length > 1 && ( )}
))} {/* Add new terminal button */} {/* Context menu */} {contextMenu.visible && (
{terminals.length > 1 && ( )}
)}
) }