@@ -28,6 +28,7 @@ import { pythonGenerator } from 'blockly/python';
28
28
import Header from './reactComponents/Header' ;
29
29
import * as Menu from './reactComponents/Menu' ;
30
30
import CodeDisplay from './reactComponents/CodeDisplay' ;
31
+ import SiderCollapseTrigger from './reactComponents/SiderCollapseTrigger' ;
31
32
import BlocklyComponent , { BlocklyComponentType } from './reactComponents/BlocklyComponent' ;
32
33
import ToolboxSettingsModal from './reactComponents/ToolboxSettings' ;
33
34
import * as Tabs from './reactComponents/Tabs' ;
@@ -81,7 +82,7 @@ const FULL_HEIGHT = '100%';
81
82
const CODE_PANEL_DEFAULT_SIZE = '25%' ;
82
83
83
84
/** Minimum size for code panel. */
84
- const CODE_PANEL_MIN_SIZE = 80 ;
85
+ const CODE_PANEL_MIN_SIZE = 100 ;
85
86
86
87
/** Background color for testing layout. */
87
88
const LAYOUT_BACKGROUND_COLOR = '#0F0' ;
@@ -167,7 +168,10 @@ const AppContent: React.FC<AppContentProps> = ({ project, setProject }): React.J
167
168
const [ shownPythonToolboxCategories , setShownPythonToolboxCategories ] = React . useState < Set < string > > ( new Set ( ) ) ;
168
169
const [ triggerPythonRegeneration , setTriggerPythonRegeneration ] = React . useState ( 0 ) ;
169
170
const [ leftCollapsed , setLeftCollapsed ] = React . useState ( false ) ;
170
- const [ rightCollapsed , setRightCollapsed ] = React . useState ( false ) ;
171
+ const [ codePanelSize , setCodePanelSize ] = React . useState < string | number > ( CODE_PANEL_DEFAULT_SIZE ) ;
172
+ const [ codePanelCollapsed , setCodePanelCollapsed ] = React . useState ( false ) ;
173
+ const [ codePanelExpandedSize , setCodePanelExpandedSize ] = React . useState < string | number > ( CODE_PANEL_DEFAULT_SIZE ) ;
174
+ const [ codePanelAnimating , setCodePanelAnimating ] = React . useState ( false ) ;
171
175
const [ theme , setTheme ] = React . useState ( 'dark' ) ;
172
176
const [ languageInitialized , setLanguageInitialized ] = React . useState ( false ) ;
173
177
const [ themeInitialized , setThemeInitialized ] = React . useState ( false ) ;
@@ -379,6 +383,30 @@ const AppContent: React.FC<AppContentProps> = ({ project, setProject }): React.J
379
383
setToolboxSettingsModalIsOpen ( false ) ;
380
384
} ;
381
385
386
+ /** Toggles the code panel between collapsed and expanded states. */
387
+ const toggleCodePanelCollapse = ( ) : void => {
388
+ setCodePanelAnimating ( true ) ;
389
+
390
+ if ( codePanelCollapsed ) {
391
+ // Expand to previous size
392
+ setCodePanelSize ( codePanelExpandedSize ) ;
393
+ setCodePanelCollapsed ( false ) ;
394
+ } else {
395
+ // Collapse to minimum size - convert current size to pixels for storage
396
+ const currentSizePx = typeof codePanelSize === 'string'
397
+ ? ( parseFloat ( codePanelSize ) / 100 ) * window . innerWidth
398
+ : codePanelSize ;
399
+ setCodePanelExpandedSize ( currentSizePx ) ;
400
+ setCodePanelSize ( CODE_PANEL_MIN_SIZE ) ;
401
+ setCodePanelCollapsed ( true ) ;
402
+ }
403
+
404
+ // Reset animation flag after transition completes
405
+ setTimeout ( ( ) => {
406
+ setCodePanelAnimating ( false ) ;
407
+ } , 200 ) ;
408
+ } ;
409
+
382
410
/** Handles toolbox settings modal OK with updated categories. */
383
411
const handleToolboxSettingsConfirm = ( updatedShownCategories : Set < string > ) : void => {
384
412
setToolboxSettingsModalIsOpen ( false ) ;
@@ -733,6 +761,8 @@ const AppContent: React.FC<AppContentProps> = ({ project, setProject }): React.J
733
761
collapsible
734
762
collapsed = { leftCollapsed }
735
763
onCollapse = { ( collapsed : boolean ) => setLeftCollapsed ( collapsed ) }
764
+ trigger = { null }
765
+ style = { { position : 'relative' } }
736
766
>
737
767
< Menu . Component
738
768
storage = { storage }
@@ -744,6 +774,10 @@ const AppContent: React.FC<AppContentProps> = ({ project, setProject }): React.J
744
774
theme = { theme }
745
775
setTheme = { setTheme }
746
776
/>
777
+ < SiderCollapseTrigger
778
+ collapsed = { leftCollapsed }
779
+ onToggle = { ( ) => setLeftCollapsed ( ! leftCollapsed ) }
780
+ />
747
781
</ Sider >
748
782
< Antd . Layout >
749
783
< Tabs . Component
@@ -757,8 +791,8 @@ const AppContent: React.FC<AppContentProps> = ({ project, setProject }): React.J
757
791
setProject = { setProject }
758
792
storage = { storage }
759
793
/>
760
- < Antd . Layout >
761
- < Content >
794
+ < div style = { { display : 'flex' , height : FULL_HEIGHT } } >
795
+ < Content style = { { flex : 1 , height : '100%' } } >
762
796
{ modulePaths . current . map ( ( modulePath ) => (
763
797
< BlocklyComponent
764
798
key = { modulePath }
@@ -769,22 +803,69 @@ const AppContent: React.FC<AppContentProps> = ({ project, setProject }): React.J
769
803
/>
770
804
) ) }
771
805
</ Content >
772
- < Sider
773
- collapsible
774
- reverseArrow = { true }
775
- collapsed = { rightCollapsed }
776
- collapsedWidth = { CODE_PANEL_MIN_SIZE }
777
- width = { CODE_PANEL_DEFAULT_SIZE }
778
- onCollapse = { ( collapsed : boolean ) => setRightCollapsed ( collapsed ) }
806
+ < div
807
+ style = { {
808
+ width : typeof codePanelSize === 'string' ? codePanelSize : `${ codePanelSize } px` ,
809
+ minWidth : CODE_PANEL_MIN_SIZE ,
810
+ height : '100%' ,
811
+ borderLeft : '1px solid #d9d9d9' ,
812
+ position : 'relative' ,
813
+ transition : codePanelAnimating ? 'width 0.2s ease' : 'none'
814
+ } }
779
815
>
816
+ < div
817
+ style = { {
818
+ position : 'absolute' ,
819
+ left : 0 ,
820
+ top : 0 ,
821
+ width : '4px' ,
822
+ height : '100%' ,
823
+ cursor : 'ew-resize' ,
824
+ backgroundColor : 'transparent' ,
825
+ zIndex : 10 ,
826
+ transform : 'translateX(-2px)'
827
+ } }
828
+ onMouseDown = { ( e ) => {
829
+ e . preventDefault ( ) ;
830
+ const startX = e . clientX ;
831
+ const startWidth = codePanelSize ;
832
+
833
+ const handleMouseMove = ( e : MouseEvent ) => {
834
+ const deltaX = startX - e . clientX ;
835
+ // Convert startWidth to number if it's a percentage
836
+ const startWidthPx = typeof startWidth === 'string'
837
+ ? ( parseFloat ( startWidth ) / 100 ) * window . innerWidth
838
+ : startWidth ;
839
+ const newWidth = Math . max ( CODE_PANEL_MIN_SIZE , startWidthPx + deltaX ) ;
840
+ setCodePanelSize ( newWidth ) ;
841
+ // Update expanded size if not at minimum
842
+ if ( newWidth > CODE_PANEL_MIN_SIZE ) {
843
+ setCodePanelExpandedSize ( newWidth ) ;
844
+ setCodePanelCollapsed ( false ) ;
845
+ } else {
846
+ setCodePanelCollapsed ( true ) ;
847
+ }
848
+ } ;
849
+
850
+ const handleMouseUp = ( ) => {
851
+ document . removeEventListener ( 'mousemove' , handleMouseMove ) ;
852
+ document . removeEventListener ( 'mouseup' , handleMouseUp ) ;
853
+ } ;
854
+
855
+ document . addEventListener ( 'mousemove' , handleMouseMove ) ;
856
+ document . addEventListener ( 'mouseup' , handleMouseUp ) ;
857
+ } }
858
+ />
780
859
< CodeDisplay
781
860
generatedCode = { generatedCode }
782
861
messageApi = { messageApi }
783
862
setAlertErrorMessage = { setAlertErrorMessage }
784
863
theme = { theme }
864
+ isCollapsed = { codePanelCollapsed }
865
+ onToggleCollapse = { toggleCodePanelCollapse }
785
866
/>
786
- </ Sider >
787
- </ Antd . Layout >
867
+ </ div >
868
+ </ div >
788
869
</ Antd . Layout >
789
870
</ Antd . Layout >
790
871
</ Antd . Layout >
0 commit comments