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 {
|
.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;
|
min-width: 180px;
|
||||||
max-height: 280px;
|
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
background: var(--color-surface);
|
background: var(--color-surface);
|
||||||
border: 2px solid var(--color-border-subtle);
|
border: 2px solid var(--color-border-subtle);
|
||||||
|
|||||||
@@ -31,18 +31,59 @@ type State =
|
|||||||
loadingMore: boolean;
|
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(
|
export function UserListPopover(
|
||||||
{ anchorRef, onClose, fetchUrl }: UserListPopoverProps,
|
{ anchorRef, onClose, fetchUrl }: UserListPopoverProps,
|
||||||
) {
|
) {
|
||||||
const { token } = useAuth();
|
const { token } = useAuth();
|
||||||
const [state, setState] = useState<State>({ status: "loading" });
|
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);
|
const popoverRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
// Position via the anchor's viewport rect (mirrors Tooltip.tsx) — fixed +
|
// 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.
|
// 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(() => {
|
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]);
|
}, [anchorRef]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -131,7 +172,7 @@ export function UserListPopover(
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!rect) return null;
|
if (!placement) return null;
|
||||||
|
|
||||||
return createPortal(
|
return createPortal(
|
||||||
<div
|
<div
|
||||||
@@ -140,8 +181,10 @@ export function UserListPopover(
|
|||||||
ref={popoverRef}
|
ref={popoverRef}
|
||||||
style={{
|
style={{
|
||||||
position: "fixed",
|
position: "fixed",
|
||||||
top: rect.bottom + 4,
|
left: placement.left,
|
||||||
left: rect.left,
|
top: placement.top,
|
||||||
|
bottom: placement.bottom,
|
||||||
|
maxHeight: placement.maxHeight,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{state.status === "loading" && (
|
{state.status === "loading" && (
|
||||||
|
|||||||
Reference in New Issue
Block a user