v3: fixed votes/likes popover placement
All checks were successful
Build and Publish Docker Image / build-and-push (push) Successful in 42s
All checks were successful
Build and Publish Docker Image / build-and-push (push) Successful in 42s
This commit is contained in:
@@ -2190,9 +2190,9 @@ body.has-player .fab-new {
|
||||
}
|
||||
|
||||
.user-list-popover {
|
||||
/* position/top/left set inline (fixed, anchored to the trigger's viewport rect) */
|
||||
/* position/top/bottom/left/max-height set inline — anchored to the
|
||||
trigger's viewport rect, flipping above it when there isn't room below */
|
||||
min-width: 180px;
|
||||
max-height: 280px;
|
||||
overflow-y: auto;
|
||||
background: var(--color-surface);
|
||||
border: 2px solid var(--color-border-subtle);
|
||||
|
||||
@@ -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" && (
|
||||
|
||||
Reference in New Issue
Block a user