11// ConsoleDrawer component - displays captured console logs from preview iframe with REPL
22
33import { useState , useRef , useEffect } from "react" ;
4+ import { useVerticalResizable } from "../../hooks/useVerticalResizable" ;
45
56export type LogLevel = "log" | "warn" | "error" | "info" | "result" | "command" ;
67
@@ -26,6 +27,9 @@ function ConsoleDrawer({ logs, onClear, isOpen, onToggle, onExecute }: ConsoleDr
2627 const inputRef = useRef < HTMLInputElement > ( null ) ;
2728 const outputRef = useRef < HTMLDivElement > ( null ) ;
2829
30+ // Vertical resizing
31+ const { height, startResizing, isResizing } = useVerticalResizable ( 192 , 48 , 600 ) ;
32+
2933 // Auto-scroll to bottom when new logs appear
3034 useEffect ( ( ) => {
3135 if ( outputRef . current ) {
@@ -129,87 +133,105 @@ function ConsoleDrawer({ logs, onClear, isOpen, onToggle, onExecute }: ConsoleDr
129133 }
130134
131135 return (
132- < div className = "flex flex-col h-48 bg-slate-900 border-t border-slate-700 transition-all duration-300" >
133- { /* Console Header */ }
134- < div className = "flex items-center justify-between px-4 py-1 bg-slate-800 border-b border-slate-700" >
135- < button
136- onClick = { onToggle }
137- className = "text-xs font-bold text-slate-300 hover:text-white"
138- >
139- ⌄ Console
140- </ button >
141- < div className = "flex items-center gap-2" >
136+ < >
137+ { /* Global overlay during resize */ }
138+ { isResizing && (
139+ < div
140+ className = "fixed inset-0 z-[9999] cursor-row-resize"
141+ style = { { background : 'transparent' } }
142+ />
143+ ) }
144+ < div
145+ className = "flex flex-col bg-slate-900 border-t border-slate-700 transition-all duration-75 relative"
146+ style = { { height : height } }
147+ >
148+ { /* Resize Handle */ }
149+ < div
150+ className = "absolute top-0 left-0 right-0 h-1 cursor-row-resize z-10 hover:bg-blue-500/50 transition-colors"
151+ onMouseDown = { startResizing }
152+ />
153+
154+ { /* Console Header */ }
155+ < div className = "flex items-center justify-between px-4 py-1 bg-slate-800 border-b border-slate-700 select-none" >
142156 < button
143- onClick = { onClear }
144- className = "text-[10px] uppercase font-bold text-slate-500 hover:text-slate-300 px-2 py-1 rounded hover:bg-slate-700 transition-colors "
157+ onClick = { onToggle }
158+ className = "text-xs font-bold text-slate-300 hover:text-white "
145159 >
146- Clear
160+ ⌄ Console
147161 </ button >
162+ < div className = "flex items-center gap-2" >
163+ < button
164+ onClick = { onClear }
165+ className = "text-[10px] uppercase font-bold text-slate-500 hover:text-slate-300 px-2 py-1 rounded hover:bg-slate-700 transition-colors"
166+ >
167+ Clear
168+ </ button >
169+ </ div >
148170 </ div >
149- </ div >
150171
151- { /* Console Output */ }
152- < div ref = { outputRef } className = "flex-1 overflow-auto p-2 font-mono text-xs" >
153- { logs . length === 0 ? (
154- < div className = "text-slate-600 italic px-2" > No logs yet. Type JavaScript below to execute in the preview context...</ div >
155- ) : (
156- logs . map ( ( log ) => (
157- < div
158- key = { log . id }
159- className = { `flex items-start gap-2 border-b border-slate-800/50 py-1 px-2 ${ getLevelStyles ( log . level ) } ` }
160- >
161- { log . level !== "command" && log . level !== "result" && (
162- < span className = "opacity-50 min-w-[50px]" >
163- { new Date ( log . timestamp ) . toLocaleTimeString ( [ ] , {
164- hour12 : false ,
165- hour : "2-digit" ,
166- minute : "2-digit" ,
167- second : "2-digit" ,
168- } ) }
169- </ span >
170- ) }
171- { ( log . level === "command" || log . level === "result" || log . level === "error" || log . level === "warn" ) && (
172- < span className = { `font-bold ${ log . level === "result" ? "text-blue-400" : log . level === "command" ? "text-slate-500" : "" } ` } >
173- { getLevelPrefix ( log . level ) }
174- </ span >
175- ) }
176- < div className = "flex-1 whitespace-pre-wrap break-words" >
177- { log . messages . map ( ( msg , i ) => (
178- < span key = { i } className = "mr-2" >
179- { typeof msg === "object"
180- ? JSON . stringify ( msg , null , 2 )
181- : String ( msg ) }
172+ { /* Console Output */ }
173+ < div ref = { outputRef } className = "flex-1 overflow-auto p-2 font-mono text-xs" >
174+ { logs . length === 0 ? (
175+ < div className = "text-slate-600 italic px-2" > No logs yet. Type JavaScript below to execute in the preview context...</ div >
176+ ) : (
177+ logs . map ( ( log ) => (
178+ < div
179+ key = { log . id }
180+ className = { `flex items-start gap-2 border-b border-slate-800/50 py-1 px-2 ${ getLevelStyles ( log . level ) } ` }
181+ >
182+ { log . level !== "command" && log . level !== "result" && (
183+ < span className = "opacity-50 min-w-[50px]" >
184+ { new Date ( log . timestamp ) . toLocaleTimeString ( [ ] , {
185+ hour12 : false ,
186+ hour : "2-digit" ,
187+ minute : "2-digit" ,
188+ second : "2-digit" ,
189+ } ) }
182190 </ span >
183- ) ) }
191+ ) }
192+ { ( log . level === "command" || log . level === "result" || log . level === "error" || log . level === "warn" ) && (
193+ < span className = { `font-bold ${ log . level === "result" ? "text-blue-400" : log . level === "command" ? "text-slate-500" : "" } ` } >
194+ { getLevelPrefix ( log . level ) }
195+ </ span >
196+ ) }
197+ < div className = "flex-1 whitespace-pre-wrap break-words" >
198+ { log . messages . map ( ( msg , i ) => (
199+ < span key = { i } className = "mr-2" >
200+ { typeof msg === "object"
201+ ? JSON . stringify ( msg , null , 2 )
202+ : String ( msg ) }
203+ </ span >
204+ ) ) }
205+ </ div >
184206 </ div >
185- </ div >
186- ) )
187- ) }
188- </ div >
207+ ) )
208+ ) }
209+ </ div >
189210
190- { /* REPL Input */ }
191- < div className = "flex items-center gap-2 px-2 py-1.5 bg-slate-800/50 border-t border-slate-700" >
192- < span className = "text-blue-400 font-mono text-xs font-bold" > ›</ span >
193- < input
194- ref = { inputRef }
195- type = "text"
196- value = { inputValue }
197- onChange = { ( e ) => setInputValue ( e . target . value ) }
198- onKeyDown = { handleKeyDown }
199- placeholder = "Type JavaScript and press Enter..."
200- className = "flex-1 bg-transparent text-slate-200 font-mono text-xs outline-none placeholder:text-slate-600"
201- spellCheck = { false }
202- autoComplete = "off"
203- />
204- < button
205- onClick = { handleExecute }
206- disabled = { ! inputValue . trim ( ) }
207- className = "text-[10px] uppercase font-bold text-slate-500 hover:text-slate-300 px-2 py-0.5 rounded hover:bg-slate-700 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
208- >
209- Run
210- </ button >
211+ { /* REPL Input */ }
212+ < div className = "flex items-center gap-2 px-2 py-1.5 bg-slate-800/50 border-t border-slate-700" >
213+ < span className = "text-blue-400 font-mono text-xs font-bold" > ›</ span >
214+ < input
215+ ref = { inputRef }
216+ type = "text"
217+ value = { inputValue }
218+ onChange = { ( e ) => setInputValue ( e . target . value ) }
219+ onKeyDown = { handleKeyDown }
220+ placeholder = "Type JavaScript and press Enter..."
221+ className = "flex-1 bg-transparent text-slate-200 font-mono text-xs outline-none placeholder:text-slate-600"
222+ spellCheck = { false }
223+ autoComplete = "off"
224+ />
225+ < button
226+ onClick = { handleExecute }
227+ disabled = { ! inputValue . trim ( ) }
228+ className = "text-[10px] uppercase font-bold text-slate-500 hover:text-slate-300 px-2 py-0.5 rounded hover:bg-slate-700 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
229+ >
230+ Run
231+ </ button >
232+ </ div >
211233 </ div >
212- </ div >
234+ </ >
213235 ) ;
214236}
215237
0 commit comments