feat(app): close (X) on panel header + Close button on stopped overlay

Wires the existing closeSurfaceCmd into the panel header (red-on-hover X next
to zoom) and adds a Close button to the stopped overlay, so a panel — including
an empty/stopped one — can be dismissed instead of resumed/restarted.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-15 16:47:43 +07:00
parent d62628be8d
commit 39bb8e5fee
+10 -2
View File
@@ -1,12 +1,12 @@
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import { Maximize2, Minimize2, RotateCw, GripVertical, Play } from "lucide-react"; import { Maximize2, Minimize2, RotateCw, GripVertical, Play, X } from "lucide-react";
import { Terminal } from "@xterm/xterm"; import { Terminal } from "@xterm/xterm";
import { TerminalView } from "./TerminalView"; import { TerminalView } from "./TerminalView";
import { SearchBar } from "./SearchBar"; import { SearchBar } from "./SearchBar";
import { StatusRing } from "./StatusRing"; import { StatusRing } from "./StatusRing";
import { COLORS, FONT, STATE_COLOR } from "./theme"; import { COLORS, FONT, STATE_COLOR } from "./theme";
import type { LayoutNode, SurfaceState, SurfaceView } from "./layoutTypes"; import type { LayoutNode, SurfaceState, SurfaceView } from "./layoutTypes";
import { setRatios, restartSurface, setZoom, moveSurface, attachSurface, detachSurface } from "./socketBridge"; import { setRatios, restartSurface, setZoom, moveSurface, attachSurface, detachSurface, closeSurfaceCmd } from "./socketBridge";
interface Props { interface Props {
workspaceId: string; workspaceId: string;
@@ -193,6 +193,10 @@ function Leaf({ id, workspaceId, running, states, surfaces, focusedId, onFocus,
style={{ display: "flex", alignItems: "center", gap: 6, padding: "6px 14px", background: COLORS.bgElevated, color: COLORS.textPrimary, border: `1px solid ${COLORS.borderStrong}`, borderRadius: 7, fontSize: 12 }}> style={{ display: "flex", alignItems: "center", gap: 6, padding: "6px 14px", background: COLORS.bgElevated, color: COLORS.textPrimary, border: `1px solid ${COLORS.borderStrong}`, borderRadius: 7, fontSize: 12 }}>
<RotateCw size={13} /> Restart fresh <RotateCw size={13} /> Restart fresh
</button> </button>
<button onClick={() => void closeSurfaceCmd(id)}
style={{ display: "flex", alignItems: "center", gap: 6, padding: "6px 14px", background: "transparent", color: COLORS.textSecondary, border: `1px solid ${COLORS.borderStrong}`, borderRadius: 7, fontSize: 12 }}>
<X size={13} /> Close
</button>
{zoomed === id && ( {zoomed === id && (
<button onClick={() => void setZoom(workspaceId, null)} <button onClick={() => void setZoom(workspaceId, null)}
style={{ display: "flex", alignItems: "center", gap: 6, padding: "6px 14px", background: "transparent", color: COLORS.textSecondary, border: `1px solid ${COLORS.borderStrong}`, borderRadius: 7, fontSize: 12 }}> style={{ display: "flex", alignItems: "center", gap: 6, padding: "6px 14px", background: "transparent", color: COLORS.textSecondary, border: `1px solid ${COLORS.borderStrong}`, borderRadius: 7, fontSize: 12 }}>
@@ -228,6 +232,10 @@ function Leaf({ id, workspaceId, running, states, surfaces, focusedId, onFocus,
onMouseDown={(e) => { e.stopPropagation(); void setZoom(workspaceId, null); }} /> onMouseDown={(e) => { e.stopPropagation(); void setZoom(workspaceId, null); }} />
: <Maximize2 size={13} color={COLORS.textMuted} style={{ cursor: "pointer" }} aria-label="Zoom" : <Maximize2 size={13} color={COLORS.textMuted} style={{ cursor: "pointer" }} aria-label="Zoom"
onMouseDown={(e) => { e.stopPropagation(); onFocus(id); void setZoom(workspaceId, id); }} />} onMouseDown={(e) => { e.stopPropagation(); onFocus(id); void setZoom(workspaceId, id); }} />}
<X size={13} color={COLORS.textMuted} style={{ cursor: "pointer" }} aria-label="Close panel"
onMouseDown={(e) => { e.stopPropagation(); void closeSurfaceCmd(id); }}
onMouseEnter={(e) => { e.currentTarget.style.color = COLORS.stError; }}
onMouseLeave={(e) => { e.currentTarget.style.color = COLORS.textMuted; }} />
</div> </div>
<div style={{ flex: 1, minHeight: 0 }}> <div style={{ flex: 1, minHeight: 0 }}>
<TerminalView key={id} surfaceId={id} font={font} palette={palette} /> <TerminalView key={id} surfaceId={id} font={font} palette={palette} />