Fix tooltips within context menu portals being unreliable (#31129)

* Fix tooltips within context menu portals being unreliable

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Update snapshots

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Add test

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
Michael Telatynski 2025-10-31 09:00:01 +00:00 committed by GitHub
parent bbb16f7ea9
commit c7f07f4c29
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 97 additions and 45 deletions

View File

@ -12,6 +12,7 @@ import React, { type JSX, type CSSProperties, type RefObject, type SyntheticEven
import ReactDOM from "react-dom"; import ReactDOM from "react-dom";
import classNames from "classnames"; import classNames from "classnames";
import FocusLock from "react-focus-lock"; import FocusLock from "react-focus-lock";
import { TooltipProvider } from "@vector-im/compound-web";
import { type Writeable } from "../../@types/common"; import { type Writeable } from "../../@types/common";
import UIStore from "../../stores/UIStore"; import UIStore from "../../stores/UIStore";
@ -425,15 +426,17 @@ export default class ContextMenu extends React.PureComponent<React.PropsWithChil
onContextMenu={this.onContextMenuPreventBubbling} onContextMenu={this.onContextMenuPreventBubbling}
> >
{background} {background}
<div <TooltipProvider>
className={menuClasses} <div
style={menuStyle} className={menuClasses}
ref={this.collectContextMenuRect} style={menuStyle}
role={managed ? "menu" : undefined} ref={this.collectContextMenuRect}
{...divProps} role={managed ? "menu" : undefined}
> {...divProps}
{body} >
</div> {body}
</div>
</TooltipProvider>
</div> </div>
)} )}
</RovingTabIndexProvider> </RovingTabIndexProvider>

View File

@ -212,38 +212,41 @@ export function ReadReceiptPerson({
onAfterClick, onAfterClick,
}: ReadReceiptPersonProps): JSX.Element { }: ReadReceiptPersonProps): JSX.Element {
return ( return (
<Tooltip description={roomMember?.rawDisplayName ?? userId} caption={userId} placement="top"> <Tooltip
<div> description={roomMember?.rawDisplayName ?? userId}
<MenuItem caption={userId}
className="mx_ReadReceiptGroup_person" placement="top"
onClick={() => { isTriggerInteractive={false}
dis.dispatch({ >
action: Action.ViewUser, <MenuItem
// XXX: We should be using a real member object and not assuming what the receiver wants. className="mx_ReadReceiptGroup_person"
// The ViewUser action leads to the RightPanelStore, and RightPanelStoreIPanelState defines the onClick={() => {
// member property of IRightPanelCardState as `RoomMember | User`, so were fine for now, but we dis.dispatch({
// should definitely clean this up later action: Action.ViewUser,
member: roomMember ?? ({ userId } as User), // XXX: We should be using a real member object and not assuming what the receiver wants.
push: false, // The ViewUser action leads to the RightPanelStore, and RightPanelStoreIPanelState defines the
}); // member property of IRightPanelCardState as `RoomMember | User`, so were fine for now, but we
onAfterClick?.(); // should definitely clean this up later
}} member: roomMember ?? ({ userId } as User),
> push: false,
<MemberAvatar });
member={roomMember} onAfterClick?.();
fallbackUserId={userId} }}
size="24px" >
aria-hidden="true" <MemberAvatar
aria-live="off" member={roomMember}
resizeMethod="crop" fallbackUserId={userId}
hideTitle size="24px"
/> aria-hidden="true"
<div className="mx_ReadReceiptGroup_name"> aria-live="off"
<p>{roomMember?.name ?? userId}</p> resizeMethod="crop"
<p className="mx_ReadReceiptGroup_secondary">{formatDate(new Date(ts), isTwelveHour)}</p> hideTitle
</div> />
</MenuItem> <div className="mx_ReadReceiptGroup_name">
</div> <p>{roomMember?.name ?? userId}</p>
<p className="mx_ReadReceiptGroup_secondary">{formatDate(new Date(ts), isTwelveHour)}</p>
</div>
</MenuItem>
</Tooltip> </Tooltip>
); );
} }

View File

@ -142,5 +142,10 @@ describe("ReadReceiptGroup", () => {
}), }),
); );
}); });
it("should fall back to userId if roomMember unspecified", async () => {
const { container } = renderReadReceipt({ roomMember: null });
expect(container).toMatchSnapshot();
});
}); });
}); });

View File

@ -6,14 +6,14 @@ exports[`ReadReceiptGroup <ReadReceiptPerson /> should display a tooltip 1`] = `
data-floating-ui-focusable="" data-floating-ui-focusable=""
id="_r_6_" id="_r_6_"
role="tooltip" role="tooltip"
style="position: absolute; left: 0px; top: 0px; transform: translate(0px, 0px);" style="position: absolute; left: 0px; top: 0px; transform: translate(5px, -6px);"
tabindex="-1" tabindex="-1"
> >
<svg <svg
aria-hidden="true" aria-hidden="true"
class="_arrow_6ode6_33" class="_arrow_6ode6_33"
height="10" height="10"
style="position: absolute; pointer-events: none; top: 100%;" style="position: absolute; pointer-events: none; top: 100%; left: -1px;"
viewBox="0 0 10 10" viewBox="0 0 10 10"
width="10" width="10"
> >
@ -46,9 +46,50 @@ exports[`ReadReceiptGroup <ReadReceiptPerson /> should display a tooltip 1`] = `
</div> </div>
`; `;
exports[`ReadReceiptGroup <ReadReceiptPerson /> should fall back to userId if roomMember unspecified 1`] = `
<div>
<span
tabindex="0"
>
<div
class="mx_AccessibleButton mx_ReadReceiptGroup_person"
role="menuitem"
tabindex="-1"
>
<span
aria-hidden="true"
aria-live="off"
class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
data-color="3"
data-testid="avatar-img"
data-type="round"
role="presentation"
style="--cpd-avatar-size: 24px;"
>
a
</span>
<div
class="mx_ReadReceiptGroup_name"
>
<p>
@alice:example.org
</p>
<p
class="mx_ReadReceiptGroup_secondary"
>
==MOCK FORMATTED DATE==
</p>
</div>
</div>
</span>
</div>
`;
exports[`ReadReceiptGroup <ReadReceiptPerson /> should render 1`] = ` exports[`ReadReceiptGroup <ReadReceiptPerson /> should render 1`] = `
<div> <div>
<div> <span
tabindex="0"
>
<div <div
class="mx_AccessibleButton mx_ReadReceiptGroup_person" class="mx_AccessibleButton mx_ReadReceiptGroup_person"
role="menuitem" role="menuitem"
@ -88,6 +129,6 @@ exports[`ReadReceiptGroup <ReadReceiptPerson /> should render 1`] = `
</p> </p>
</div> </div>
</div> </div>
</div> </span>
</div> </div>
`; `;