Task 4b: Fix PatientBanner scroll condensation

Root cause: sentinel element with `absolute top-0` inside PatientBanner was
positioned at viewport top, always triggering the IntersectionObserver's
-100px rootMargin threshold — banner was permanently stuck in condensed state.

Fix: Restructured PMRInterface layout from document-scroll to flex container
with explicit scroll container (`overflow-y-auto` on main). Lifted scroll
condensation logic to PMRInterface, passing `isCondensed` prop down to
PatientBanner. Replaced IntersectionObserver with scroll event listener on
the main element for reliable scroll position detection.

Key changes:
- PMRInterface: flex h-screen overflow-hidden layout (sidebar + content column)
- PatientBanner: accepts isCondensed prop, removed sticky/sentinel/hook
- ClinicalSidebar: h-full instead of h-screen sticky (parent handles sizing)
- useScrollCondensation: scroll event on container element via callback ref

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-13 00:30:23 +00:00
parent b7471c5cf8
commit d16656b954
4 changed files with 91 additions and 104 deletions
+15 -20
View File
@@ -1,35 +1,30 @@
import { useState, useEffect, useRef } from 'react'
import { useState, useEffect, useCallback } from 'react'
interface UseScrollCondensationOptions {
threshold?: number
scrollContainer?: HTMLElement | null
}
export function useScrollCondensation(options: UseScrollCondensationOptions = {}) {
const { threshold = 100 } = options
const { threshold = 100, scrollContainer } = options
const [isCondensed, setIsCondensed] = useState(false)
const sentinelRef = useRef<HTMLDivElement>(null)
const handleScroll = useCallback(() => {
if (!scrollContainer) return
setIsCondensed(scrollContainer.scrollTop >= threshold)
}, [scrollContainer, threshold])
useEffect(() => {
const sentinel = sentinelRef.current
if (!sentinel) return
if (!scrollContainer) return
const observer = new IntersectionObserver(
(entries) => {
const [entry] = entries
setIsCondensed(!entry.isIntersecting)
},
{
rootMargin: `-${threshold}px 0px 0px 0px`,
threshold: 0,
}
)
observer.observe(sentinel)
scrollContainer.addEventListener('scroll', handleScroll, { passive: true })
// Check initial state
handleScroll()
return () => {
observer.disconnect()
scrollContainer.removeEventListener('scroll', handleScroll)
}
}, [threshold])
}, [scrollContainer, handleScroll])
return { isCondensed, sentinelRef }
return { isCondensed }
}