Skip to content

Commit 671ab73

Browse files
committed
i have no mouth yet i must scream
1 parent 5cd4d82 commit 671ab73

2 files changed

Lines changed: 78 additions & 9 deletions

File tree

src/app/exercise/[id]/ExercisePageClient.tsx

Lines changed: 72 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client';
22

3-
import { use, useEffect, useState } from 'react';
3+
import { use, useEffect, useRef, useState } from 'react';
44
import { usePathname, useRouter } from 'next/navigation';
55
import { useExerciseContext } from '@/state/ExerciseContext';
66
import { getExercise, getAllExercises } from '@/exercises/registry';
@@ -150,6 +150,10 @@ export default function ExercisePageClient({ params }: { params: Promise<{ id: s
150150
const pathname = usePathname();
151151
const [isMobile, setIsMobile] = useState(false);
152152
const [activeMobileTab, setActiveMobileTab] = useState<'source' | 'viz' | 'log' | 'misc'>('source');
153+
const mobileShellRef = useRef<HTMLDivElement | null>(null);
154+
const mobileWorkspacePanelRef = useRef<HTMLDivElement | null>(null);
155+
const mobileDirectionsRef = useRef<HTMLDivElement | null>(null);
156+
const mobileBottomDockRef = useRef<HTMLDivElement | null>(null);
153157

154158
useEffect(() => {
155159
if (typeof window === 'undefined') return;
@@ -205,6 +209,67 @@ export default function ExercisePageClient({ params }: { params: Promise<{ id: s
205209
setActiveMobileTab('source');
206210
}, [id]);
207211

212+
useEffect(() => {
213+
if (!isMobile) {
214+
mobileShellRef.current?.style.removeProperty('--mobile-workspace-panel-height');
215+
return;
216+
}
217+
218+
const shellElement = mobileShellRef.current;
219+
const panelElement = mobileWorkspacePanelRef.current;
220+
const directionsElement = mobileDirectionsRef.current;
221+
const bottomDockElement = mobileBottomDockRef.current;
222+
223+
if (!shellElement || !panelElement || !directionsElement || !bottomDockElement) {
224+
return;
225+
}
226+
227+
let frameId = 0;
228+
229+
const syncPanelHeight = () => {
230+
frameId = 0;
231+
const shellRect = shellElement.getBoundingClientRect();
232+
const panelRect = panelElement.getBoundingClientRect();
233+
const bottomDockRect = bottomDockElement.getBoundingClientRect();
234+
const shellStyles = window.getComputedStyle(shellElement);
235+
const shellGap = parseFloat(shellStyles.rowGap || shellStyles.gap || '0') || 0;
236+
const availableHeight = Math.floor(bottomDockRect.top - panelRect.top - shellGap);
237+
const maxAvailableHeight = Math.floor(shellRect.bottom - panelRect.top);
238+
const nextHeight = Math.max(0, Math.min(availableHeight, maxAvailableHeight));
239+
shellElement.style.setProperty('--mobile-workspace-panel-height', `${nextHeight}px`);
240+
};
241+
242+
const queueSyncPanelHeight = () => {
243+
if (frameId !== 0) return;
244+
frameId = window.requestAnimationFrame(syncPanelHeight);
245+
};
246+
247+
queueSyncPanelHeight();
248+
249+
const observer = new ResizeObserver(() => {
250+
queueSyncPanelHeight();
251+
});
252+
253+
observer.observe(shellElement);
254+
observer.observe(directionsElement);
255+
observer.observe(bottomDockElement);
256+
257+
window.addEventListener('resize', queueSyncPanelHeight);
258+
window.visualViewport?.addEventListener('resize', queueSyncPanelHeight);
259+
window.visualViewport?.addEventListener('scroll', queueSyncPanelHeight);
260+
261+
return () => {
262+
observer.disconnect();
263+
window.removeEventListener('resize', queueSyncPanelHeight);
264+
window.visualViewport?.removeEventListener('resize', queueSyncPanelHeight);
265+
window.visualViewport?.removeEventListener('scroll', queueSyncPanelHeight);
266+
if (frameId !== 0) {
267+
window.cancelAnimationFrame(frameId);
268+
}
269+
shellElement.style.removeProperty('--mobile-workspace-panel-height');
270+
};
271+
}, [activeMobileTab, isMobile]);
272+
208273
useEffect(() => {
209274
const mainElement = document.querySelector('#app-body > main');
210275
const appElement = document.getElementById('app');
@@ -447,8 +512,10 @@ export default function ExercisePageClient({ params }: { params: Promise<{ id: s
447512

448513
if (isMobile) {
449514
return (
450-
<div className="mobile-exercise-shell">
451-
<ExerciseDirectionsPanel />
515+
<div className="mobile-exercise-shell" ref={mobileShellRef}>
516+
<div ref={mobileDirectionsRef}>
517+
<ExerciseDirectionsPanel />
518+
</div>
452519

453520
<section className="mobile-workspace">
454521
<div className="mobile-workspace-tabs" role="tablist" aria-label="Exercise workspace">
@@ -490,7 +557,7 @@ export default function ExercisePageClient({ params }: { params: Promise<{ id: s
490557
</button>
491558
</div>
492559

493-
<div className="mobile-workspace-panel">
560+
<div className="mobile-workspace-panel" ref={mobileWorkspacePanelRef}>
494561
{activeMobileTab === 'source' && <SourcePanel showDescription={false} />}
495562
{activeMobileTab === 'viz' && (
496563
<ErrorBoundary>
@@ -509,7 +576,7 @@ export default function ExercisePageClient({ params }: { params: Promise<{ id: s
509576
</div>
510577
</section>
511578

512-
<div className="mobile-bottom-dock">
579+
<div className="mobile-bottom-dock" ref={mobileBottomDockRef}>
513580
<ErrorBoundary>
514581
<InputPanel showToolkit={false} />
515582
</ErrorBoundary>

src/app/globals.css

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -980,7 +980,6 @@ button.link-button:focus-visible,
980980

981981
.mobile-workspace {
982982
min-height: 0;
983-
flex: 1;
984983
display: flex;
985984
flex-direction: column;
986985
gap: 0.5rem;
@@ -1022,15 +1021,18 @@ button.link-button:focus-visible,
10221021

10231022
.mobile-workspace-panel {
10241023
display: flex;
1025-
flex: 1 1 auto;
1024+
flex: 0 0 auto;
10261025
flex-direction: column;
1026+
height: var(--mobile-workspace-panel-height, auto);
1027+
max-height: var(--mobile-workspace-panel-height, none);
10271028
min-height: 0;
10281029
overflow: hidden;
10291030
}
10301031

10311032
.mobile-workspace-panel > .panel {
1032-
flex: 1 1 auto;
1033-
height: auto;
1033+
flex: 1 1 100%;
1034+
height: 100%;
1035+
max-height: 100%;
10341036
min-height: 0;
10351037
}
10361038

0 commit comments

Comments
 (0)