@@ -15,6 +15,10 @@ const statusNode = document.getElementById('status')
1515const appGrid = document . querySelector ( '.app-grid' )
1616const appGridLayoutButtons = document . querySelectorAll ( '[data-app-grid-layout]' )
1717const appThemeButtons = document . querySelectorAll ( '[data-app-theme]' )
18+ const panelCollapseButtons = document . querySelectorAll ( '[data-panel-collapse]' )
19+ const componentPanel = document . getElementById ( 'component-panel' )
20+ const stylesPanel = document . getElementById ( 'styles-panel' )
21+ const previewPanel = document . getElementById ( 'preview-panel' )
1822const renderMode = document . getElementById ( 'render-mode' )
1923const autoRenderToggle = document . getElementById ( 'auto-render' )
2024const typecheckButton = document . getElementById ( 'typecheck-button' )
@@ -71,6 +75,185 @@ const layoutTheme = createLayoutThemeController({
7175const { applyAppGridLayout, applyTheme, getInitialAppGridLayout, getInitialTheme } =
7276 layoutTheme
7377
78+ const panelMap = {
79+ component : componentPanel ,
80+ styles : stylesPanel ,
81+ preview : previewPanel ,
82+ }
83+
84+ const getCurrentLayout = ( ) => {
85+ if ( appGrid . classList . contains ( 'app-grid--preview-right' ) ) {
86+ return 'preview-right'
87+ }
88+
89+ if ( appGrid . classList . contains ( 'app-grid--preview-left' ) ) {
90+ return 'preview-left'
91+ }
92+
93+ return 'default'
94+ }
95+
96+ const isCompactViewport = ( ) => window . matchMedia ( '(max-width: 900px)' ) . matches
97+
98+ const getPanelCollapseAxis = panelName => {
99+ if ( isCompactViewport ( ) ) {
100+ return 'vertical'
101+ }
102+
103+ const layout = getCurrentLayout ( )
104+
105+ if ( panelName === 'preview' ) {
106+ return layout === 'default' ? 'vertical' : 'horizontal'
107+ }
108+
109+ if ( panelName === 'component' || panelName === 'styles' ) {
110+ return layout === 'default' ? 'horizontal' : 'vertical'
111+ }
112+
113+ return 'vertical'
114+ }
115+
116+ const getPanelCollapseDirection = panelName => {
117+ const axis = getPanelCollapseAxis ( panelName )
118+ if ( axis !== 'horizontal' ) {
119+ return 'none'
120+ }
121+
122+ const layout = getCurrentLayout ( )
123+
124+ if ( panelName === 'preview' ) {
125+ return layout === 'preview-left' ? 'left' : 'right'
126+ }
127+
128+ if ( panelName === 'component' ) {
129+ return 'left'
130+ }
131+
132+ if ( panelName === 'styles' ) {
133+ return 'right'
134+ }
135+
136+ return 'right'
137+ }
138+
139+ const panelCollapseState = {
140+ component : false ,
141+ styles : false ,
142+ preview : false ,
143+ }
144+
145+ const normalizePanelCollapseState = ( ) => {
146+ const collapsedPanels = Object . entries ( panelCollapseState )
147+ . filter ( ( [ , isCollapsed ] ) => isCollapsed )
148+ . map ( ( [ panelName ] ) => panelName )
149+
150+ if ( collapsedPanels . length === Object . keys ( panelCollapseState ) . length ) {
151+ panelCollapseState . preview = false
152+ }
153+ }
154+
155+ const syncPanelCollapseButtons = ( ) => {
156+ const collapsedCount = Object . values ( panelCollapseState ) . filter ( Boolean ) . length
157+
158+ for ( const button of panelCollapseButtons ) {
159+ const panelName = button . dataset . panelCollapse
160+ if ( ! panelName || ! ( panelName in panelMap ) ) {
161+ continue
162+ }
163+
164+ const axis = getPanelCollapseAxis ( panelName )
165+ const direction = getPanelCollapseDirection ( panelName )
166+ const isCollapsed = panelCollapseState [ panelName ] === true
167+ const panelTitle = `${ panelName . charAt ( 0 ) . toUpperCase ( ) } ${ panelName . slice ( 1 ) } `
168+ const canCollapse = isCollapsed || collapsedCount < 2
169+
170+ button . dataset . collapseAxis = axis
171+ button . dataset . collapseDirection = direction
172+ button . dataset . collapsed = isCollapsed ? 'true' : 'false'
173+ button . setAttribute ( 'aria-expanded' , isCollapsed ? 'false' : 'true' )
174+ button . disabled = ! canCollapse
175+ button . setAttribute ( 'aria-disabled' , canCollapse ? 'false' : 'true' )
176+ button . setAttribute (
177+ 'aria-label' ,
178+ `${ isCollapsed ? 'Expand' : 'Collapse' } ${ panelTitle . toLowerCase ( ) } panel` ,
179+ )
180+ button . setAttribute (
181+ 'title' ,
182+ canCollapse
183+ ? `${ isCollapsed ? 'Expand' : 'Collapse' } ${ panelTitle . toLowerCase ( ) } panel`
184+ : 'At least one panel must remain expanded.' ,
185+ )
186+ }
187+ }
188+
189+ const applyPanelCollapseState = ( ) => {
190+ normalizePanelCollapseState ( )
191+
192+ const previewAxis = getPanelCollapseAxis ( 'preview' )
193+ const componentAxis = getPanelCollapseAxis ( 'component' )
194+ const stylesAxis = getPanelCollapseAxis ( 'styles' )
195+
196+ if ( componentPanel ) {
197+ const isCollapsed = panelCollapseState . component
198+ componentPanel . classList . toggle (
199+ 'panel--collapsed-vertical' ,
200+ isCollapsed && componentAxis === 'vertical' ,
201+ )
202+ componentPanel . classList . toggle (
203+ 'panel--collapsed-horizontal' ,
204+ isCollapsed && componentAxis === 'horizontal' ,
205+ )
206+ }
207+
208+ if ( stylesPanel ) {
209+ const isCollapsed = panelCollapseState . styles
210+ stylesPanel . classList . toggle (
211+ 'panel--collapsed-vertical' ,
212+ isCollapsed && stylesAxis === 'vertical' ,
213+ )
214+ stylesPanel . classList . toggle (
215+ 'panel--collapsed-horizontal' ,
216+ isCollapsed && stylesAxis === 'horizontal' ,
217+ )
218+ }
219+
220+ if ( previewPanel ) {
221+ const isCollapsed = panelCollapseState . preview
222+ previewPanel . classList . toggle (
223+ 'panel--collapsed-vertical' ,
224+ isCollapsed && previewAxis === 'vertical' ,
225+ )
226+ previewPanel . classList . toggle (
227+ 'panel--collapsed-horizontal' ,
228+ isCollapsed && previewAxis === 'horizontal' ,
229+ )
230+ }
231+
232+ appGrid . classList . toggle (
233+ 'app-grid--preview-collapsed-horizontal' ,
234+ panelCollapseState . preview && previewAxis === 'horizontal' ,
235+ )
236+ appGrid . classList . toggle (
237+ 'app-grid--component-collapsed-horizontal' ,
238+ panelCollapseState . component && componentAxis === 'horizontal' ,
239+ )
240+ appGrid . classList . toggle (
241+ 'app-grid--styles-collapsed-horizontal' ,
242+ panelCollapseState . styles && stylesAxis === 'horizontal' ,
243+ )
244+
245+ syncPanelCollapseButtons ( )
246+ }
247+
248+ const togglePanelCollapse = panelName => {
249+ if ( ! ( panelName in panelCollapseState ) ) {
250+ return
251+ }
252+
253+ panelCollapseState [ panelName ] = ! panelCollapseState [ panelName ]
254+ applyPanelCollapseState ( )
255+ }
256+
74257const diagnosticsUi = createDiagnosticsUiController ( {
75258 diagnosticsToggle,
76259 diagnosticsDrawer,
@@ -451,6 +634,7 @@ for (const button of appGridLayoutButtons) {
451634 return
452635 }
453636 applyAppGridLayout ( nextLayout )
637+ applyPanelCollapseState ( )
454638 } )
455639}
456640
@@ -464,8 +648,24 @@ for (const button of appThemeButtons) {
464648 } )
465649}
466650
651+ for ( const button of panelCollapseButtons ) {
652+ button . addEventListener ( 'click' , ( ) => {
653+ const panelName = button . dataset . panelCollapse
654+ if ( ! panelName ) {
655+ return
656+ }
657+
658+ togglePanelCollapse ( panelName )
659+ } )
660+ }
661+
662+ window . addEventListener ( 'resize' , ( ) => {
663+ applyPanelCollapseState ( )
664+ } )
665+
467666applyAppGridLayout ( getInitialAppGridLayout ( ) , { persist : false } )
468667applyTheme ( getInitialTheme ( ) , { persist : false } )
668+ applyPanelCollapseState ( )
469669
470670updateRenderButtonVisibility ( )
471671renderDiagnosticsScope ( 'component' )
0 commit comments