Skip to content

Commit

Permalink
fix: Message Menu is cut vertically if screen is too small (#30197)
Browse files Browse the repository at this point in the history
Co-authored-by: Guilherme Jun Grillo <48109548+guijun13@users.noreply.github.com>
  • Loading branch information
gabriellsh and guijun13 committed Sep 1, 2023
1 parent 2615de9 commit db26f8a
Show file tree
Hide file tree
Showing 7 changed files with 127 additions and 74 deletions.
5 changes: 5 additions & 0 deletions .changeset/serious-shrimps-try.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@rocket.chat/meteor": patch
---

fixed an issue with the positioning of the message menu
12 changes: 8 additions & 4 deletions apps/meteor/client/components/message/MessageToolboxHolder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { IMessage } from '@rocket.chat/core-typings';
import { MessageToolboxWrapper } from '@rocket.chat/fuselage';
import { useQuery } from '@tanstack/react-query';
import type { ReactElement } from 'react';
import React, { Suspense, lazy, memo, useRef } from 'react';
import React, { Suspense, lazy, memo, useRef, useState } from 'react';

import type { MessageActionContext } from '../../../app/ui-utils/client/lib/MessageAction';
import { useChat } from '../../views/room/contexts/ChatContext';
Expand All @@ -18,7 +18,10 @@ const MessageToolbox = lazy(() => import('./toolbox/MessageToolbox'));
const MessageToolboxHolder = ({ message, context }: MessageToolboxHolderProps): ReactElement => {
const ref = useRef(null);

const [visible] = useIsVisible(ref);
const [isVisible] = useIsVisible(ref);
const [kebabOpen, setKebabOpen] = useState(false);

const showToolbox = isVisible || kebabOpen;

const chat = useChat();

Expand All @@ -32,10 +35,11 @@ const MessageToolboxHolder = ({ message, context }: MessageToolboxHolderProps):
});

return (
<MessageToolboxWrapper ref={ref}>
{visible && depsQueryResult.isSuccess && depsQueryResult.data.room && (
<MessageToolboxWrapper ref={ref} visible={kebabOpen}>
{showToolbox && depsQueryResult.isSuccess && depsQueryResult.data.room && (
<Suspense fallback={null}>
<MessageToolbox
onChangeMenuVisibility={setKebabOpen}
message={message}
messageContext={context}
room={depsQueryResult.data.room}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,36 +1,6 @@
import { Tile } from '@rocket.chat/fuselage';
import { useMergedRefs, usePosition } from '@rocket.chat/fuselage-hooks';
import { Tile, PositionAnimated } from '@rocket.chat/fuselage';
import type { ReactNode, Ref, RefObject } from 'react';
import React, { useMemo, useRef, forwardRef } from 'react';

const getDropdownContainer = (descendant: HTMLElement | null) => {
for (let element = descendant ?? document.body; element !== document.body; element = element.parentElement ?? document.body) {
if (
getComputedStyle(element).transform !== 'none' ||
getComputedStyle(element).position === 'fixed' ||
getComputedStyle(element).willChange === 'transform'
) {
return element;
}
}

return document.body;
};

const useDropdownPosition = (reference: RefObject<HTMLElement>, target: RefObject<HTMLElement>) => {
const innerContainer = getDropdownContainer(reference.current);
const boundingRect = innerContainer.getBoundingClientRect();

const { style } = usePosition(reference, target, {
placement: 'bottom-end',
container: innerContainer,
});

const left = `${parseFloat(String(style?.left ?? '0')) - boundingRect.left}px`;
const top = `${parseFloat(String(style?.top ?? '0')) - boundingRect.top}px`;

return useMemo(() => ({ ...style, left, top }), [style, left, top]);
};
import React, { forwardRef } from 'react';

type DesktopToolboxDropdownProps = {
children: ReactNode;
Expand All @@ -41,15 +11,12 @@ const DesktopToolboxDropdown = forwardRef(function ToolboxDropdownDesktop(
{ reference, children }: DesktopToolboxDropdownProps,
ref: Ref<HTMLElement>,
) {
const targetRef = useRef<HTMLElement>(null);
const mergedRef = useMergedRefs(ref, targetRef);

const style = useDropdownPosition(reference, targetRef);

return (
<Tile is='ul' padding={0} paddingBlock={12} paddingInline={0} elevation='2' ref={mergedRef} style={style}>
{children}
</Tile>
<PositionAnimated anchor={reference} placement='bottom-end' visible='visible'>
<Tile is='ul' padding={0} paddingBlock={12} paddingInline={0} elevation='2' ref={ref}>
{children}
</Tile>
</PositionAnimated>
);
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Box, MessageToolboxItem, Option, OptionDivider, OptionTitle } from '@rocket.chat/fuselage';
import { MessageToolboxItem, Option, OptionDivider, OptionTitle } from '@rocket.chat/fuselage';
import { useTranslation } from '@rocket.chat/ui-contexts';
import type { ComponentProps, MouseEvent, MouseEventHandler, ReactElement } from 'react';
import React, { Fragment, useRef, useState } from 'react';
import React, { Fragment, useCallback, useRef, useState } from 'react';

import type { MessageActionConfig } from '../../../../app/ui-utils/client/lib/MessageAction';
import { useEmbeddedLayout } from '../../../hooks/useEmbeddedLayout';
Expand All @@ -12,6 +12,7 @@ type MessageActionConfigOption = Omit<MessageActionConfig, 'condition' | 'contex
};

type MessageActionMenuProps = {
onChangeMenuVisibility: (visible: boolean) => void;
options: MessageActionConfigOption[];
};

Expand All @@ -32,12 +33,20 @@ const getSectionOrder = (section: string): number => {
}
};

const MessageActionMenu = ({ options, ...props }: MessageActionMenuProps): ReactElement => {
const ref = useRef(null);
const MessageActionMenu = ({ options, onChangeMenuVisibility, ...props }: MessageActionMenuProps): ReactElement => {
const buttonRef = useRef<HTMLButtonElement | null>(null);
const t = useTranslation();
const [visible, setVisible] = useState(false);
const isLayoutEmbedded = useEmbeddedLayout();

const handleChangeMenuVisibility = useCallback(
(visible: boolean): void => {
setVisible(visible);
onChangeMenuVisibility(visible);
},
[onChangeMenuVisibility],
);

const groupOptions = options.reduce((acc, option) => {
const { type = '' } = option;

Expand All @@ -62,20 +71,22 @@ const MessageActionMenu = ({ options, ...props }: MessageActionMenuProps): React
return acc;
}, [] as unknown as [section: string, options: Array<MessageActionConfigOption>][]);

const handleClose = useCallback(() => {
handleChangeMenuVisibility(false);
}, [handleChangeMenuVisibility]);
return (
<>
<MessageToolboxItem
ref={ref}
ref={buttonRef}
icon='kebab'
onClick={(): void => setVisible(!visible)}
onClick={(): void => handleChangeMenuVisibility(!visible)}
data-qa-id='menu'
data-qa-type='message-action-menu'
title={t('More')}
/>
{visible && (
<>
<Box position='fixed' inset={0} onClick={(): void => setVisible(!visible)} />
<ToolboxDropdown reference={ref} {...props}>
<ToolboxDropdown handleClose={handleClose} reference={buttonRef} {...props}>
{groupOptions.map(([section, options], index, arr) => (
<Fragment key={index}>
{section === 'apps' && <OptionTitle>Apps</OptionTitle>}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,16 @@ type MessageToolboxProps = {
messageContext?: MessageActionContext;
room: IRoom;
subscription?: ISubscription;
onChangeMenuVisibility: (visible: boolean) => void;
};

const MessageToolbox = ({ message, messageContext, room, subscription }: MessageToolboxProps): ReactElement | null => {
const MessageToolbox = ({
message,
messageContext,
room,
subscription,
onChangeMenuVisibility,
}: MessageToolboxProps): ReactElement | null => {
const t = useTranslation();
const user = useUser() ?? undefined;
const settings = useSettings();
Expand Down Expand Up @@ -109,6 +116,7 @@ const MessageToolbox = ({ message, messageContext, room, subscription }: Message
))}
{actionsQueryResult.isSuccess && actionsQueryResult.data.menu.length > 0 && (
<MessageActionMenu
onChangeMenuVisibility={onChangeMenuVisibility}
options={[...actionsQueryResult.data?.menu, ...(actionButtonApps.data ?? [])].filter(Boolean).map((action) => ({
...action,
action: (e): void => action.action(e, { message, tabbar: toolbox, room, chat, autoTranslateOptions }),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { Box } from '@rocket.chat/fuselage';
import { useOutsideClick } from '@rocket.chat/fuselage-hooks';
import { useLayout } from '@rocket.chat/ui-contexts';
import type { ReactNode, ReactElement } from 'react';
import React, { useRef } from 'react';
Expand All @@ -8,20 +10,27 @@ import MobileToolboxDropdown from './MobileToolboxDropdown';
type ToolboxDropdownProps<R> = {
children: ReactNode;
reference: React.RefObject<R>;
handleClose: () => void;
};

const ToolboxDropdown = <TReferenceElement extends HTMLElement>({
children,
handleClose,
reference,
}: ToolboxDropdownProps<TReferenceElement>): ReactElement => {
const { isMobile } = useLayout();
const target = useRef<HTMLButtonElement>(null);
const boxRef = useRef<HTMLDivElement>(null);

const Dropdown = isMobile ? MobileToolboxDropdown : DesktopToolboxDropdown;

useOutsideClick([boxRef], handleClose);

return (
<Dropdown ref={target} reference={reference}>
{children}
<Box w='full' h='full' ref={boxRef}>
{children}
</Box>
</Dropdown>
);
};
Expand Down

0 comments on commit db26f8a

Please sign in to comment.