Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 | 19x 19x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x | import { useEffect, useLayoutEffect, useRef, useState } from "react";
import { Box } from "@mui/material";
import GiveLink from "@shared/Link/GiveLink";
import GiveText from "@shared/Text/GiveText";
/** Beyond this the reply is truncated behind Show More (annotation, frame 7506-74973). */
const REPLY_CLAMP_LINES = 3;
type Props = {
body: string;
};
/**
* A published reply, clamped to three lines with Show More when — and only
* when — it is actually cut off. The short replies in the frames carry no link.
*
* Shared by the portal's reply block and the live event page's: the measurement
* is subtle enough (clamped while collapsed, re-run once webfonts settle) that
* two copies of it drift.
*/
const ReviewReplyBody = ({ body }: Props) => {
const [expanded, setExpanded] = useState(false);
const [isTruncated, setIsTruncated] = useState(false);
const bodyRef = useRef<HTMLDivElement | null>(null);
// Measured while collapsed, because a clamped block stops reporting overflow
// once it is expanded.
useLayoutEffect(() => {
const node = bodyRef.current;
Iif (!node || expanded) return;
setIsTruncated(node.scrollHeight > node.clientHeight + 1);
}, [expanded, body]);
// The measurement above runs before webfonts settle, which can change the
// line count; re-measure once they have.
useEffect(() => {
const fonts = (document as Document & { fonts?: FontFaceSet }).fonts;
Eif (!fonts) return;
let cancelled = false;
fonts.ready.then(() => {
const node = bodyRef.current;
if (cancelled || !node || expanded) return;
setIsTruncated(node.scrollHeight > node.clientHeight + 1);
});
return () => {
cancelled = true;
};
}, [expanded, body]);
return (
<>
{/* The clamp lives on this wrapper rather than on GiveText, which does
not forward a ref — and the measurement needs the clamped node. */}
<Box
ref={bodyRef}
sx={
expanded
? { whiteSpace: "pre-wrap" }
: {
display: "-webkit-box",
WebkitLineClamp: REPLY_CLAMP_LINES,
WebkitBoxOrient: "vertical",
overflow: "hidden",
}
}
>
<GiveText variant="bodyS" color="primary" component="span">
{body}
</GiveText>
</Box>
{(isTruncated || expanded) && (
<GiveLink
component="button"
color="primary"
onClick={() => setExpanded((value) => !value)}
>
{expanded ? "Show Less" : "Show More"}
</GiveLink>
)}
</>
);
};
export default ReviewReplyBody;
|