the universal sandbox runtime for agents and humans.
pocketenv.io
sandbox
openclaw
agent
claude-code
vercel-sandbox
deno-sandbox
cloudflare-sandbox
atproto
sprites
daytona
1import { useEffect, useState } from "react";
2import { createPortal } from "react-dom";
3import Terminal from "../../../../components/terminal";
4
5export type TerminalModalProps = {
6 isOpen: boolean;
7 onClose: () => void;
8 sandboxId: string;
9 worker: string;
10 title?: string;
11 isCloudflare?: boolean;
12 isTty?: boolean;
13 pty?: boolean;
14};
15
16function TerminalModal({
17 isOpen,
18 onClose,
19 title,
20 worker,
21 sandboxId,
22 isCloudflare,
23 isTty,
24 pty,
25}: TerminalModalProps) {
26 const [isFullscreen, setIsFullscreen] = useState(false);
27
28 useEffect(() => {
29 const handleEscapeKey = (event: KeyboardEvent) => {
30 if (event.key === "Escape" && isOpen) {
31 if (isFullscreen) {
32 setIsFullscreen(false);
33 (document.activeElement as HTMLElement)?.blur();
34 } else {
35 onClose();
36 }
37 }
38 };
39
40 document.addEventListener("keydown", handleEscapeKey);
41 return () => {
42 document.removeEventListener("keydown", handleEscapeKey);
43 };
44 }, [isOpen, isFullscreen, onClose]);
45
46 // Reset fullscreen when modal closes
47 useEffect(() => {
48 if (!isOpen) {
49 // eslint-disable-next-line react-hooks/set-state-in-effect
50 setIsFullscreen(false);
51 }
52 }, [isOpen]);
53
54 const handleBackdropClick = (e: React.MouseEvent<HTMLDivElement>) => {
55 e.stopPropagation();
56 if (e.target === e.currentTarget) {
57 onClose();
58 }
59 };
60
61 const handleContentClick = (e: React.MouseEvent<HTMLDivElement>) => {
62 e.stopPropagation();
63 };
64
65 const handleCloseButton = (e: React.MouseEvent<HTMLButtonElement>) => {
66 e.stopPropagation();
67 onClose();
68 };
69
70 const handleFullscreenToggle = (e: React.MouseEvent<HTMLButtonElement>) => {
71 e.stopPropagation();
72 setIsFullscreen((prev) => !prev);
73 setTimeout(() => window.dispatchEvent(new Event("resize")), 50);
74 };
75
76 if (!isOpen) return null;
77
78 return createPortal(
79 <>
80 <div
81 className="overlay modal modal-middle overlay-open:opacity-100 overlay-open:duration-300 open opened"
82 role="dialog"
83 style={{ outline: "none", zIndex: 80 }}
84 onClick={handleBackdropClick}
85 onMouseDown={handleBackdropClick}
86 >
87 <div
88 className={`overlay-animation-target modal-dialog overlay-open:duration-300 transition-all ease-out ${
89 isFullscreen
90 ? "fixed inset-0 m-0! max-w-none! w-screen! h-screen! rounded-none!"
91 : "modal-dialog-xl overlay-open:mt-4 mt-12"
92 }`}
93 onClick={handleContentClick}
94 onMouseDown={handleContentClick}
95 style={isFullscreen ? { maxHeight: "100vh" } : undefined}
96 >
97 <div
98 className={`modal-content ${isFullscreen ? "rounded-none! h-full!" : ""}`}
99 >
100 <div className="modal-header">
101 <div className="flex-1 text-center">{title}</div>
102 <button
103 type="button"
104 className="btn btn-text btn-circle btn-sm absolute end-10 top-3"
105 aria-label={isFullscreen ? "Exit fullscreen" : "Fullscreen"}
106 onClick={handleFullscreenToggle}
107 onMouseDown={(e) => e.stopPropagation()}
108 >
109 <span
110 className={
111 isFullscreen
112 ? "icon-[mingcute--fullscreen-exit-2-line] size-4"
113 : "icon-[mingcute--fullscreen-2-line] size-4"
114 }
115 ></span>
116 </button>
117 <button
118 type="button"
119 className="btn btn-text btn-circle btn-sm absolute end-3 top-3"
120 aria-label="Close"
121 onClick={handleCloseButton}
122 onMouseDown={(e) => e.stopPropagation()}
123 >
124 <span className="icon-[tabler--x] size-4"></span>
125 </button>
126 </div>
127 <div
128 className="modal-body p-0 pl-2 overflow-y-hidden"
129 style={
130 isFullscreen
131 ? { height: "100vh", paddingBottom: 20 }
132 : { height: "60vh" }
133 }
134 >
135 <Terminal
136 sandboxId={sandboxId}
137 onClose={onClose}
138 isCloudflare={isCloudflare}
139 worker={worker}
140 isTty={isTty}
141 pty={pty}
142 />
143 </div>
144 </div>
145 </div>
146 </div>
147
148 <div
149 data-overlay-backdrop-template=""
150 style={{ zIndex: 79 }}
151 className="overlay-backdrop transition duration-300 fixed inset-0 bg-base-300/60 overflow-y-auto opacity-75"
152 onClick={handleBackdropClick}
153 onMouseDown={(e) => e.stopPropagation()}
154 ></div>
155 </>,
156 document.body,
157 );
158}
159
160export default TerminalModal;