refactor: extract TerminalPage components

- Extract use-terminal-page hook for terminal state and effects
- Extract MobileTerminalView and DesktopTerminalView components
- Slim TerminalPage from 571 to 112 lines

Quality gates: tsc --noEmit passes, npm run build passes
This commit is contained in:
Developer
2026-06-05 19:17:04 +00:00
parent d7d5baa41a
commit e7f219f7c3
4 changed files with 753 additions and 564 deletions
@@ -0,0 +1,198 @@
import React from "react";
import { TerminalComponent, type TerminalRef } from "./terminal";
import { TerminalSessionTabs, type TerminalSessionInfo } from "./terminal-session-tabs";
import type { TerminalSession } from "../../../api/terminal";
interface Props {
instanceId: string;
sessions: TerminalSession[];
sessionInfos: TerminalSessionInfo[];
activeSessionId: string;
terminalRefs: React.MutableRefObject<Record<string, React.RefObject<TerminalRef>>>;
isFullscreen: boolean;
status: string;
error: string | null;
loading: boolean;
showResetConfirm: boolean;
onFullscreenClick: (e: React.MouseEvent<HTMLElement>) => void;
onSelect: (id: string) => void;
onClose: (id: string) => void;
onCreate: () => void;
onRename: (id: string, name: string) => void;
onNavigateBack: () => void;
onToggleFullscreen: () => void;
onFontSizeChange: (delta: number) => void;
onShowResetConfirm: () => void;
onHideResetConfirm: () => void;
onReset: () => void;
onTerminalReady: (
sendData: (data: string) => void,
status: "connecting" | "connected" | "disconnected" | "error" | "resetting",
focusInput: () => void,
changeFontSize: (delta: number) => void,
) => void;
}
export const DesktopTerminalView: React.FC<Props> = ({
instanceId,
sessions,
sessionInfos,
activeSessionId,
terminalRefs,
isFullscreen,
status,
error,
loading,
showResetConfirm,
onFullscreenClick,
onSelect,
onClose,
onCreate,
onRename,
onNavigateBack,
onToggleFullscreen,
onFontSizeChange,
onShowResetConfirm,
onHideResetConfirm,
onReset,
onTerminalReady,
}) => {
return (
<section
className={`terminal-page ${isFullscreen ? "fullscreen" : ""}`}
onClick={onFullscreenClick}
>
{!isFullscreen && (
<div className="terminal-page-header">
<button className="secondary-button" onClick={onNavigateBack} type="button">
Back
</button>
<h1>Terminal</h1>
<button
className="secondary-button"
onClick={onToggleFullscreen}
type="button"
title="Toggle fullscreen (Alt+Shift+F)"
>
{isFullscreen ? "Exit Fullscreen" : "Fullscreen"}
</button>
</div>
)}
{isFullscreen ? (
<div className="terminal-fullscreen-header">
<div className="terminal-fullscreen-header-tabs">
<TerminalSessionTabs
sessions={sessionInfos}
activeSessionId={activeSessionId}
onSelect={onSelect}
onClose={onClose}
onCreate={onCreate}
onRename={onRename}
isMobile={false}
/>
</div>
<div className="terminal-fullscreen-header-controls">
<span
className={`terminal-fullscreen-status status-dot ${status}`}
aria-label={`Terminal status: ${status}`}
/>
<button
className="terminal-header-button"
onClick={() => onFontSizeChange(-1)}
type="button"
aria-label="Decrease font size"
>
A-
</button>
<button
className="terminal-header-button"
onClick={() => onFontSizeChange(1)}
type="button"
aria-label="Increase font size"
>
A+
</button>
<button
className="terminal-header-button"
onClick={onShowResetConfirm}
type="button"
aria-label="Reset terminal"
>
Reset
</button>
<button
className="terminal-close"
onClick={onToggleFullscreen}
type="button"
title="Exit fullscreen (Esc)"
>
Exit
</button>
</div>
{showResetConfirm && (
<div className="terminal-reset-confirm">
<div className="terminal-reset-confirm-content">
<p>
Reset terminal? This will kill the current shell session and
start fresh.
</p>
<div className="terminal-reset-confirm-buttons">
<button
className="terminal-reset-confirm-button cancel"
onClick={onHideResetConfirm}
type="button"
>
Cancel
</button>
<button
className="terminal-reset-confirm-button confirm"
onClick={() => {
onHideResetConfirm();
onReset();
}}
type="button"
>
Reset
</button>
</div>
</div>
</div>
)}
</div>
) : (
<TerminalSessionTabs
sessions={sessionInfos}
activeSessionId={activeSessionId}
onSelect={onSelect}
onClose={onClose}
onCreate={onCreate}
onRename={onRename}
isMobile={false}
/>
)}
<div className="terminal-page-content">
{error && <div className="terminal-error-banner">{error}</div>}
{sessions
.filter((session) => session.id === activeSessionId)
.map((session) => (
<div key={session.id} className="terminal-instance active">
<TerminalComponent
ref={terminalRefs.current[session.id]}
instanceId={instanceId}
sessionId={session.id}
onClose={() => onClose(session.id)}
isMobile={false}
showControls={!isFullscreen}
onTerminalReady={onTerminalReady}
/>
</div>
))}
{sessions.length === 0 && !loading && (
<div className="terminal-empty-state">
<p>No terminal sessions. Press Alt+Shift+N to create one.</p>
</div>
)}
</div>
</section>
);
};