Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ const RECEIPT_PREVIEW_EDGE_MARGIN = 24;
const RECEIPT_PREVIEW_MIN_VISIBLE_HEIGHT = 160;

/**
* Anchors the preview's bottom-left corner to the right of the hovered thumbnail, so the preview grows upward
* from the row. If there isn't room on the right, it flips to the left of the thumbnail. The top is clamped to a
* viewport margin so a tall receipt near the top of the screen never runs off the top edge. Returns undefined
* when there is no anchor, so the caller keeps the static style.
* Anchors the preview's bottom-left corner beside the hovered thumbnail, so the preview grows upward from the
* row. It prefers the left of the thumbnail and only flips to the right when there isn't room on the left. The
* top is clamped to a viewport margin so a tall receipt near the top of the screen never runs off the top edge.
* Returns undefined when there is no anchor, so the caller keeps the static style.
*
* @param previewHeight Measured height of the preview. 0 means "not measured yet" — we can't bottom-align without
* it, so we fall back to aligning the top with the row (keeping a minimum slice on-screen) for the first frame.
Expand All @@ -28,9 +28,10 @@ function getAnchoredPreviewPosition(anchorPosition: AnchorPosition | undefined,
return undefined;
}

const leftOfThumbnail = anchorPosition.left - RECEIPT_PREVIEW_WIDTH - RECEIPT_PREVIEW_GAP;
const rightOfThumbnail = anchorPosition.left + anchorPosition.width + RECEIPT_PREVIEW_GAP;
const overflowsRight = windowWidth > 0 && rightOfThumbnail + RECEIPT_PREVIEW_WIDTH + RECEIPT_PREVIEW_EDGE_MARGIN > windowWidth;
const left = overflowsRight ? Math.max(RECEIPT_PREVIEW_EDGE_MARGIN, anchorPosition.left - RECEIPT_PREVIEW_WIDTH - RECEIPT_PREVIEW_GAP) : rightOfThumbnail;
const fitsLeft = leftOfThumbnail >= RECEIPT_PREVIEW_EDGE_MARGIN;
const left = fitsLeft ? leftOfThumbnail : rightOfThumbnail;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep fallback preview inside the viewport

When the thumbnail is close enough to the left edge that fitsLeft is false but the viewport is only slightly wider than the narrow breakpoint, this fallback can place the 380px preview off the right edge. For example, with windowWidth 801 and a thumbnail at left 400, leftOfThumbnail is 4 so we choose rightOfThumbnail 484, making the preview extend to 864 before the edge margin; the previous overflow check would have clamped it to the left instead. This affects wide layouts just above the mobile breakpoint and clips the hover preview.

Useful? React with 👍 / 👎.


// Before it's measured we can't bottom-align, so keep a minimum slice on-screen relative to the row top.
if (previewHeight <= 0) {
Expand Down
15 changes: 12 additions & 3 deletions tests/unit/components/ReceiptPreviewPositionTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,17 @@ describe('getAnchoredPreviewPosition', () => {
expect(getAnchoredPreviewPosition(undefined, WINDOW_WIDTH, WINDOW_HEIGHT)).toBeUndefined();
});

it('places the preview just to the right of the hovered thumbnail', () => {
it('places the preview just to the left of the hovered thumbnail when there is room', () => {
const anchor = {top: 300, left: 500, width: 68, height: 64};

const position = getAnchoredPreviewPosition(anchor, WINDOW_WIDTH, WINDOW_HEIGHT);

expect(position?.left).toBe(anchor.left - RECEIPT_PREVIEW_WIDTH - RECEIPT_PREVIEW_GAP);
expect(position?.left).toBeLessThan(anchor.left);
});

it('flips to the right of the thumbnail when there is not enough room on the left', () => {
// A thumbnail hugging the left edge leaves no room for the preview to its left.
const anchor = {top: 300, left: 120, width: 68, height: 64};

const position = getAnchoredPreviewPosition(anchor, WINDOW_WIDTH, WINDOW_HEIGHT);
Expand All @@ -27,8 +37,7 @@ describe('getAnchoredPreviewPosition', () => {
expect(getAnchoredPreviewPosition(anchor, WINDOW_WIDTH, WINDOW_HEIGHT)?.top).toBe(420);
});

it('flips to the left of the thumbnail when there is not enough room on the right', () => {
// A thumbnail hugging the right edge leaves no room for a 380px-wide preview to its right.
it('keeps the preview to the left of a thumbnail near the right edge', () => {
const anchor = {top: 300, left: WINDOW_WIDTH - 80, width: 68, height: 64};

const position = getAnchoredPreviewPosition(anchor, WINDOW_WIDTH, WINDOW_HEIGHT);
Expand Down
Loading