fix: restore special keys bar on mobile terminal

- Add SpecialKeysStrip and SpecialKeysPanel to mobile terminal page
- Store sendData and focusInput refs via onTerminalReady callback
- Pass activeModifier/onModifierChange to TerminalComponent on mobile
- Add virtual keyboard padding to prevent keyboard from covering terminal
- Special keys bar sits at bottom of viewport, panel opens as overlay
This commit is contained in:
Alex Blank
2026-05-29 17:36:08 +02:00
parent 946ac6f66a
commit 97ebc19313
+38 -2
View File
@@ -6,10 +6,14 @@ import {
type TerminalSessionInfo,
} from "../components/terminal-session-tabs";
import { Icon } from "../components/icon";
import { SpecialKeysStrip } from "../components/special-keys-strip";
import { SpecialKeysPanel } from "../components/special-keys-panel";
import { useMobileViewport } from "../hooks/use-mobile-viewport";
import { useAutoHide } from "../hooks/use-auto-hide";
import { useVirtualKeyboard } from "../hooks/use-virtual-keyboard";
import { useTerminalSessions } from "../hooks/use-terminal-sessions";
import type { TerminalSession } from "../api/terminal";
import type { ModifierKey } from "../hooks/use-special-keys";
const SESSIONS_TO_INFO = (sessions: TerminalSession[]): TerminalSessionInfo[] =>
sessions.map((s) => ({
@@ -40,7 +44,12 @@ export const TerminalPage: React.FC = () => {
Record<string, TerminalStatus>
>({});
const changeFontSizeRef = useRef<((delta: number) => void) | null>(null);
const sendDataRef = useRef<((data: string) => void) | null>(null);
const focusInputRef = useRef<(() => void) | null>(null);
const [showResetConfirm, setShowResetConfirm] = useState(false);
const [showSpecialKeysPanel, setShowSpecialKeysPanel] = useState(false);
const [activeModifier, setActiveModifier] = useState<ModifierKey | null>(null);
const { isOpen: isKeyboardOpen, height: keyboardHeight } = useVirtualKeyboard();
const {
sessions,
@@ -206,15 +215,17 @@ export const TerminalPage: React.FC = () => {
const handleTerminalReady = useCallback(
(
_sendData: (data: string) => void,
sendData: (data: string) => void,
status: TerminalStatus,
_focusInput: () => void,
focusInput: () => void,
changeFontSize: (delta: number) => void,
) => {
setTerminalStatuses((prev) => ({
...prev,
[activeSessionId ?? "default"]: status,
}));
sendDataRef.current = sendData;
focusInputRef.current = focusInput;
changeFontSizeRef.current = changeFontSize;
},
[activeSessionId],
@@ -224,6 +235,10 @@ export const TerminalPage: React.FC = () => {
changeFontSizeRef.current?.(delta);
}, []);
const handleSendKey = useCallback((data: string) => {
sendDataRef.current?.(data);
}, []);
const handleReset = useCallback(() => {
if (activeSessionId && terminalRefs.current[activeSessionId]) {
terminalRefs.current[activeSessionId].current?.reset();
@@ -318,6 +333,7 @@ export const TerminalPage: React.FC = () => {
{/* Terminal content — always fills full viewport */}
<div
className="terminal-page-content mobile-full"
style={{ paddingBottom: isKeyboardOpen ? keyboardHeight : 0 }}
onClick={() => headerAutoHide.toggle()}
>
{error && <div className="terminal-error-banner">{error}</div>}
@@ -332,6 +348,8 @@ export const TerminalPage: React.FC = () => {
onClose={() => handleClose(session.id)}
isMobile={true}
showControls={false}
activeModifier={activeModifier}
onModifierChange={setActiveModifier}
onTerminalReady={handleTerminalReady}
/>
</div>
@@ -342,6 +360,24 @@ export const TerminalPage: React.FC = () => {
</div>
)}
</div>
<SpecialKeysStrip
onSend={handleSendKey}
isVisible={!showSpecialKeysPanel}
onMoreClick={() => setShowSpecialKeysPanel(true)}
onKeepFocus={() => focusInputRef.current?.()}
activeModifier={activeModifier}
onModifierChange={setActiveModifier}
/>
<SpecialKeysPanel
onSend={handleSendKey}
isOpen={showSpecialKeysPanel}
onClose={() => setShowSpecialKeysPanel(false)}
onKeepFocus={() => focusInputRef.current?.()}
activeModifier={activeModifier}
onModifierChange={setActiveModifier}
/>
</section>
);
}