1import { useEffect, useId, useRef, useState, type ComponentProps } from "react";
2
3import { cn } from "~/lib/utils";
4import { Button } from "../ui/button";
5import { PullRequestMarkdown } from "./PullRequestMarkdown";
6import { observeResize } from "~/lib/observeResize";
7
8/** Keep the complete markdown intact while limiting long reports to a readable preview. */
9export function PullRequestCommentBody({
10 className,
11 ...props
12}: ComponentProps<typeof PullRequestMarkdown>) {
13 const [expanded, setExpanded] = useState(false);
14 const [overflowing, setOverflowing] = useState(false);
15 const content = useRef<HTMLDivElement>(null);
16 const id = useId();
17
18 useEffect(() => {
19 const element = content.current;
20 if (!element) return;
21 const measure = () => setOverflowing(element.getBoundingClientRect().height > 240);
22 measure();
23 return observeResize(element, measure);
24 }, []);
25
26 return (
27 <div className={cn("min-w-0", className)}>
28 <div
29 id={id}
30 className={cn("relative", !expanded && "max-h-[240px] overflow-hidden")}
31 onFocusCapture={() => setExpanded(true)}
32 >
33 <div ref={content}>
34 <PullRequestMarkdown {...props} />
35 </div>
36 {overflowing && !expanded ? (
37 <div
38 aria-hidden
39 className="pointer-events-none absolute inset-x-0 bottom-0 h-10 bg-gradient-to-t from-background to-transparent"
40 />
41 ) : null}
42 </div>
43 {overflowing ? (
44 <Button
45 size="xs"
46 variant="ghost-muted"
47 className="mt-2"
48 aria-expanded={expanded}
49 aria-controls={id}
50 onClick={() => {
51 if (expanded) content.current?.parentElement?.scrollIntoView({ block: "nearest" });
52 setExpanded(!expanded);
53 }}
54 >
55 {expanded ? "Show less" : "Show full comment"}
56 </Button>
57 ) : null}
58 </div>
59 );
60}