@@ -47,16 +47,16 @@ export default function LessonPage({
4747 const reviewRef = useRef < HTMLDivElement > ( null ) ;
4848 const contentRef = useRef < HTMLDivElement > ( null ) ;
4949 const topBarRef = useRef < HTMLDivElement > ( null ) ;
50+ const leftColRef = useRef < HTMLDivElement > ( null ) ;
5051
5152 function scrollToRef ( ref : RefObject < HTMLDivElement | null > ) {
52- if ( ! ref . current ) return ;
53- const headerHeight = topBarRef . current ?. offsetHeight ?? 64 ;
53+ if ( ! ref . current || ! leftColRef . current ) return ;
54+ const container = leftColRef . current ;
5455 const top =
55- ref . current . getBoundingClientRect ( ) . top +
56- window . scrollY -
57- headerHeight -
58- 16 ;
59- window . scrollTo ( { top, behavior : 'smooth' } ) ;
56+ container . scrollTop +
57+ ref . current . getBoundingClientRect ( ) . top -
58+ container . getBoundingClientRect ( ) . top ;
59+ container . scrollTo ( { top, behavior : 'smooth' } ) ;
6060 }
6161
6262 function handleTabChange ( next : LessonTabValue ) {
@@ -200,93 +200,101 @@ export default function LessonPage({
200200 </ div >
201201
202202 < div className = "mx-auto w-full max-w-[1236px] px-300" >
203- < div className = "grid grid-cols-content-sidebar-360 items-start gap-250 pt-500 " >
203+ < div className = "flex items-start gap-250" >
204204 { /* LEFT */ }
205- < div className = "min-w-0" >
206- < Link
207- href = { `/class/${ lesson ?. courseSlug ?? slug } /home` }
208- className = "inline-flex items-center gap-125 rounded-full border border-gray-200 bg-background-default px-200 py-100"
209- >
210- < ArrowLeft className = "h-250 w-250 text-gray-800" />
211- < span className = "font-designer-14m text-gray-1000" >
212- 학습 여정 맵 돌아가기
213- </ span >
214- </ Link >
215-
216- < div className = "mt-300 flex items-center justify-between" >
217- < div className = "flex items-center gap-200" >
218- < span className = "rounded-100 bg-rose-200 px-125 py-25 font-designer-14m text-rose-400" >
219- Lesson { String ( lessonId ) . padStart ( 2 , '0' ) }
205+ < div className = "min-w-0 flex-1 flex flex-col h-[calc(100vh-var(--spacing-800))]" >
206+ { /* Fixed header: back link, title, description, tabs — never scrolls */ }
207+ < div className = "shrink-0 pt-500" >
208+ < Link
209+ href = { `/class/${ lesson ?. courseSlug ?? slug } /home` }
210+ className = "inline-flex items-center gap-125 rounded-full border border-gray-200 bg-background-default px-200 py-100"
211+ >
212+ < ArrowLeft className = "h-250 w-250 text-gray-800" />
213+ < span className = "font-designer-14m text-gray-1000" >
214+ 학습 여정 맵 돌아가기
220215 </ span >
221- < h1 className = "font-designer-32b text-gray-800" >
222- { lesson ?. title ?? 'AI 처음 만나는 날' }
223- </ h1 >
216+ </ Link >
217+
218+ < div className = "mt-300 flex items-center justify-between" >
219+ < div className = "flex items-center gap-200" >
220+ < span className = "rounded-100 bg-rose-200 px-125 py-25 font-designer-14m text-rose-400" >
221+ Lesson { String ( lessonId ) . padStart ( 2 , '0' ) }
222+ </ span >
223+ < h1 className = "font-designer-32b text-gray-800" >
224+ { lesson ?. title ?? 'AI 처음 만나는 날' }
225+ </ h1 >
226+ </ div >
227+ < p className = "font-designer-16m text-gray-500" >
228+ { lesson ?. estimatedMinutes
229+ ? `약 ${ lesson . estimatedMinutes } 분 소요`
230+ : '' }
231+ </ p >
224232 </ div >
225- < p className = "font-designer-16m text-gray-500" >
226- { lesson ?. estimatedMinutes
227- ? `약 ${ lesson . estimatedMinutes } 분 소요`
228- : '' }
229- </ p >
230- </ div >
231233
232- { lesson ?. description ? (
233- < p className = "mt-150 whitespace-pre-line font-designer-16r text-gray-700" >
234- { lesson . description }
235- </ p >
236- ) : null }
234+ { lesson ?. description ? (
235+ < p className = "mt-150 whitespace-pre-line font-designer-16r text-gray-700" >
236+ { lesson . description }
237+ </ p >
238+ ) : null }
237239
238- < div className = "sticky top-800 z-20 mt-300 bg-gray-100" >
239- < LessonTabs value = { tab } onChange = { handleTabChange } />
240+ < div className = "mt-300 bg-gray-100" >
241+ < LessonTabs value = { tab } onChange = { handleTabChange } />
242+ </ div >
240243 </ div >
241244
242- < div
243- ref = { contentRef }
244- className = "mt-300 min-h-[964px] rounded-150 bg-background-default p-500"
245- >
246- { lesson ?. contentMarkdown ? (
247- < MarkdownContentCore content = { lesson . contentMarkdown } />
248- ) : (
249- < p className = "font-designer-16r text-gray-500" >
250- 본문이 준비 중입니다.
251- </ p >
252- ) }
253- </ div >
245+ { /* Scroll area: content + review form only */ }
246+ < div ref = { leftColRef } className = "flex-1 overflow-y-auto" >
247+ < div
248+ ref = { contentRef }
249+ className = "mt-300 min-h-[964px] rounded-150 bg-background-default p-500"
250+ >
251+ { lesson ?. contentMarkdown ? (
252+ < MarkdownContentCore content = { lesson . contentMarkdown } />
253+ ) : (
254+ < p className = "font-designer-16r text-gray-500" >
255+ 본문이 준비 중입니다.
256+ </ p >
257+ ) }
258+ </ div >
254259
255- < div ref = { reviewRef } />
256- < hr className = "my-500 border-gray-300" />
260+ < div ref = { reviewRef } />
261+ < hr className = "my-500 border-gray-300" />
257262
258- { tab === 'review' || tab === 'follow' ? (
259- < LessonReviewForm
260- key = { lessonId }
261- retrospectivePurpose = {
262- lesson ?. retrospectivePurpose ?? 'ARTIFACT_SHARE'
263- }
264- retrospectivePrompt = { lesson ?. retrospectivePrompt }
265- artifactSubmissionRequired = {
266- lesson ?. artifactSubmissionRequired ?? false
267- }
268- alreadySubmitted = { alreadySubmitted }
269- submitting = { submitRetrospective . isPending }
270- isLastLesson = { isLastLesson }
271- onSubmit = { handleSubmit }
272- />
273- ) : null }
263+ { tab === 'review' || tab === 'follow' ? (
264+ < LessonReviewForm
265+ key = { lessonId }
266+ retrospectivePurpose = {
267+ lesson ?. retrospectivePurpose ?? 'ARTIFACT_SHARE'
268+ }
269+ retrospectivePrompt = { lesson ?. retrospectivePrompt }
270+ artifactSubmissionRequired = {
271+ lesson ?. artifactSubmissionRequired ?? false
272+ }
273+ alreadySubmitted = { alreadySubmitted }
274+ submitting = { submitRetrospective . isPending }
275+ isLastLesson = { isLastLesson }
276+ onSubmit = { handleSubmit }
277+ />
278+ ) : null }
274279
275- < div className = "h-1000" />
280+ < div className = "h-1000" />
281+ </ div >
276282 </ div >
277283
278- { /* RIGHT sticky sidebar */ }
279- < div className = "sticky top-800 z-10 flex flex-col gap-250" >
280- < LessonQnaCard
281- myQnas = { qnaSidebar ?. qnas ?? [ ] }
282- builderQnas = { qnaSidebar ?. builderQnas ?? [ ] }
283- onAskClick = { ( ) => setSubmissionModalOpen ( true ) }
284- onSelectQna = { setSelectedQnaId }
285- />
286- < LessonBuilderFeedCard
287- feeds = { feedPreview ?. feeds ?? [ ] }
288- onSelectFeed = { setSelectedFeedId }
289- />
284+ { /* RIGHT sidebar — stays put while left column scrolls */ }
285+ < div className = "w-4500 shrink-0 pt-500" >
286+ < div className = "flex flex-col gap-250" >
287+ < LessonQnaCard
288+ myQnas = { qnaSidebar ?. qnas ?? [ ] }
289+ builderQnas = { qnaSidebar ?. builderQnas ?? [ ] }
290+ onAskClick = { ( ) => setSubmissionModalOpen ( true ) }
291+ onSelectQna = { setSelectedQnaId }
292+ />
293+ < LessonBuilderFeedCard
294+ feeds = { feedPreview ?. feeds ?? [ ] }
295+ onSelectFeed = { setSelectedFeedId }
296+ />
297+ </ div >
290298 </ div >
291299 </ div >
292300 </ div >
0 commit comments