fix: re-position hover preview correctly to prevent going out of page bounds (#5702)
This commit is contained in:
@@ -4,7 +4,7 @@ import styled, { css } from "styled-components";
|
||||
import { s } from "@shared/styles";
|
||||
import Text from "~/components/Text";
|
||||
|
||||
export const CARD_MARGIN = 16;
|
||||
export const CARD_MARGIN = 10;
|
||||
|
||||
const NUMBER_OF_LINES = 10;
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { m } from "framer-motion";
|
||||
import * as React from "react";
|
||||
import { Portal } from "react-portal";
|
||||
import styled from "styled-components";
|
||||
import { depths, s } from "@shared/styles";
|
||||
import { depths } from "@shared/styles";
|
||||
import { UnfurlType } from "@shared/types";
|
||||
import LoadingIndicator from "~/components/LoadingIndicator";
|
||||
import useEventListener from "~/hooks/useEventListener";
|
||||
@@ -27,6 +27,14 @@ type Props = {
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
enum Direction {
|
||||
UP,
|
||||
DOWN,
|
||||
}
|
||||
|
||||
const POINTER_HEIGHT = 22;
|
||||
const POINTER_WIDTH = 22;
|
||||
|
||||
function HoverPreviewInternal({ element, onClose }: Props) {
|
||||
const url = element.href || element.dataset.url;
|
||||
const [isVisible, setVisible] = React.useState(false);
|
||||
@@ -36,31 +44,46 @@ function HoverPreviewInternal({ element, onClose }: Props) {
|
||||
const stores = useStores();
|
||||
const [cardLeft, setCardLeft] = React.useState(0);
|
||||
const [cardTop, setCardTop] = React.useState(0);
|
||||
const [pointerOffset, setPointerOffset] = React.useState(0);
|
||||
const [pointerLeft, setPointerLeft] = React.useState(0);
|
||||
const [pointerTop, setPointerTop] = React.useState(0);
|
||||
const [pointerDir, setPointerDir] = React.useState(Direction.UP);
|
||||
|
||||
React.useLayoutEffect(() => {
|
||||
if (isVisible && cardRef.current) {
|
||||
const elem = element.getBoundingClientRect();
|
||||
const card = cardRef.current.getBoundingClientRect();
|
||||
|
||||
const top = elem.bottom + window.scrollY;
|
||||
setCardTop(top);
|
||||
let cTop = elem.bottom + window.scrollY + CARD_MARGIN;
|
||||
let pTop = -POINTER_HEIGHT;
|
||||
let pDir = Direction.UP;
|
||||
if (cTop + card.height > window.innerHeight + window.scrollY) {
|
||||
// shift card upwards if it goes out of screen
|
||||
const bottom = elem.top + window.scrollY;
|
||||
cTop = bottom - card.height;
|
||||
// shift a little further to leave some margin between card and element boundary
|
||||
cTop -= CARD_MARGIN;
|
||||
// pointer should be shifted downwards to align with card's bottom
|
||||
pTop = card.height;
|
||||
pDir = Direction.DOWN;
|
||||
}
|
||||
setCardTop(cTop);
|
||||
setPointerTop(pTop);
|
||||
setPointerDir(pDir);
|
||||
|
||||
let left = elem.left;
|
||||
let pointerOffset = elem.width / 2;
|
||||
if (left + card.width > window.innerWidth) {
|
||||
let cLeft = elem.left;
|
||||
let pLeft = elem.width / 2;
|
||||
if (cLeft + card.width > window.innerWidth) {
|
||||
// shift card leftwards by the amount it went out of screen
|
||||
let shiftBy = left + card.width - window.innerWidth;
|
||||
// shift a littler further to leave some margin between card and window boundary
|
||||
let shiftBy = cLeft + card.width - window.innerWidth;
|
||||
// shift a little further to leave some margin between card and window boundary
|
||||
shiftBy += CARD_MARGIN;
|
||||
left -= shiftBy;
|
||||
cLeft -= shiftBy;
|
||||
|
||||
// shift pointer rightwards by same amount so as to position it back correctly
|
||||
pointerOffset += shiftBy;
|
||||
pLeft += shiftBy;
|
||||
}
|
||||
setCardLeft(left);
|
||||
|
||||
setPointerOffset(pointerOffset);
|
||||
setCardLeft(cLeft);
|
||||
setPointerLeft(pLeft);
|
||||
}
|
||||
}, [isVisible, element]);
|
||||
|
||||
@@ -193,7 +216,11 @@ function HoverPreviewInternal({ element, onClose }: Props) {
|
||||
description={data.description}
|
||||
/>
|
||||
)}
|
||||
<Pointer offset={pointerOffset} />
|
||||
<Pointer
|
||||
top={pointerTop}
|
||||
left={pointerLeft}
|
||||
direction={pointerDir}
|
||||
/>
|
||||
</Animate>
|
||||
) : null}
|
||||
</Position>
|
||||
@@ -217,7 +244,6 @@ const Animate = styled(m.div)`
|
||||
`;
|
||||
|
||||
const Position = styled.div<{ fixed?: boolean; top?: number; left?: number }>`
|
||||
margin-top: 10px;
|
||||
position: ${({ fixed }) => (fixed ? "fixed" : "absolute")};
|
||||
z-index: ${depths.hoverPreview};
|
||||
display: flex;
|
||||
@@ -227,11 +253,11 @@ const Position = styled.div<{ fixed?: boolean; top?: number; left?: number }>`
|
||||
${({ left }) => (left !== undefined ? `left: ${left}px` : "")};
|
||||
`;
|
||||
|
||||
const Pointer = styled.div<{ offset: number }>`
|
||||
top: -22px;
|
||||
left: ${(props) => props.offset}px;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
const Pointer = styled.div<{ top: number; left: number; direction: Direction }>`
|
||||
top: ${(props) => props.top}px;
|
||||
left: ${(props) => props.left}px;
|
||||
width: ${POINTER_WIDTH}px;
|
||||
height: ${POINTER_HEIGHT}px;
|
||||
position: absolute;
|
||||
transform: translateX(-50%);
|
||||
pointer-events: none;
|
||||
@@ -241,20 +267,26 @@ const Pointer = styled.div<{ offset: number }>`
|
||||
content: "";
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
${({ direction }) => (direction === Direction.UP ? "bottom: 0" : "top: 0")};
|
||||
${({ direction }) => (direction === Direction.UP ? "right: 0" : "left: 0")};
|
||||
}
|
||||
|
||||
&:before {
|
||||
border: 8px solid transparent;
|
||||
border-bottom-color: ${(props) =>
|
||||
props.theme.menuBorder || "rgba(0, 0, 0, 0.1)"};
|
||||
right: -1px;
|
||||
${({ direction, theme }) =>
|
||||
direction === Direction.UP
|
||||
? `border-bottom-color: ${theme.menuBorder || "rgba(0, 0, 0, 0.1)"}`
|
||||
: `border-top-color: ${theme.menuBorder || "rgba(0, 0, 0, 0.1)"}`};
|
||||
${({ direction }) =>
|
||||
direction === Direction.UP ? "right: -1px" : "left: -1px"};
|
||||
}
|
||||
|
||||
&:after {
|
||||
border: 7px solid transparent;
|
||||
border-bottom-color: ${s("menuBackground")};
|
||||
${({ direction, theme }) =>
|
||||
direction === Direction.UP
|
||||
? `border-bottom-color: ${theme.menuBackground}`
|
||||
: `border-top-color: ${theme.menuBackground}`};
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
@@ -26,9 +26,9 @@ const HoverPreviewLink = React.forwardRef(function _HoverPreviewLink(
|
||||
) {
|
||||
return (
|
||||
<Preview as="a" href={url} target="_blank" rel="noopener noreferrer">
|
||||
<Flex column>
|
||||
<Flex column ref={ref}>
|
||||
{thumbnailUrl ? <Thumbnail src={thumbnailUrl} alt={""} /> : null}
|
||||
<Card ref={ref}>
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Flex column>
|
||||
<Title>{title}</Title>
|
||||
|
||||
Reference in New Issue
Block a user