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, type TerminalSessionInfo,
} from "../components/terminal-session-tabs"; } from "../components/terminal-session-tabs";
import { Icon } from "../components/icon"; 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 { useMobileViewport } from "../hooks/use-mobile-viewport";
import { useAutoHide } from "../hooks/use-auto-hide"; import { useAutoHide } from "../hooks/use-auto-hide";
import { useVirtualKeyboard } from "../hooks/use-virtual-keyboard";
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";
import type { ModifierKey } from "../hooks/use-special-keys";
const SESSIONS_TO_INFO = (sessions: TerminalSession[]): TerminalSessionInfo[] => const SESSIONS_TO_INFO = (sessions: TerminalSession[]): TerminalSessionInfo[] =>
sessions.map((s) => ({ sessions.map((s) => ({
@@ -40,7 +44,12 @@ export const TerminalPage: React.FC = () => {
Record<string, TerminalStatus> Record<string, TerminalStatus>
>({}); >({});
const changeFontSizeRef = useRef<((delta: number) => void) | null>(null); 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 [showResetConfirm, setShowResetConfirm] = useState(false);
const [showSpecialKeysPanel, setShowSpecialKeysPanel] = useState(false);
const [activeModifier, setActiveModifier] = useState<ModifierKey | null>(null);
const { isOpen: isKeyboardOpen, height: keyboardHeight } = useVirtualKeyboard();
const { const {
sessions, sessions,
@@ -206,15 +215,17 @@ export const TerminalPage: React.FC = () => {
const handleTerminalReady = useCallback( const handleTerminalReady = useCallback(
( (
_sendData: (data: string) => void, sendData: (data: string) => void,
status: TerminalStatus, status: TerminalStatus,
_focusInput: () => void, focusInput: () => void,
changeFontSize: (delta: number) => void, changeFontSize: (delta: number) => void,
) => { ) => {
setTerminalStatuses((prev) => ({ setTerminalStatuses((prev) => ({
...prev, ...prev,
[activeSessionId ?? "default"]: status, [activeSessionId ?? "default"]: status,
})); }));
sendDataRef.current = sendData;
focusInputRef.current = focusInput;
changeFontSizeRef.current = changeFontSize; changeFontSizeRef.current = changeFontSize;
}, },
[activeSessionId], [activeSessionId],
@@ -224,6 +235,10 @@ export const TerminalPage: React.FC = () => {
changeFontSizeRef.current?.(delta); changeFontSizeRef.current?.(delta);
}, []); }, []);
const handleSendKey = useCallback((data: string) => {
sendDataRef.current?.(data);
}, []);
const handleReset = useCallback(() => { const handleReset = useCallback(() => {
if (activeSessionId && terminalRefs.current[activeSessionId]) { if (activeSessionId && terminalRefs.current[activeSessionId]) {
terminalRefs.current[activeSessionId].current?.reset(); terminalRefs.current[activeSessionId].current?.reset();
@@ -318,6 +333,7 @@ export const TerminalPage: React.FC = () => {
{/* Terminal content — always fills full viewport */} {/* Terminal content — always fills full viewport */}
<div <div
className="terminal-page-content mobile-full" className="terminal-page-content mobile-full"
style={{ paddingBottom: isKeyboardOpen ? keyboardHeight : 0 }}
onClick={() => headerAutoHide.toggle()} onClick={() => headerAutoHide.toggle()}
> >
{error && <div className="terminal-error-banner">{error}</div>} {error && <div className="terminal-error-banner">{error}</div>}
@@ -332,6 +348,8 @@ export const TerminalPage: React.FC = () => {
onClose={() => handleClose(session.id)} onClose={() => handleClose(session.id)}
isMobile={true} isMobile={true}
showControls={false} showControls={false}
activeModifier={activeModifier}
onModifierChange={setActiveModifier}
onTerminalReady={handleTerminalReady} onTerminalReady={handleTerminalReady}
/> />
</div> </div>
@@ -342,6 +360,24 @@ export const TerminalPage: React.FC = () => {
</div> </div>
)} )}
</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> </section>
); );
} }