Skip to content

Commit 5657793

Browse files
committed
fix: people page UI overlap
1 parent 5d6bb5e commit 5657793

2 files changed

Lines changed: 82 additions & 20 deletions

File tree

‎src/app/globals.css‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,10 @@
156156
@apply border-border;
157157
}
158158

159+
html {
160+
scrollbar-gutter: stable;
161+
}
162+
159163
body {
160164
@apply bg-background text-foreground;
161165
font-family:

‎src/components/PeopleDirectory.tsx‎

Lines changed: 78 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import Image from "next/image";
44
import { useEffect, useId, useRef, useState } from "react";
5+
import { createPortal } from "react-dom";
56
import type { PeopleGroups, Person } from "@/lib/people-schema";
67

78
function TeamMemberCard({
@@ -153,21 +154,73 @@ function AlumniDetailModal({
153154
}) {
154155
const titleId = useId();
155156
const closeButtonRef = useRef<HTMLButtonElement>(null);
157+
const dialogRef = useRef<HTMLDivElement>(null);
158+
const backdropRef = useRef<HTMLDivElement>(null);
159+
const [mounted, setMounted] = useState(false);
156160

157161
useEffect(() => {
158-
closeButtonRef.current?.focus();
162+
setMounted(true);
163+
}, []);
164+
165+
// Prevent background scrolling while modal is open without removing body scrollbar (avoids sideways layout shift)
166+
useEffect(() => {
167+
const backdrop = backdropRef.current;
168+
if (!backdrop) return;
169+
170+
const handleWheel = (e: WheelEvent) => {
171+
if (!dialogRef.current?.contains(e.target as Node)) {
172+
e.preventDefault();
173+
}
174+
};
175+
176+
const handleTouchMove = (e: TouchEvent) => {
177+
if (!dialogRef.current?.contains(e.target as Node)) {
178+
e.preventDefault();
179+
}
180+
};
181+
182+
backdrop.addEventListener("wheel", handleWheel, { passive: false });
183+
backdrop.addEventListener("touchmove", handleTouchMove, { passive: false });
184+
185+
return () => {
186+
backdrop.removeEventListener("wheel", handleWheel);
187+
backdrop.removeEventListener("touchmove", handleTouchMove);
188+
};
189+
}, []);
190+
191+
// Move focus into the modal on open, restore previous focus on close, and trap Tab navigation
192+
useEffect(() => {
193+
const trigger = document.activeElement as HTMLElement | null;
194+
closeButtonRef.current?.focus({ preventScroll: true });
159195

160196
const handleKeyDown = (event: KeyboardEvent) => {
161197
if (event.key === "Escape") {
162198
event.preventDefault();
163199
onClose();
200+
} else if (event.key === "Tab") {
201+
const dialog = dialogRef.current;
202+
if (!dialog) return;
203+
const focusables = dialog.querySelectorAll<HTMLElement>(
204+
'button:not([disabled]), a[href]:not([disabled]), [tabindex]:not([tabindex="-1"])',
205+
);
206+
if (focusables.length === 0) return;
207+
const first = focusables[0];
208+
const last = focusables[focusables.length - 1];
209+
if (event.shiftKey && document.activeElement === first) {
210+
event.preventDefault();
211+
last.focus();
212+
} else if (!event.shiftKey && document.activeElement === last) {
213+
event.preventDefault();
214+
first.focus();
215+
}
164216
}
165217
};
166218

167219
window.addEventListener("keydown", handleKeyDown);
168220

169221
return () => {
170222
window.removeEventListener("keydown", handleKeyDown);
223+
trigger?.focus({ preventScroll: true });
171224
};
172225
}, [onClose]);
173226

@@ -178,38 +231,42 @@ function AlumniDetailModal({
178231
? [member.title]
179232
: [];
180233

181-
return (
234+
if (!mounted) return null;
235+
236+
return createPortal(
182237
<div
183-
className="fixed inset-0 z-60 flex items-center justify-center p-4 bg-black/50 backdrop-blur-xs motion-safe:animate-fade-in"
238+
ref={backdropRef}
239+
className="fixed inset-0 z-[100] flex items-center justify-center p-4 sm:p-6 bg-black/60 backdrop-blur-xs motion-safe:animate-fade-in touch-none"
184240
onClick={(event) => {
185241
if (event.target === event.currentTarget) onClose();
186242
}}
187243
role="presentation"
188244
>
189245
<div
246+
ref={dialogRef}
190247
role="dialog"
191248
aria-modal="true"
192249
aria-labelledby={titleId}
193-
className="relative w-full max-w-md rounded-2xl bg-card border border-border shadow-xl p-5 sm:p-6 text-left motion-safe:animate-slide-up-fade"
250+
className="relative w-[92vw] sm:w-[85vw] md:max-w-xl max-h-[88vh] overflow-y-auto overscroll-contain rounded-2xl bg-card border border-border shadow-2xl p-6 sm:p-7 text-left motion-safe:animate-slide-up-fade touch-auto"
194251
>
195252
<button
196253
ref={closeButtonRef}
197254
type="button"
198255
onClick={onClose}
199256
aria-label="Close alumni details"
200-
className="absolute top-4 right-4 inline-flex h-8 w-8 items-center justify-center rounded-lg text-muted-foreground hover:text-foreground hover:bg-muted transition-colors cursor-pointer"
257+
className="absolute top-4 right-4 sm:top-5 sm:right-5 inline-flex h-8 w-8 items-center justify-center rounded-lg text-muted-foreground hover:text-foreground hover:bg-muted transition-colors cursor-pointer"
201258
>
202259
✕
203260
</button>
204261

205-
<div className="flex items-center gap-4 pr-8">
206-
<div className="relative h-16 w-16 shrink-0 overflow-hidden rounded-xl bg-muted border border-border/60">
262+
<div className="flex items-center gap-4 sm:gap-5 pr-8">
263+
<div className="relative h-16 w-16 sm:h-18 sm:w-18 shrink-0 overflow-hidden rounded-xl bg-muted border border-border/60">
207264
<Image
208265
src={member.photo}
209266
alt={member.name}
210267
fill
211268
className="object-cover"
212-
sizes="64px"
269+
sizes="(max-width: 640px) 64px, 72px"
213270
/>
214271
</div>
215272
<div className="min-w-0">
@@ -220,34 +277,34 @@ function AlumniDetailModal({
220277
{member.name}
221278
</h3>
222279
{member.role && (
223-
<p className="text-xs font-medium text-primary mt-0.5">
280+
<p className="text-xs sm:text-sm font-medium text-primary mt-0.5">
224281
{member.role}
225282
</p>
226283
)}
227284
{member.title && (
228-
<p className="text-xs text-muted-foreground mt-0.5">
285+
<p className="text-xs sm:text-sm text-muted-foreground mt-0.5">
229286
{member.title}
230287
</p>
231288
)}
232289
</div>
233290
</div>
234291

235292
{employmentList.length > 0 && (
236-
<div className="mt-5 pt-4 border-t border-border/60">
237-
<h4 className="text-[11px] font-semibold uppercase tracking-[0.08em] text-muted-foreground mb-3">
293+
<div className="mt-6 pt-4 border-t border-border/60">
294+
<h4 className="text-[11px] sm:text-xs font-semibold uppercase tracking-[0.08em] text-muted-foreground mb-3.5">
238295
Career &amp; Employment History
239296
</h4>
240-
<ol className="relative border-l border-border/80 ml-2 space-y-3">
297+
<ol className="relative border-l border-border/80 ml-2 space-y-3.5">
241298
{employmentList.map((entry, idx) => (
242-
<li key={entry} className="pl-4 relative">
299+
<li key={entry} className="pl-4 sm:pl-5 relative">
243300
<span
244301
className={`absolute -left-[5px] top-1.5 h-2.5 w-2.5 rounded-full border-2 border-card ${
245302
idx === 0 ? "bg-primary" : "bg-muted-foreground/50"
246303
}`}
247304
aria-hidden="true"
248305
/>
249306
<p
250-
className={`text-xs leading-relaxed ${
307+
className={`text-xs sm:text-[13px] leading-relaxed ${
251308
idx === 0
252309
? "font-medium text-foreground"
253310
: "text-muted-foreground"
@@ -261,30 +318,31 @@ function AlumniDetailModal({
261318
</div>
262319
)}
263320

264-
<div className="mt-5 pt-3 border-t border-border/60 flex items-center justify-end gap-2">
321+
<div className="mt-6 pt-3.5 border-t border-border/60 flex items-center justify-end gap-2.5">
265322
{member.url && (
266323
<a
267324
href={member.url}
268325
target="_blank"
269326
rel="noopener noreferrer"
270-
className="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-medium bg-primary/10 text-primary hover:bg-primary/15 transition-colors"
327+
className="inline-flex items-center gap-1.5 px-3.5 py-1.5 rounded-lg text-xs sm:text-sm font-medium bg-primary/10 text-primary hover:bg-primary/15 transition-colors"
271328
>
272329
<span>Visit Website</span>
273-
<span className="text-[11px] font-mono" aria-hidden="true">
330+
<span className="text-[11px] sm:text-xs font-mono" aria-hidden="true">
274331
↗
275332
</span>
276333
</a>
277334
)}
278335
<button
279336
type="button"
280337
onClick={onClose}
281-
className="inline-flex items-center px-3 py-1.5 rounded-lg text-xs font-medium bg-muted text-foreground hover:bg-muted/80 transition-colors cursor-pointer"
338+
className="inline-flex items-center px-3.5 py-1.5 rounded-lg text-xs sm:text-sm font-medium bg-muted text-foreground hover:bg-muted/80 transition-colors cursor-pointer"
282339
>
283340
Close
284341
</button>
285342
</div>
286343
</div>
287-
</div>
344+
</div>,
345+
document.body,
288346
);
289347
}
290348

0 commit comments

Comments
 (0)