44 * Right 50%: Memory Visualization
55 */
66
7- import { useEffect , useState , useMemo } from 'react' ;
7+ import { useEffect , useState , useMemo , useCallback } from 'react' ;
88import { useTranslation } from 'react-i18next' ;
99import { useIsMobile } from '@/hooks' ;
10- import { Play , Layers } from 'lucide-react' ;
10+ import { Play , Layers , ChevronDown , ChevronRight as ChevronRightIcon , Terminal } from 'lucide-react' ;
1111import { LanguageTabs } from './components/LanguageTabs' ;
1212import { CodeMirrorEditor } from '@/features/visualizers/shared/components/CodeMirrorEditor' ;
1313import { StepControls } from './components/StepControls' ;
@@ -29,7 +29,7 @@ const MAX_EDITOR_HEIGHT = 500;
2929
3030export function PlaygroundPage ( ) {
3131 const { t } = useTranslation ( ) ;
32- const { steps, currentStepIndex, error, language, setCode } = usePlaygroundStore ( ) ;
32+ const { steps, currentStepIndex, error, language, setCode, stdins , setStdin } = usePlaygroundStore ( ) ;
3333 const code = useCurrentCode ( ) ;
3434 const setPageTitle = useStore ( ( s ) => s . setPageTitle ) ;
3535 const currentTheme = useThemeStore ( ( s ) => s . theme ) ;
@@ -40,6 +40,13 @@ export function PlaygroundPage() {
4040 const currentStep = steps [ currentStepIndex ] ;
4141 const hasSteps = steps . length > 0 ;
4242
43+ // stdin 접이식 상태
44+ const [ stdinOpen , setStdinOpen ] = useState ( false ) ;
45+ const currentStdin = stdins [ language ] || '' ;
46+ const handleStdinChange = useCallback ( ( e : React . ChangeEvent < HTMLTextAreaElement > ) => {
47+ setStdin ( e . target . value ) ;
48+ } , [ setStdin ] ) ;
49+
4350 // Flow/Memory 탭 상태
4451 const [ activeTab , setActiveTab ] = useState < 'flow' | 'memory' > ( 'flow' ) ;
4552
@@ -88,21 +95,16 @@ export function PlaygroundPage() {
8895 < div style = { { backgroundColor : colors . pageBg , minHeight : '100vh' , display : 'flex' , flexDirection : 'column' , padding : '16px' } } >
8996 { /* Code Section */ }
9097 < div style = { { backgroundColor : colors . panelBg , flexShrink : 0 } } >
91- { /* Header */ }
92- < div
93- style = { {
94- height : '40px' ,
95- padding : '0 8px' ,
96- display : 'flex' ,
97- alignItems : 'center' ,
98- justifyContent : 'space-between' ,
99- borderBottom : `1px solid ${ colors . border } ` ,
100- backgroundColor : colors . headerBg ,
101- gap : '4px' ,
102- } }
103- >
98+ { /* Header: 모바일 2줄 — 탭 꽉 채움 + 컨트롤 */ }
99+ < div style = { {
100+ backgroundColor : colors . headerBg ,
101+ borderBottom : `1px solid ${ colors . border } ` ,
102+ padding : '8px 10px' ,
103+ display : 'flex' ,
104+ flexDirection : 'column' ,
105+ gap : '8px' ,
106+ } } >
104107 < LanguageTabs isMobile = { true } />
105- { /* Run + Reset + Navigation 버튼 (하단에도 추가로 표시) */ }
106108 < StepControls isMobile = { true } showRun = { true } showReset = { true } showNavigation = { true } />
107109 </ div >
108110
@@ -125,6 +127,55 @@ export function PlaygroundPage() {
125127 />
126128 ) }
127129 </ div >
130+
131+ { /* stdin 입력 (접이식) */ }
132+ { language === 'c' && (
133+ < div style = { { borderTop : `1px solid ${ colors . border } ` } } >
134+ < button
135+ onClick = { ( ) => setStdinOpen ( ! stdinOpen ) }
136+ style = { {
137+ width : '100%' ,
138+ display : 'flex' ,
139+ alignItems : 'center' ,
140+ gap : '4px' ,
141+ padding : '6px 8px' ,
142+ fontSize : '10px' ,
143+ fontWeight : 600 ,
144+ color : currentStdin ? colors . accent : colors . textMuted ,
145+ background : 'transparent' ,
146+ border : 'none' ,
147+ cursor : 'pointer' ,
148+ } }
149+ >
150+ { stdinOpen ? < ChevronDown size = { 10 } /> : < ChevronRightIcon size = { 10 } /> }
151+ < Terminal size = { 10 } />
152+ Input (stdin)
153+ { currentStdin && < span style = { { fontSize : '9px' , opacity : 0.7 } } > *</ span > }
154+ </ button >
155+ { stdinOpen && (
156+ < textarea
157+ value = { currentStdin }
158+ onChange = { handleStdinChange }
159+ placeholder = "Enter input values, one per line..."
160+ rows = { 3 }
161+ style = { {
162+ width : '100%' ,
163+ padding : '6px 8px' ,
164+ fontSize : '11px' ,
165+ fontFamily : 'monospace' ,
166+ backgroundColor : colors . panelBg ,
167+ color : colors . textMuted ,
168+ border : 'none' ,
169+ borderTop : `1px solid ${ colors . border } ` ,
170+ outline : 'none' ,
171+ resize : 'vertical' ,
172+ minHeight : '48px' ,
173+ maxHeight : '120px' ,
174+ } }
175+ />
176+ ) }
177+ </ div >
178+ ) }
128179 </ div >
129180
130181 { /* Visualization Section - Flow Only */ }
@@ -314,22 +365,19 @@ export function PlaygroundPage() {
314365 minHeight : '133px' ,
315366 } }
316367 >
317- { /* Code Header */ }
318- < div
319- style = { {
320- height : '48px' ,
321- padding : '0 16px' ,
322- display : 'flex' ,
323- alignItems : 'center' ,
324- justifyContent : 'space-between' ,
325- borderBottom : `1px solid ${ colors . border } ` ,
326- backgroundColor : colors . headerBg ,
327- flexShrink : 0 ,
328- gap : '8px' ,
329- } }
330- >
368+ { /* Code Header: flex-wrap — 넓으면 1줄, 좁으면 자연스럽게 줄바꿈 */ }
369+ < div style = { {
370+ backgroundColor : colors . headerBg ,
371+ borderBottom : `1px solid ${ colors . border } ` ,
372+ flexShrink : 0 ,
373+ padding : '10px 16px' ,
374+ display : 'flex' ,
375+ flexWrap : 'wrap' ,
376+ alignItems : 'center' ,
377+ gap : '10px' ,
378+ } } >
331379 < LanguageTabs />
332- { /* Run + Reset + Navigation 버튼 (헤더) */ }
380+ < div style = { { width : '1px' , height : '22px' , background : colors . border , flexShrink : 0 } } />
333381 < StepControls showRun = { true } showReset = { true } showNavigation = { true } />
334382 </ div >
335383
@@ -353,6 +401,55 @@ export function PlaygroundPage() {
353401 ) }
354402 </ div >
355403
404+ { /* stdin 입력 (접이식) */ }
405+ { language === 'c' && (
406+ < div style = { { borderTop : `1px solid ${ colors . border } ` , flexShrink : 0 } } >
407+ < button
408+ onClick = { ( ) => setStdinOpen ( ! stdinOpen ) }
409+ style = { {
410+ width : '100%' ,
411+ display : 'flex' ,
412+ alignItems : 'center' ,
413+ gap : '6px' ,
414+ padding : '8px 16px' ,
415+ fontSize : '12px' ,
416+ fontWeight : 600 ,
417+ color : currentStdin ? colors . accent : colors . textMuted ,
418+ background : 'transparent' ,
419+ border : 'none' ,
420+ cursor : 'pointer' ,
421+ transition : 'color 0.15s' ,
422+ } }
423+ >
424+ { stdinOpen ? < ChevronDown size = { 12 } /> : < ChevronRightIcon size = { 12 } /> }
425+ < Terminal size = { 12 } />
426+ Input (stdin)
427+ { currentStdin && < span style = { { fontSize : '10px' , opacity : 0.7 } } > *</ span > }
428+ </ button >
429+ { stdinOpen && (
430+ < textarea
431+ value = { currentStdin }
432+ onChange = { handleStdinChange }
433+ placeholder = "Enter input values, one per line..."
434+ rows = { 3 }
435+ style = { {
436+ width : '100%' ,
437+ padding : '8px 16px' ,
438+ fontSize : '12px' ,
439+ fontFamily : 'monospace' ,
440+ backgroundColor : colors . panelBg ,
441+ color : colors . textMuted ,
442+ border : 'none' ,
443+ borderTop : `1px solid ${ colors . border } ` ,
444+ outline : 'none' ,
445+ resize : 'vertical' ,
446+ minHeight : '60px' ,
447+ maxHeight : '150px' ,
448+ } }
449+ />
450+ ) }
451+ </ div >
452+ ) }
356453 </ div >
357454
358455 { /* ===== Right Panel: Flow + Memory Tabs - 컨텐츠에 따라 늘어남 ===== */ }
0 commit comments