-
Notifications
You must be signed in to change notification settings - Fork 223
feat(terminal): expandable mini terminal — full-screen modal view #1424
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
arnestrickmann
merged 5 commits into
generalaction:main
from
shreyaspapi:feat/expandable-mini-terminal
Mar 12, 2026
Merged
Changes from 3 commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
ffdd9c1
feat(terminal): add expandable mini terminal modal
shreyaspapi 31e2a61
fix(terminal): allow keyboard input in expanded terminal modal
shreyaspapi 17372ad
fix: address PR review — remove duplicate button, scope reattach key,…
shreyaspapi 5cf6773
fix(terminal): prevent expanded modal from overlapping titlebar
shreyaspapi f244298
Merge remote-tracking branch 'upstream/main' into feat/expandable-min…
shreyaspapi File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,130 @@ | ||
| import React, { useEffect, useRef, useCallback } from 'react'; | ||
| import { createPortal } from 'react-dom'; | ||
| import { Minimize2 } from 'lucide-react'; | ||
| import { Button } from './ui/button'; | ||
| import { cn } from '@/lib/utils'; | ||
| import { terminalSessionRegistry } from '../terminal/SessionRegistry'; | ||
|
|
||
| interface Props { | ||
| terminalId: string; | ||
| title?: string; | ||
| onClose: () => void; | ||
| variant?: 'dark' | 'light'; | ||
| } | ||
|
|
||
| /** | ||
| * Full-screen modal overlay that re-attaches an existing terminal session. | ||
| * The session is detached from the mini terminal in the sidebar and attached | ||
| * to this modal's container. On close, the session returns to the sidebar. | ||
| */ | ||
| const ExpandedTerminalModal: React.FC<Props> = ({ terminalId, title, onClose, variant }) => { | ||
| const containerRef = useRef<HTMLDivElement>(null); | ||
|
|
||
| // Attach terminal session to the modal container on mount | ||
| useEffect(() => { | ||
| const container = containerRef.current; | ||
| if (!container || !terminalId) return; | ||
|
|
||
| // Attach to this modal's container — attach() internally detaches first | ||
| const session = terminalSessionRegistry.reattach(terminalId, container); | ||
|
|
||
| // Focus the terminal after DOM settles — double rAF ensures xterm has | ||
| // opened and fitted before we try to grab focus. | ||
| requestAnimationFrame(() => { | ||
| requestAnimationFrame(() => { | ||
| session?.focus(); | ||
| }); | ||
| }); | ||
|
|
||
| return () => { | ||
| // On unmount, detach from modal — TerminalPane in sidebar will re-attach | ||
| terminalSessionRegistry.detach(terminalId); | ||
| }; | ||
| }, [terminalId]); | ||
|
|
||
| // Handle Escape key to close | ||
| const handleKeyDown = useCallback( | ||
| (e: KeyboardEvent) => { | ||
| // Only close on Escape when not inside the terminal textarea | ||
| // (xterm captures Escape for its own use only when not at a prompt) | ||
| if ( | ||
| e.key === 'Escape' && | ||
| !(e.target as HTMLElement)?.classList?.contains('xterm-helper-textarea') | ||
| ) { | ||
| e.preventDefault(); | ||
| e.stopPropagation(); | ||
| onClose(); | ||
| } | ||
| }, | ||
| [onClose] | ||
| ); | ||
|
|
||
| useEffect(() => { | ||
| window.addEventListener('keydown', handleKeyDown, true); | ||
| return () => window.removeEventListener('keydown', handleKeyDown, true); | ||
| }, [handleKeyDown]); | ||
|
|
||
| const isDark = variant === 'dark'; | ||
|
|
||
| return createPortal( | ||
| <div | ||
| className="fixed inset-0 z-[900] flex flex-col" | ||
| data-expanded-terminal="true" | ||
| aria-label="Expanded terminal" | ||
| > | ||
| {/* Backdrop */} | ||
| <div className="absolute inset-0 bg-black/60 backdrop-blur-sm" onClick={onClose} /> | ||
|
|
||
| {/* Modal content */} | ||
| <div | ||
| className={cn( | ||
| 'relative z-10 mx-4 my-4 flex flex-1 flex-col overflow-hidden rounded-lg border shadow-2xl', | ||
| isDark ? 'border-zinc-700 bg-zinc-900' : 'border-border bg-background' | ||
| )} | ||
| > | ||
| {/* Header */} | ||
| <div | ||
| className={cn( | ||
| 'flex items-center justify-between border-b px-4 py-2', | ||
| isDark ? 'border-zinc-700' : 'border-border' | ||
| )} | ||
| > | ||
| <span | ||
| className={cn( | ||
| 'text-xs font-medium', | ||
| isDark ? 'text-zinc-300' : 'text-muted-foreground' | ||
| )} | ||
| > | ||
| {title || 'Terminal'} | ||
| </span> | ||
| <Button | ||
| variant="ghost" | ||
| size="icon-sm" | ||
| onClick={onClose} | ||
| className={cn( | ||
| isDark | ||
| ? 'text-zinc-400 hover:bg-zinc-800 hover:text-zinc-200' | ||
| : 'text-muted-foreground hover:text-foreground' | ||
| )} | ||
| title="Collapse terminal (Esc)" | ||
| > | ||
| <Minimize2 className="h-3.5 w-3.5" /> | ||
| </Button> | ||
| </div> | ||
|
|
||
| {/* Terminal container — click to focus */} | ||
| <div | ||
| ref={containerRef} | ||
| className="flex-1 overflow-hidden" | ||
| onClick={() => { | ||
| const session = terminalSessionRegistry.getSession(terminalId); | ||
| session?.focus(); | ||
| }} | ||
| /> | ||
| </div> | ||
| </div>, | ||
| document.body | ||
| ); | ||
| }; | ||
|
|
||
| export default ExpandedTerminalModal; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.