1+ import {
2+ BracesIcon ,
3+ CodeXmlIcon ,
4+ ExpandIcon ,
5+ Minimize2Icon ,
6+ PaintbrushIcon ,
7+ XIcon ,
8+ } from 'lucide-react' ;
9+ import type { RefObject } from 'react' ;
10+ import { MonacoEditor } from '@/components/code-editor/monaco-editor' ;
11+ import { Button } from '@/components/ui/button' ;
12+ import { ResizableHandle , ResizablePanel , ResizablePanelGroup } from '@/components/ui/resizable' ;
13+ import { BuilderPreviewPanel } from './builder-preview-panel' ;
14+ import type { BuilderDeviceMode } from './builder-utils' ;
15+
16+ type FooterEditorTab = 'html' | 'css' | 'js' ;
17+
18+ type BuilderCenterWorkspaceProps = {
19+ deviceMode : BuilderDeviceMode ;
20+ iframeRef : RefObject < HTMLIFrameElement | null > ;
21+ overlayContainerRef : RefObject < HTMLDivElement | null > ;
22+ previewUrl : string | null ;
23+ previewDocument : string ;
24+ pageTitle : string ;
25+ footerEditorOpen : boolean ;
26+ footerEditorFullscreen : boolean ;
27+ footerEditorTab : FooterEditorTab ;
28+ footerEditorTitle : string ;
29+ footerEditorLanguage : 'html' | 'css' | 'js' ;
30+ footerEditorValue : string ;
31+ footerEditorIsDirty : boolean ;
32+ onPreviewLoad : ( ) => void ;
33+ onOpenFooterEditor : ( tab : FooterEditorTab ) => void ;
34+ onCloseFooterEditor : ( ) => void ;
35+ onToggleFooterEditorFullscreen : ( ) => void ;
36+ onFooterEditorValueChange : ( value : string ) => void ;
37+ onApplyFooterEditor : ( ) => void ;
38+ } ;
39+
40+ export function BuilderCenterWorkspace ( {
41+ deviceMode,
42+ iframeRef,
43+ overlayContainerRef,
44+ previewUrl,
45+ previewDocument,
46+ pageTitle,
47+ footerEditorOpen,
48+ footerEditorFullscreen,
49+ footerEditorTab,
50+ footerEditorTitle,
51+ footerEditorLanguage,
52+ footerEditorValue,
53+ footerEditorIsDirty,
54+ onPreviewLoad,
55+ onOpenFooterEditor,
56+ onCloseFooterEditor,
57+ onToggleFooterEditorFullscreen,
58+ onFooterEditorValueChange,
59+ onApplyFooterEditor,
60+ } : BuilderCenterWorkspaceProps ) {
61+ return (
62+ < div className = "flex h-full min-w-0 flex-col" >
63+ < div className = "flex min-h-0 flex-1 flex-col overflow-hidden border border-border/60 border-b-0 bg-background" >
64+ { footerEditorOpen && footerEditorFullscreen ? (
65+ < div className = "flex min-h-0 flex-1 flex-col" >
66+ < div className = "flex items-center justify-between border-b border-border/60 px-2 py-1" >
67+ < div className = "flex items-center gap-1" >
68+ < span className = "text-[10px] font-semibold uppercase tracking-wider text-muted-foreground" > Editor</ span >
69+ < span className = "rounded-full bg-muted px-1.5 py-0.5 text-[10px] font-medium text-foreground/80" >
70+ { footerEditorTitle }
71+ </ span >
72+ </ div >
73+ < div className = "flex items-center gap-1" >
74+ < Button
75+ variant = "outline"
76+ size = "sm"
77+ onClick = { onApplyFooterEditor }
78+ disabled = { ! footerEditorIsDirty }
79+ className = "h-6 px-2.5 text-[11px]"
80+ >
81+ Apply
82+ </ Button >
83+ < Button
84+ variant = "ghost"
85+ size = "icon-sm"
86+ onClick = { onToggleFooterEditorFullscreen }
87+ aria-label = "Exit fullscreen editor"
88+ >
89+ < Minimize2Icon className = "size-3.5" />
90+ </ Button >
91+ < Button
92+ variant = "ghost"
93+ size = "icon-sm"
94+ onClick = { onCloseFooterEditor }
95+ aria-label = "Close editor"
96+ >
97+ < XIcon className = "size-3.5" />
98+ </ Button >
99+ </ div >
100+ </ div >
101+ < div className = "min-h-0 flex-1" data-builder-shortcut-scope = "footer-code" >
102+ < MonacoEditor
103+ value = { footerEditorValue }
104+ onChange = { onFooterEditorValueChange }
105+ language = { footerEditorLanguage }
106+ height = "100%"
107+ />
108+ </ div >
109+ </ div >
110+ ) : (
111+ < >
112+ { footerEditorOpen ? (
113+ < ResizablePanelGroup orientation = "vertical" className = "min-h-0 flex-1" >
114+ < ResizablePanel defaultSize = "58%" minSize = "25%" >
115+ < PreviewSurface
116+ deviceMode = { deviceMode }
117+ iframeRef = { iframeRef }
118+ overlayContainerRef = { overlayContainerRef }
119+ previewUrl = { previewUrl }
120+ previewDocument = { previewDocument }
121+ pageTitle = { pageTitle }
122+ onPreviewLoad = { onPreviewLoad }
123+ />
124+ </ ResizablePanel >
125+ < ResizableHandle withHandle />
126+ < ResizablePanel defaultSize = "42%" minSize = "18%" >
127+ < div className = "flex h-full min-h-0 flex-col border-t border-border/60" >
128+ < div className = "flex items-center justify-between border-b border-border/60 px-2 py-1" >
129+ < div className = "flex items-center gap-1" >
130+ < span className = "text-[10px] font-semibold uppercase tracking-wider text-muted-foreground" > Editor</ span >
131+ < span className = "rounded-full bg-muted px-1.5 py-0.5 text-[10px] font-medium text-foreground/80" >
132+ { footerEditorTitle }
133+ </ span >
134+ </ div >
135+ < div className = "flex items-center gap-1" >
136+ < Button
137+ variant = "outline"
138+ size = "sm"
139+ onClick = { onApplyFooterEditor }
140+ disabled = { ! footerEditorIsDirty }
141+ className = "h-6 px-2.5 text-[11px]"
142+ >
143+ Apply
144+ </ Button >
145+ < Button
146+ variant = "ghost"
147+ size = "icon-sm"
148+ onClick = { onToggleFooterEditorFullscreen }
149+ aria-label = "Expand editor"
150+ >
151+ < ExpandIcon className = "size-3.5" />
152+ </ Button >
153+ < Button
154+ variant = "ghost"
155+ size = "icon-sm"
156+ onClick = { onCloseFooterEditor }
157+ aria-label = "Close editor"
158+ >
159+ < XIcon className = "size-3.5" />
160+ </ Button >
161+ </ div >
162+ </ div >
163+ < div className = "min-h-0 flex-1" data-builder-shortcut-scope = "footer-code" >
164+ < MonacoEditor
165+ value = { footerEditorValue }
166+ onChange = { onFooterEditorValueChange }
167+ language = { footerEditorLanguage }
168+ height = "100%"
169+ />
170+ </ div >
171+ </ div >
172+ </ ResizablePanel >
173+ </ ResizablePanelGroup >
174+ ) : (
175+ < PreviewSurface
176+ deviceMode = { deviceMode }
177+ iframeRef = { iframeRef }
178+ overlayContainerRef = { overlayContainerRef }
179+ previewUrl = { previewUrl }
180+ previewDocument = { previewDocument }
181+ pageTitle = { pageTitle }
182+ onPreviewLoad = { onPreviewLoad }
183+ />
184+ ) }
185+ </ >
186+ ) }
187+
188+ < div className = "flex items-center justify-between border-t border-border/60 bg-background px-2 py-1" >
189+ < div className = "flex items-center gap-1" >
190+ < Button
191+ variant = { footerEditorOpen && footerEditorTab === 'html' ? 'secondary' : 'ghost' }
192+ size = "sm"
193+ onClick = { ( ) => onOpenFooterEditor ( 'html' ) }
194+ className = "h-6 gap-1 px-2 text-[11px]"
195+ >
196+ < CodeXmlIcon className = "size-3" />
197+ HTML
198+ </ Button >
199+ < Button
200+ variant = { footerEditorOpen && footerEditorTab === 'css' ? 'secondary' : 'ghost' }
201+ size = "sm"
202+ onClick = { ( ) => onOpenFooterEditor ( 'css' ) }
203+ className = "h-6 gap-1 px-2 text-[11px]"
204+ >
205+ < PaintbrushIcon className = "size-3" />
206+ CSS
207+ </ Button >
208+ < Button
209+ variant = { footerEditorOpen && footerEditorTab === 'js' ? 'secondary' : 'ghost' }
210+ size = "sm"
211+ onClick = { ( ) => onOpenFooterEditor ( 'js' ) }
212+ className = "h-6 gap-1 px-2 text-[11px]"
213+ >
214+ < BracesIcon className = "size-3" />
215+ JS
216+ </ Button >
217+ </ div >
218+ < span className = "text-[10px] text-muted-foreground" >
219+ { footerEditorOpen ? `${ footerEditorTitle } editor open` : 'Open code editor' }
220+ </ span >
221+ </ div >
222+ </ div >
223+ </ div >
224+ ) ;
225+ }
226+
227+ function PreviewSurface ( {
228+ deviceMode,
229+ iframeRef,
230+ overlayContainerRef,
231+ previewUrl,
232+ previewDocument,
233+ pageTitle,
234+ onPreviewLoad,
235+ } : {
236+ deviceMode : BuilderDeviceMode ;
237+ iframeRef : RefObject < HTMLIFrameElement | null > ;
238+ overlayContainerRef : RefObject < HTMLDivElement | null > ;
239+ previewUrl : string | null ;
240+ previewDocument : string ;
241+ pageTitle : string ;
242+ onPreviewLoad : ( ) => void ;
243+ } ) {
244+ return (
245+ < div className = "relative h-full min-h-0 overflow-hidden bg-[#f0f2f5] p-1.5 sm:p-2 lg:bg-transparent lg:p-0" >
246+ < BuilderPreviewPanel
247+ deviceMode = { deviceMode }
248+ iframeRef = { iframeRef }
249+ onLoad = { onPreviewLoad }
250+ previewUrl = { previewUrl }
251+ previewHtml = { previewDocument || undefined }
252+ title = { `Builder preview for ${ pageTitle } ` }
253+ />
254+ < div ref = { overlayContainerRef } className = "pointer-events-none absolute inset-0 z-50" />
255+ </ div >
256+ ) ;
257+ }
0 commit comments