fix: Sidebar accounts for sticky header height via ResizeObserver
Measures the review header dynamically and offsets the sidebar's sticky top and max-height accordingly, eliminating the gap when scrolled.
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { useCallback, useMemo, useRef, useState } from "react";
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { Loader2 } from "lucide-react";
|
import { Loader2 } from "lucide-react";
|
||||||
import { trpc } from "@/lib/trpc";
|
import { trpc } from "@/lib/trpc";
|
||||||
@@ -18,6 +18,18 @@ export function ReviewTab({ initiativeId }: ReviewTabProps) {
|
|||||||
const [selectedCommit, setSelectedCommit] = useState<string | null>(null);
|
const [selectedCommit, setSelectedCommit] = useState<string | null>(null);
|
||||||
const [viewedFiles, setViewedFiles] = useState<Set<string>>(new Set());
|
const [viewedFiles, setViewedFiles] = useState<Set<string>>(new Set());
|
||||||
const fileRefs = useRef<Map<string, HTMLDivElement>>(new Map());
|
const fileRefs = useRef<Map<string, HTMLDivElement>>(new Map());
|
||||||
|
const headerRef = useRef<HTMLDivElement>(null);
|
||||||
|
const [headerHeight, setHeaderHeight] = useState(0);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const el = headerRef.current;
|
||||||
|
if (!el) return;
|
||||||
|
const ro = new ResizeObserver(([entry]) => {
|
||||||
|
setHeaderHeight(entry.contentRect.height);
|
||||||
|
});
|
||||||
|
ro.observe(el);
|
||||||
|
return () => ro.disconnect();
|
||||||
|
}, []);
|
||||||
|
|
||||||
const toggleViewed = useCallback((filePath: string) => {
|
const toggleViewed = useCallback((filePath: string) => {
|
||||||
setViewedFiles(prev => {
|
setViewedFiles(prev => {
|
||||||
@@ -313,6 +325,7 @@ export function ReviewTab({ initiativeId }: ReviewTabProps) {
|
|||||||
return (
|
return (
|
||||||
<div className="rounded-lg border border-border bg-card">
|
<div className="rounded-lg border border-border bg-card">
|
||||||
{/* Header: phase selector + toolbar */}
|
{/* Header: phase selector + toolbar */}
|
||||||
|
<div ref={headerRef}>
|
||||||
<ReviewHeader
|
<ReviewHeader
|
||||||
phases={reviewablePhases.map((p) => ({ id: p.id, name: p.name, status: p.status }))}
|
phases={reviewablePhases.map((p) => ({ id: p.id, name: p.name, status: p.status }))}
|
||||||
activePhaseId={activePhaseId}
|
activePhaseId={activePhaseId}
|
||||||
@@ -331,12 +344,19 @@ export function ReviewTab({ initiativeId }: ReviewTabProps) {
|
|||||||
viewedCount={viewedFiles.size}
|
viewedCount={viewedFiles.size}
|
||||||
totalCount={allFiles.length}
|
totalCount={allFiles.length}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Main content area — sidebar always rendered to preserve state */}
|
{/* Main content area — sidebar always rendered to preserve state */}
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-[260px_1fr] rounded-b-lg">
|
<div className="grid grid-cols-1 lg:grid-cols-[260px_1fr] rounded-b-lg">
|
||||||
{/* Left: Sidebar — sticky to viewport, scrolls independently */}
|
{/* Left: Sidebar — sticky to viewport, scrolls independently */}
|
||||||
<div className="border-r border-border">
|
<div className="border-r border-border">
|
||||||
<div className="sticky top-0 h-screen max-h-[calc(100vh-4rem)] overflow-hidden">
|
<div
|
||||||
|
className="sticky overflow-hidden"
|
||||||
|
style={{
|
||||||
|
top: `${headerHeight}px`,
|
||||||
|
maxHeight: `calc(100vh - ${headerHeight}px)`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
<ReviewSidebar
|
<ReviewSidebar
|
||||||
files={allFiles}
|
files={allFiles}
|
||||||
comments={comments}
|
comments={comments}
|
||||||
|
|||||||
Reference in New Issue
Block a user