v3: fixed votes/likes popover placement
All checks were successful
Build and Publish Docker Image / build-and-push (push) Successful in 42s

This commit is contained in:
khannurien
2026-06-21 14:47:25 +00:00
parent 3d03c9e69c
commit 57cf55cc48
2 changed files with 50 additions and 7 deletions

View File

@@ -31,18 +31,59 @@ type State =
loadingMore: boolean;
};
interface Placement {
left: number;
top?: number;
bottom?: number;
maxHeight: number;
}
const PREFERRED_MAX_HEIGHT = 280;
const VIEWPORT_MARGIN = 8;
const MIN_USABLE_HEIGHT = 100;
export function UserListPopover(
{ anchorRef, onClose, fetchUrl }: UserListPopoverProps,
) {
const { token } = useAuth();
const [state, setState] = useState<State>({ status: "loading" });
const [rect, setRect] = useState<DOMRect | null>(null);
const [placement, setPlacement] = useState<Placement | null>(null);
const popoverRef = useRef<HTMLDivElement>(null);
// Position via the anchor's viewport rect (mirrors Tooltip.tsx) — fixed +
// portaled to <body> so the popover can't be clipped by a card's overflow.
// Flips above the anchor (anchored from the viewport bottom edge, so it
// grows upward) when there isn't enough room below, so a long list never
// overflows past the bottom of the viewport.
useEffect(() => {
setRect(anchorRef.current?.getBoundingClientRect() ?? null);
const anchorRect = anchorRef.current?.getBoundingClientRect();
if (!anchorRect) {
setPlacement(null);
return;
}
const spaceBelow = globalThis.innerHeight - anchorRect.bottom -
VIEWPORT_MARGIN;
const spaceAbove = anchorRect.top - VIEWPORT_MARGIN;
if (spaceBelow >= MIN_USABLE_HEIGHT || spaceBelow >= spaceAbove) {
setPlacement({
left: anchorRect.left,
top: anchorRect.bottom + 4,
maxHeight: Math.max(
MIN_USABLE_HEIGHT,
Math.min(PREFERRED_MAX_HEIGHT, spaceBelow),
),
});
} else {
setPlacement({
left: anchorRect.left,
bottom: globalThis.innerHeight - anchorRect.top + 4,
maxHeight: Math.max(
MIN_USABLE_HEIGHT,
Math.min(PREFERRED_MAX_HEIGHT, spaceAbove),
),
});
}
}, [anchorRef]);
useEffect(() => {
@@ -131,7 +172,7 @@ export function UserListPopover(
});
};
if (!rect) return null;
if (!placement) return null;
return createPortal(
<div
@@ -140,8 +181,10 @@ export function UserListPopover(
ref={popoverRef}
style={{
position: "fixed",
top: rect.bottom + 4,
left: rect.left,
left: placement.left,
top: placement.top,
bottom: placement.bottom,
maxHeight: placement.maxHeight,
}}
>
{state.status === "loading" && (