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:
@@ -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) => (
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user