Skip to content

Commit 89f75e5

Browse files
committed
Auto scroll
1 parent a88fe2f commit 89f75e5

1 file changed

Lines changed: 44 additions & 15 deletions

File tree

  • changelog-generator/app/response/[runId]

changelog-generator/app/response/[runId]/page.tsx

Lines changed: 44 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"use client";
22

33
import { useParams, useSearchParams, useRouter } from "next/navigation";
4-
import { useMemo, useState } from "react";
4+
import { useMemo, useRef, useState } from "react";
55
import { useRealtimeRun, useRealtimeStream } from "@trigger.dev/react-hooks";
66
import { Streamdown } from "streamdown";
77
import { ArrowLeft, AlertCircle, Calendar, Copy, Check } from "lucide-react";
@@ -88,6 +88,8 @@ export default function ResponsePage() {
8888
const searchParams = useSearchParams();
8989
const router = useRouter();
9090
const [copiedIndex, setCopiedIndex] = useState<number | null>(null);
91+
const userScrolledRef = useRef(false);
92+
const lastScrollTop = useRef(0);
9193

9294
const runId = params.runId as string;
9395
const accessToken = searchParams.get("accessToken") || "";
@@ -110,6 +112,21 @@ export default function ResponsePage() {
110112
router.push("/");
111113
};
112114

115+
// Detect if user scrolled up (away from bottom) to cancel auto-scroll
116+
const handleScroll = () => {
117+
const scrollTop = window.scrollY;
118+
// User scrolled up - cancel auto-scroll
119+
if (scrollTop < lastScrollTop.current - 50) {
120+
userScrolledRef.current = true;
121+
}
122+
// User scrolled to bottom - re-enable auto-scroll
123+
const isNearBottom = window.innerHeight + scrollTop >= document.body.scrollHeight - 100;
124+
if (isNearBottom) {
125+
userScrolledRef.current = false;
126+
}
127+
lastScrollTop.current = scrollTop;
128+
};
129+
113130
const handleCopySection = async (
114131
section: { title: string; content: string; category?: string },
115132
index: number
@@ -151,7 +168,7 @@ export default function ResponsePage() {
151168
const metadata = run?.metadata as RunMetadata | undefined;
152169

153170
return (
154-
<div className="min-h-screen bg-background">
171+
<div className="min-h-screen bg-background" onScroll={handleScroll} onWheel={handleScroll}>
155172
<div className="container mx-auto px-4 py-12 max-w-2xl">
156173
{/* Header */}
157174
<header className="mb-8">
@@ -248,24 +265,26 @@ export default function ResponsePage() {
248265
<Card key={i}>
249266
<CardHeader className="flex gap-4">
250267
<div className="flex items-center w-full justify-between gap-4 shrink-0">
251-
<div className="gap-3 flex items-center">
252-
{section.category && (
253-
<span className="text-xs font-medium text-muted-foreground bg-secondary px-2 py-0.5 rounded">
254-
{section.category}
255-
</span>
256-
)}
257-
{section.date && (
258-
<span className="text-xs text-muted-foreground">
259-
{section.date}
260-
</span>
261-
)}
262-
</div>
268+
{(section.category || section.date) && (
269+
<div className="gap-3 flex items-center">
270+
{section.category && (
271+
<span className="text-xs font-medium text-muted-foreground bg-secondary px-2 py-0.5 rounded">
272+
{section.category}
273+
</span>
274+
)}
275+
{section.date && (
276+
<span className="text-xs text-muted-foreground">
277+
{section.date}
278+
</span>
279+
)}
280+
</div>
281+
)}
263282

264283
<Button
265284
variant="ghost"
266285
size="sm"
267286
onClick={() => handleCopySection(section, i)}
268-
className="text-xs text-muted-foreground hover:text-foreground"
287+
className="ml-auto text-xs text-muted-foreground hover:text-foreground"
269288
>
270289
{copiedIndex === i ? (
271290
<>
@@ -287,6 +306,7 @@ export default function ResponsePage() {
287306
isAnimating={isStreaming && i === sections.length - 1}
288307
mode="streaming"
289308
shikiTheme={["github-dark", "github-dark"]}
309+
className="text-foreground/85"
290310
>
291311
{section.content}
292312
</Streamdown>
@@ -305,6 +325,15 @@ export default function ResponsePage() {
305325
</div>
306326
)}
307327

328+
{/* Scroll anchor - auto-scrolls unless user scrolled away */}
329+
<div
330+
ref={(el) => {
331+
if (el && isStreaming && !userScrolledRef.current) {
332+
el.scrollIntoView({ behavior: "smooth", block: "end" });
333+
}
334+
}}
335+
/>
336+
308337
{/* Completion Actions */}
309338
{status === "completed" && (
310339
<div className="mt-8">

0 commit comments

Comments
 (0)