fix: unified fullscreen terminal header
- Add showControls prop to TerminalComponent to optionally hide internal header - Add reset() method to TerminalRef for external reset control - TerminalPage now renders a unified fullscreen header bar combining: - Session tabs (TerminalSessionTabs) - Terminal controls (status dot, A-, A+, Reset, Exit Fullscreen) - Unified header is always visible in fullscreen (no hover-to-reveal) - TerminalComponent internal header hidden when in fullscreen mode - Remove old CSS that hid session tabs with opacity:0 until hover Quality gates: pytest 188 passed, frontend typecheck clean Fixes: terminal-fullscreen-unified-header
This commit is contained in:
@@ -20,6 +20,7 @@ export interface TerminalProps {
|
||||
sessionId?: string;
|
||||
onClose?: () => void;
|
||||
isMobile?: boolean;
|
||||
showControls?: boolean;
|
||||
activeModifier?: ModifierKey | null;
|
||||
onModifierChange?: (modifier: ModifierKey | null) => void;
|
||||
onTerminalReady?: (
|
||||
@@ -38,6 +39,7 @@ export interface TerminalProps {
|
||||
export interface TerminalRef {
|
||||
fit: () => void;
|
||||
focus: () => void;
|
||||
reset: () => void;
|
||||
}
|
||||
|
||||
const FONT_SIZE_KEY = "terminal-font-size";
|
||||
@@ -53,6 +55,7 @@ export const TerminalComponent = React.forwardRef<TerminalRef, TerminalProps>(
|
||||
sessionId,
|
||||
onClose,
|
||||
isMobile = false,
|
||||
showControls = true,
|
||||
activeModifier,
|
||||
onModifierChange,
|
||||
onTerminalReady,
|
||||
@@ -471,6 +474,11 @@ export const TerminalComponent = React.forwardRef<TerminalRef, TerminalProps>(
|
||||
focus: () => {
|
||||
termRef.current?.focus();
|
||||
},
|
||||
reset: () => {
|
||||
if (wsRef.current?.readyState === WebSocket.OPEN) {
|
||||
wsRef.current.send(JSON.stringify({ type: "reset" }));
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
// Update parent about status changes
|
||||
@@ -561,78 +569,80 @@ export const TerminalComponent = React.forwardRef<TerminalRef, TerminalProps>(
|
||||
|
||||
return (
|
||||
<div className={`terminal-wrapper ${isMobile ? "mobile" : ""}`}>
|
||||
<div className="terminal-header">
|
||||
<div className="terminal-header-left">
|
||||
<div className="terminal-status">
|
||||
<span
|
||||
className={`status-dot ${status}`}
|
||||
aria-label={`Terminal status: ${status}`}
|
||||
/>
|
||||
<span className="status-text">
|
||||
{status === "resetting"
|
||||
? "Resetting..."
|
||||
: reconnectAttemptsRef.current > 0 && status !== "connected"
|
||||
? `Reconnecting (${reconnectAttemptsRef.current}/${RECONNECT_ATTEMPTS})...`
|
||||
: status}
|
||||
</span>
|
||||
{showControls && (
|
||||
<div className="terminal-header">
|
||||
<div className="terminal-header-left">
|
||||
<div className="terminal-status">
|
||||
<span
|
||||
className={`status-dot ${status}`}
|
||||
aria-label={`Terminal status: ${status}`}
|
||||
/>
|
||||
<span className="status-text">
|
||||
{status === "resetting"
|
||||
? "Resetting..."
|
||||
: reconnectAttemptsRef.current > 0 && status !== "connected"
|
||||
? `Reconnecting (${reconnectAttemptsRef.current}/${RECONNECT_ATTEMPTS})...`
|
||||
: status}
|
||||
</span>
|
||||
</div>
|
||||
{isMobile && (
|
||||
<>
|
||||
<button
|
||||
className="terminal-header-button"
|
||||
onClick={handleCopy}
|
||||
type="button"
|
||||
aria-label="Copy selection"
|
||||
>
|
||||
Copy
|
||||
</button>
|
||||
<button
|
||||
className="terminal-header-button"
|
||||
onClick={handlePaste}
|
||||
type="button"
|
||||
aria-label="Paste from clipboard"
|
||||
>
|
||||
Paste
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{isMobile && (
|
||||
<>
|
||||
<button
|
||||
className="terminal-header-button"
|
||||
onClick={handleCopy}
|
||||
type="button"
|
||||
aria-label="Copy selection"
|
||||
>
|
||||
Copy
|
||||
</button>
|
||||
<button
|
||||
className="terminal-header-button"
|
||||
onClick={handlePaste}
|
||||
type="button"
|
||||
aria-label="Paste from clipboard"
|
||||
>
|
||||
Paste
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div className="terminal-header-right">
|
||||
<button
|
||||
className="terminal-header-button"
|
||||
onClick={() => handleFontSizeChange(-1)}
|
||||
type="button"
|
||||
aria-label="Decrease font size"
|
||||
>
|
||||
A-
|
||||
</button>
|
||||
<button
|
||||
className="terminal-header-button"
|
||||
onClick={() => handleFontSizeChange(1)}
|
||||
type="button"
|
||||
aria-label="Increase font size"
|
||||
>
|
||||
A+
|
||||
</button>
|
||||
<button
|
||||
className="terminal-header-button"
|
||||
onClick={() => setShowResetConfirm(true)}
|
||||
type="button"
|
||||
aria-label="Reset terminal"
|
||||
>
|
||||
Reset
|
||||
</button>
|
||||
{onClose && (
|
||||
<div className="terminal-header-right">
|
||||
<button
|
||||
className="terminal-close"
|
||||
onClick={onClose}
|
||||
className="terminal-header-button"
|
||||
onClick={() => handleFontSizeChange(-1)}
|
||||
type="button"
|
||||
aria-label="Decrease font size"
|
||||
>
|
||||
Close
|
||||
A-
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
className="terminal-header-button"
|
||||
onClick={() => handleFontSizeChange(1)}
|
||||
type="button"
|
||||
aria-label="Increase font size"
|
||||
>
|
||||
A+
|
||||
</button>
|
||||
<button
|
||||
className="terminal-header-button"
|
||||
onClick={() => setShowResetConfirm(true)}
|
||||
type="button"
|
||||
aria-label="Reset terminal"
|
||||
>
|
||||
Reset
|
||||
</button>
|
||||
{onClose && (
|
||||
<button
|
||||
className="terminal-close"
|
||||
onClick={onClose}
|
||||
type="button"
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{showResetConfirm && (
|
||||
<div className="terminal-reset-confirm">
|
||||
<div className="terminal-reset-confirm-content">
|
||||
|
||||
Reference in New Issue
Block a user