fix(reply-quote-preview): keep floating OP previews at desktop width

This commit is contained in:
Tommaso Casaburi
2026-04-16 15:20:49 +07:00
parent b288c62c06
commit f6ce2ba018
5 changed files with 86 additions and 5 deletions
@@ -4,6 +4,7 @@ import { createRoot, type Root } from 'react-dom/client';
import { MemoryRouter } from 'react-router-dom';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import ExternalNumberQuoteLink from '../external-number-quote-link';
import postStyles from '../../../views/post/post.module.css';
(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
const act = (React as { act?: (cb: () => void | Promise<void>) => void | Promise<void> }).act as (cb: () => void | Promise<void>) => void | Promise<void>;
@@ -146,4 +147,41 @@ describe('ExternalNumberQuoteLink', () => {
expect(document.body.querySelector('[data-testid="post-preview"]')?.textContent).toBe('cid-77');
});
it('uses the OP preview width class for resolved external OP previews', async () => {
testState.resolveExternalQuoteTargetMock.mockResolvedValue({
boardPath: 'fit',
cid: 'cid-77',
comment: { cid: 'cid-77' },
isUnavailable: false,
route: '/fit/thread/cid-77',
communityAddress: 'fit',
});
await act(async () => {
root.render(
createElement(
MemoryRouter,
{},
createElement(ExternalNumberQuoteLink, {
reference: {
boardIdentifier: 'fit',
kind: 'cross-board',
number: 77,
raw: '>>>/fit/77',
},
}),
),
);
});
await act(async () => {
container.querySelector('a')?.dispatchEvent(new MouseEvent('mouseover', { bubbles: true }));
await Promise.resolve();
await new Promise((resolve) => setTimeout(resolve, 0));
});
const preview = document.body.querySelector(`.${postStyles.replyQuotePreview}`);
expect(preview?.classList.contains(postStyles.replyQuotePreviewOp)).toBe(true);
});
});
@@ -260,6 +260,10 @@ const ExternalNumberQuoteLink = ({ reference }: ExternalNumberQuoteLinkProps) =>
) : (
<div className={`${styles.externalQuotePreviewState} ${styles.externalQuotePreviewError}`}>{previewState.message}</div>
);
const previewClassName =
previewState.kind === 'resolved' && previewState.target.comment && !previewState.target.comment.parentCid
? `${postStyles.replyQuotePreview} ${postStyles.replyQuotePreviewOp}`
: postStyles.replyQuotePreview;
return (
<>
@@ -280,7 +284,7 @@ const ExternalNumberQuoteLink = ({ reference }: ExternalNumberQuoteLinkProps) =>
previewContent &&
createPortal(
<div
className={postStyles.replyQuotePreview}
className={previewClassName}
data-thread-scroll-preview='true'
ref={previewRef}
style={{ left: previewPosition.left, position: 'fixed', top: previewPosition.top, zIndex: 1000 }}
@@ -401,6 +401,36 @@ describe('ReplyQuotePreview', () => {
outOfView.remove();
});
it('uses the OP preview width class for floating desktop OP quotelinks', async () => {
const outOfView = appendReplyElement({ cid: 'thread-cid', inViewport: false, isThreadCard: true });
await renderPreview({
isOP: true,
isQuotelinkReply: true,
quotelinkReply: {
cid: 'thread-cid',
number: 1,
communityAddress: 'music-posting.eth',
},
});
const link = queryAnchorByText('>>1 (OP)');
expect(link).toBeTruthy();
await act(async () => {
link?.dispatchEvent(new MouseEvent('mouseover', { bubbles: true }));
});
const preview = document.querySelector(`.${styles.replyQuotePreview}`);
expect(preview?.classList.contains(styles.replyQuotePreviewOp)).toBe(true);
await act(async () => {
link?.dispatchEvent(new MouseEvent('mouseout', { bubbles: true }));
});
outOfView.remove();
});
it('marks quotelinks as your own via the direct account comment lookup before author fallback', async () => {
testState.account = {
id: 'account-1',
@@ -24,6 +24,9 @@ interface ReplyQuotePreviewProps {
showTrailingBreak?: boolean;
}
const getQuotePreviewClassName = (previewPost?: Comment) =>
!previewPost?.parentCid ? `${styles.replyQuotePreview} ${styles.replyQuotePreviewOp}` : styles.replyQuotePreview;
const handleQuoteHover = (cid: string, onElementOutOfView: () => void) => {
const targetElements = document.querySelectorAll(`[data-cid="${cid}"]`);
const isOpElement = (element: HTMLElement) => element.getAttribute('data-post-cid') === cid;
@@ -209,7 +212,7 @@ const DesktopQuotePreview = ({
{hoveredCid === normalizedBacklinkReply?.cid &&
outOfViewCid === normalizedBacklinkReply?.cid &&
createPortal(
<div className={styles.replyQuotePreview} data-thread-scroll-preview='true' ref={refs.setFloating} style={floatingStyles}>
<div className={getQuotePreviewClassName(normalizedBacklinkReply)} data-thread-scroll-preview='true' ref={refs.setFloating} style={floatingStyles}>
<Post post={normalizedBacklinkReply} showReplies={false} />
</div>,
document.body,
@@ -265,7 +268,7 @@ const DesktopQuotePreview = ({
{showTrailingBreak && <br />}
{shouldShowQuotelinkPreview &&
createPortal(
<div className={styles.replyQuotePreview} data-thread-scroll-preview='true' ref={refs.setFloating} style={floatingStyles}>
<div className={getQuotePreviewClassName(normalizedQuotelinkReply)} data-thread-scroll-preview='true' ref={refs.setFloating} style={floatingStyles}>
<Post post={normalizedQuotelinkReply} showReplies={false} />
</div>,
document.body,
@@ -373,7 +376,7 @@ const MobileQuotePreview = ({
{hoveredCid === normalizedBacklinkReply?.cid &&
outOfViewCid === normalizedBacklinkReply?.cid &&
createPortal(
<div className={styles.replyQuotePreview} data-thread-scroll-preview='true' ref={refs.setFloating} style={floatingStyles}>
<div className={getQuotePreviewClassName(normalizedBacklinkReply)} data-thread-scroll-preview='true' ref={refs.setFloating} style={floatingStyles}>
<Post post={normalizedBacklinkReply} showReplies={false} />
</div>,
document.body,
@@ -427,7 +430,7 @@ const MobileQuotePreview = ({
{showTrailingBreak && <br />}
{shouldShowQuotelinkPreview &&
createPortal(
<div className={styles.replyQuotePreview} data-thread-scroll-preview='true' ref={refs.setFloating} style={floatingStyles}>
<div className={getQuotePreviewClassName(normalizedQuotelinkReply)} data-thread-scroll-preview='true' ref={refs.setFloating} style={floatingStyles}>
<Post post={normalizedQuotelinkReply} showReplies={false} />
</div>,
document.body,
+6
View File
@@ -672,6 +672,8 @@
border: var(--quote-preview-border);
border-right: var(--quote-preview-border-right);
border-bottom: var(--quote-preview-border-bottom);
box-sizing: border-box;
max-width: calc(100vw - 20px);
}
.postDesktop .replyQuotePreviewSpacer {
@@ -724,6 +726,10 @@
padding-left: 2px;
}
.replyQuotePreviewOp {
width: min(520px, calc(100vw - 20px));
}
.postMobile {
display: none;
}