feat: mobile auto-hide header and tabs for multi-session terminal

- Add useAutoHide hook to TerminalPage for mobile header/tab strip
- Header and tabs auto-hide after 3s, tap to reveal
- Add CSS transitions for smooth show/hide on mobile
- Fullscreen mobile mode hides header and tabs completely
This commit is contained in:
2026-05-28 14:10:06 +02:00
parent 8e5e815ac9
commit 8eb851793d
2 changed files with 55 additions and 28 deletions
+36 -28
View File
@@ -6,6 +6,7 @@ import {
type TerminalSessionInfo, type TerminalSessionInfo,
} from "../components/terminal-session-tabs"; } from "../components/terminal-session-tabs";
import { useMobileViewport } from "../hooks/use-mobile-viewport"; import { useMobileViewport } from "../hooks/use-mobile-viewport";
import { useAutoHide } from "../hooks/use-auto-hide";
import { useTerminalSessions } from "../hooks/use-terminal-sessions"; import { useTerminalSessions } from "../hooks/use-terminal-sessions";
import type { TerminalSession } from "../api/terminal"; import type { TerminalSession } from "../api/terminal";
@@ -26,6 +27,7 @@ export const TerminalPage: React.FC = () => {
const isMobile = useMobileViewport(); const isMobile = useMobileViewport();
const [isFullscreen, setIsFullscreen] = useState(false); const [isFullscreen, setIsFullscreen] = useState(false);
const terminalRefs = useRef<Record<string, React.RefObject<TerminalRef>>>({}); const terminalRefs = useRef<Record<string, React.RefObject<TerminalRef>>>({});
const headerAutoHide = useAutoHide({ timeout: 3000, enabled: isMobile });
const { const {
sessions, sessions,
@@ -193,34 +195,40 @@ export const TerminalPage: React.FC = () => {
<section <section
className={`terminal-page mobile ${isFullscreen ? "fullscreen" : ""}`} className={`terminal-page mobile ${isFullscreen ? "fullscreen" : ""}`}
> >
{!isFullscreen && ( <div
<div className="terminal-page-header mobile-header"> className={`terminal-page-header mobile-header ${headerAutoHide.isVisible ? "visible" : "hidden"}`}
<button onClick={() => headerAutoHide.show()}
className="secondary-button" >
onClick={() => navigate(-1)} <button
type="button" className="secondary-button"
> onClick={() => navigate(-1)}
Back type="button"
</button> >
<h1>Terminal</h1> Back
<button </button>
className="secondary-button" <h1>Terminal</h1>
onClick={() => setIsFullscreen((p) => !p)} <button
type="button" className="secondary-button"
> onClick={() => setIsFullscreen((p) => !p)}
{isFullscreen ? "Exit" : "Fullscreen"} type="button"
</button> >
</div> {isFullscreen ? "Exit" : "Fullscreen"}
)} </button>
<TerminalSessionTabs </div>
sessions={sessionInfos} <div
activeSessionId={activeSessionId ?? ""} className={`mobile-tabs-container ${headerAutoHide.isVisible ? "visible" : "hidden"}`}
onSelect={handleSelect} onClick={() => headerAutoHide.show()}
onClose={handleClose} >
onCreate={handleCreate} <TerminalSessionTabs
onRename={handleRename} sessions={sessionInfos}
isMobile={true} activeSessionId={activeSessionId ?? ""}
/> onSelect={handleSelect}
onClose={handleClose}
onCreate={handleCreate}
onRename={handleRename}
isMobile={true}
/>
</div>
<div className="terminal-page-content"> <div className="terminal-page-content">
{error && <div className="terminal-error-banner">{error}</div>} {error && <div className="terminal-error-banner">{error}</div>}
{sessions.map((session) => ( {sessions.map((session) => (
+19
View File
@@ -2904,6 +2904,25 @@ a.nav-item,
opacity: 1; opacity: 1;
} }
/* Mobile auto-hide header and tabs */
.terminal-page.mobile .terminal-page-header,
.mobile-tabs-container {
transition: transform 0.3s ease, opacity 0.3s ease;
}
.terminal-page.mobile .terminal-page-header.hidden,
.mobile-tabs-container.hidden {
transform: translateY(-100%);
opacity: 0;
pointer-events: none;
}
.terminal-page.mobile .terminal-page-header.visible,
.mobile-tabs-container.visible {
transform: translateY(0);
opacity: 1;
}
/* Mobile fullscreen */ /* Mobile fullscreen */
@media (max-width: 767px) { @media (max-width: 767px) {
.terminal-page.fullscreen { .terminal-page.fullscreen {