Fix react picker positioning in narrow window

This commit is contained in:
Jamie
2025-11-18 15:45:51 -08:00
committed by GitHub
parent 05c3966dd6
commit 745472cc4b
3 changed files with 30 additions and 52 deletions

View File

@@ -6,12 +6,6 @@ import type { LocalizerType } from '../../types/I18N.std.js';
import { AxoMenuBuilder } from '../../axo/AxoMenuBuilder.dom.js'; import { AxoMenuBuilder } from '../../axo/AxoMenuBuilder.dom.js';
import { isPinnedMessagesEnabled } from '../../util/isPinnedMessagesEnabled.std.js'; import { isPinnedMessagesEnabled } from '../../util/isPinnedMessagesEnabled.std.js';
export type ContextMenuTriggerType = {
handleContextClick: (
event: React.MouseEvent<HTMLDivElement> | MouseEvent
) => void;
};
type MessageContextMenuProps = Readonly<{ type MessageContextMenuProps = Readonly<{
i18n: LocalizerType; i18n: LocalizerType;
renderer: AxoMenuBuilder.Renderer; renderer: AxoMenuBuilder.Renderer;

View File

@@ -3,14 +3,8 @@
import classNames from 'classnames'; import classNames from 'classnames';
import lodash from 'lodash'; import lodash from 'lodash';
import React, { import React, { useCallback, useEffect, useMemo, useState } from 'react';
useCallback, import type { ReactNode } from 'react';
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import type { ReactNode, Ref } from 'react';
import { createPortal } from 'react-dom'; import { createPortal } from 'react-dom';
import { Manager, Popper, Reference } from 'react-popper'; import { Manager, Popper, Reference } from 'react-popper';
import type { PreventOverflowModifier } from '@popperjs/core/lib/modifiers/preventOverflow.js'; import type { PreventOverflowModifier } from '@popperjs/core/lib/modifiers/preventOverflow.js';
@@ -39,10 +33,7 @@ import type {
ForwardMessagesPayload, ForwardMessagesPayload,
} from '../../state/ducks/globalModals.preload.js'; } from '../../state/ducks/globalModals.preload.js';
import { useScrollerLock } from '../../hooks/useScrollLock.dom.js'; import { useScrollerLock } from '../../hooks/useScrollLock.dom.js';
import { import { MessageContextMenu } from './MessageContextMenu.dom.js';
type ContextMenuTriggerType,
MessageContextMenu,
} from './MessageContextMenu.dom.js';
import { ForwardMessagesModalType } from '../ForwardMessagesModal.dom.js'; import { ForwardMessagesModalType } from '../ForwardMessagesModal.dom.js';
import { useGroupedAndOrderedReactions } from '../../util/groupAndOrderReactions.dom.js'; import { useGroupedAndOrderedReactions } from '../../util/groupAndOrderReactions.dom.js';
import { isNotNil } from '../../util/isNotNil.std.js'; import { isNotNil } from '../../util/isNotNil.std.js';
@@ -108,7 +99,6 @@ export type Props = PropsData &
export function TimelineMessage(props: Props): JSX.Element { export function TimelineMessage(props: Props): JSX.Element {
const { const {
attachments, attachments,
author,
canDownload, canDownload,
canCopy, canCopy,
canEditMessage, canEditMessage,
@@ -149,7 +139,6 @@ export function TimelineMessage(props: Props): JSX.Element {
const [reactionPickerRoot, setReactionPickerRoot] = useState< const [reactionPickerRoot, setReactionPickerRoot] = useState<
HTMLDivElement | undefined HTMLDivElement | undefined
>(undefined); >(undefined);
const menuTriggerRef = useRef<ContextMenuTriggerType | null>(null);
const [pinMessageDialogOpen, setPinMessageDialogOpen] = useState(false); const [pinMessageDialogOpen, setPinMessageDialogOpen] = useState(false);
const isWindowWidthNotNarrow = const isWindowWidthNotNarrow =
@@ -172,10 +161,6 @@ export function TimelineMessage(props: Props): JSX.Element {
}; };
}, [containerElementRef]); }, [containerElementRef]);
// This id is what connects our triple-dot click with our associated pop-up menu.
// It needs to be unique.
const triggerId = String(id || `${author.id}-${timestamp}`);
const toggleReactionPicker = useCallback( const toggleReactionPicker = useCallback(
(onlyRemove = false): void => { (onlyRemove = false): void => {
if (reactionPickerRoot) { if (reactionPickerRoot) {
@@ -392,10 +377,8 @@ export function TimelineMessage(props: Props): JSX.Element {
<Manager> <Manager>
<MessageMenu <MessageMenu
i18n={i18n} i18n={i18n}
triggerId={triggerId}
isWindowWidthNotNarrow={isWindowWidthNotNarrow} isWindowWidthNotNarrow={isWindowWidthNotNarrow}
direction={direction} direction={direction}
menuTriggerRef={menuTriggerRef}
onDownload={handleDownload} onDownload={handleDownload}
onReplyToMessage={canReply ? handleReplyToMessage : null} onReplyToMessage={canReply ? handleReplyToMessage : null}
onReact={canReact ? handleReact : null} onReact={canReact ? handleReact : null}
@@ -433,10 +416,8 @@ export function TimelineMessage(props: Props): JSX.Element {
); );
}, [ }, [
i18n, i18n,
triggerId,
isWindowWidthNotNarrow, isWindowWidthNotNarrow,
direction, direction,
menuTriggerRef,
canReply, canReply,
canReact, canReact,
handleDownload, handleDownload,
@@ -484,9 +465,7 @@ export function TimelineMessage(props: Props): JSX.Element {
type MessageMenuProps = { type MessageMenuProps = {
i18n: LocalizerType; i18n: LocalizerType;
triggerId: string;
isWindowWidthNotNarrow: boolean; isWindowWidthNotNarrow: boolean;
menuTriggerRef: Ref<ContextMenuTriggerType>;
onDownload: (() => void) | null; onDownload: (() => void) | null;
onReplyToMessage: (() => void) | null; onReplyToMessage: (() => void) | null;
onReact: (() => void) | null; onReact: (() => void) | null;
@@ -595,9 +574,19 @@ function MessageMenu({
)} )}
</> </>
)} )}
{renderMessageContextMenu( <Reference>
{({ ref: popperRef }) => {
// Only attach the popper reference to the collapsed menu button if
// the reaction button is not visible (it is hidden when the
// timeline is narrow)
const maybePopperRef = !isWindowWidthNotNarrow
? popperRef
: undefined;
return renderMessageContextMenu(
'AxoDropdownMenu', 'AxoDropdownMenu',
<button <button
ref={maybePopperRef}
type="button" type="button"
aria-label={i18n('icu:messageContextMenuButton')} aria-label={i18n('icu:messageContextMenuButton')}
className={classNames( className={classNames(
@@ -609,7 +598,9 @@ function MessageMenu({
ev.stopPropagation(); ev.stopPropagation();
}} }}
/> />
)} );
}}
</Reference>
</div> </div>
); );
} }

View File

@@ -1843,13 +1843,6 @@
"updated": "2021-01-20T21:30:08.430Z", "updated": "2021-01-20T21:30:08.430Z",
"reasonDetail": "Doesn't touch the DOM." "reasonDetail": "Doesn't touch the DOM."
}, },
{
"rule": "React-useRef",
"path": "ts/components/conversation/TimelineMessage.dom.tsx",
"line": " const menuTriggerRef = useRef<ContextMenuTriggerType | null>(null);",
"reasonCategory": "usageTrusted",
"updated": "2023-12-08T20:28:57.595Z"
},
{ {
"rule": "React-useRef", "rule": "React-useRef",
"path": "ts/components/conversation/TypingBubble.dom.tsx", "path": "ts/components/conversation/TypingBubble.dom.tsx",