Skip to content

Commit 13f96ef

Browse files
authored
fix: Modify the popup location of ContextMenu/MenuItems (#289)
* Rollback to class component with MenuItems
1 parent e429dac commit 13f96ef

File tree

11 files changed

+174
-94
lines changed

11 files changed

+174
-94
lines changed

src/smart-components/ChannelList/components/ChannelPreviewAction.jsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import LeaveChannelModal from './LeaveChannel';
1313

1414
export default function ChannelPreviewAction({ disabled, onLeaveChannel }) {
1515
const parentRef = useRef(null);
16+
const parentContainerRef = useRef(null);
1617
const [showModal, setShowModal] = useState(false);
1718
const { stringSet } = useContext(LocalizationContext);
1819

@@ -23,6 +24,7 @@ export default function ChannelPreviewAction({ disabled, onLeaveChannel }) {
2324
onKeyDown={(e) => { e.stopPropagation(); }}
2425
tabIndex={0}
2526
onClick={(e) => { e.stopPropagation(); }}
27+
ref={parentContainerRef}
2628
>
2729
<ContextMenu
2830
menuTrigger={(toggleDropdown) => (
@@ -43,7 +45,7 @@ export default function ChannelPreviewAction({ disabled, onLeaveChannel }) {
4345
menuItems={(closeDropdown) => (
4446
<MenuItems
4547
parentRef={parentRef}
46-
parentContainRef={parentRef}
48+
parentContainRef={parentContainerRef}
4749
closeDropdown={closeDropdown}
4850
>
4951
<MenuItem

src/ui/ContextMenu/MenuItems.tsx

Lines changed: 102 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -1,112 +1,122 @@
1-
import React, { ReactElement, RefObject, useEffect, useRef, useState } from 'react';
1+
import React, { ReactElement } from 'react';
22
import { createPortal } from 'react-dom';
33

4-
type MenuStyle = { left: number, top: number };
5-
export interface MenuItemsProps {
6-
className?: string | Array<string>;
7-
children?: ReactElement;
8-
style?: { [key: string]: string };
9-
parentRef: RefObject<HTMLDivElement>;
10-
parentContainRef?: RefObject<HTMLDivElement>;
4+
interface MenuItemsProps {
5+
style?: Record<string, string>;
116
openLeft?: boolean;
7+
children: React.ReactElement;
8+
parentRef: React.RefObject<HTMLDivElement>;
9+
parentContainRef: React.RefObject<HTMLDivElement>;
1210
closeDropdown: () => void;
1311
}
1412

15-
const MenuItems = ({
16-
className,
17-
children,
18-
style = {},
19-
parentRef,
20-
parentContainRef,
21-
openLeft = false,
22-
closeDropdown,
23-
}: MenuItemsProps): ReactElement => {
24-
const menuRef = useRef(null);
25-
const [menuStyle, setMenuStyle] = useState<MenuStyle>({ left: 0, top: 0 });
13+
type MenuStyleType = { top?: number, left?: number };
14+
interface MenuItemsState {
15+
menuStyle: MenuStyleType;
16+
handleClickOutside: (e: MouseEvent) => void;
17+
}
2618

27-
/* showParent & hideParent */
28-
useEffect(() => {
29-
if (parentContainRef && parentContainRef?.current) {
30-
parentContainRef.current.classList.add('sendbird-reactions--pressed');
31-
}
32-
return () => {
33-
if (parentContainRef && parentContainRef?.current) {
34-
parentContainRef.current.classList.remove('sendbird-reactions--pressed');
35-
}
19+
export default class MenuItems extends React.Component<MenuItemsProps, MenuItemsState> {
20+
constructor(props: MenuItemsProps) {
21+
super(props);
22+
this.state = {
23+
menuStyle: {},
24+
handleClickOutside: () => {/* noop */ },
3625
};
37-
}, []);
26+
}
27+
menuRef: React.RefObject<HTMLUListElement> = React.createRef();
28+
29+
componentDidMount(): void {
30+
this.setupEvents();
31+
this.getMenuPosition();
32+
}
33+
34+
componentWillUnmount(): void {
35+
this.cleanUpEvents();
36+
}
3837

39-
/* setupEvents & cleanupEvents */
40-
useEffect(() => {
38+
setupEvents = (): void => {
39+
const { closeDropdown } = this.props;
40+
const { menuRef } = this;
4141
const handleClickOutside = (event) => {
42-
if (menuRef?.current && !menuRef?.current.contains?.(event.target)) {
43-
closeDropdown();
42+
if (menuRef?.current && !menuRef?.current?.contains?.(event.target)) {
43+
closeDropdown?.();
4444
}
4545
};
46+
this.setState({
47+
handleClickOutside,
48+
});
49+
4650
document.addEventListener('mousedown', handleClickOutside);
47-
return () => {
48-
document.removeEventListener('mousedown', handleClickOutside);
49-
};
50-
}, []);
51+
}
52+
53+
cleanUpEvents = (): void => {
54+
const {
55+
handleClickOutside,
56+
} = this.state;
57+
document.removeEventListener('mousedown', handleClickOutside);
58+
}
5159

52-
/* getMenuPosition */
53-
useEffect(() => {
54-
const parentRect = parentRef?.current?.getBoundingClientRect();
55-
const x = parentRect.x || parentRect.left;
56-
const y = parentRect.y || parentRect.top;
60+
getMenuPosition = (): MenuStyleType => {
61+
const { parentRef, openLeft } = this.props;
62+
const parentRect = parentRef?.current?.getBoundingClientRect?.();
63+
const x = parentRect?.x || parentRect?.left || 0;
64+
const y = parentRect?.y || parentRect?.top || 0;
5765
const menuStyle = {
5866
top: y,
5967
left: x,
6068
};
61-
if (!menuRef?.current) {
62-
setMenuStyle(menuStyle);
63-
} else {
64-
const { innerWidth, innerHeight } = window;
65-
const rect = menuRef.current.getBoundingClientRect();
66-
if (y + rect.height > innerHeight) {
67-
menuStyle.top -= rect.height;
68-
}
69-
if (x + rect.width > innerWidth && !openLeft) {
70-
menuStyle.left -= rect.width;
71-
}
72-
if (menuStyle.top < 0) {
73-
menuStyle.top = rect.height < innerHeight ? (innerHeight - rect.height) / 2 : 0;
74-
}
75-
if (menuStyle.left < 0) {
76-
menuStyle.left = rect.width < innerWidth ? (innerWidth - rect.width) / 2 : 0;
77-
}
78-
menuStyle.top += 32;
79-
if (openLeft) {
80-
const padding = Number.isNaN(rect.width - 30)
81-
? 108 // default
82-
: rect.width - 30;
83-
menuStyle.left -= padding;
84-
}
85-
setMenuStyle(menuStyle);
69+
if (!this.menuRef.current) return menuStyle;
70+
const { innerWidth, innerHeight } = window;
71+
const rect = this.menuRef.current.getBoundingClientRect();
72+
if (y + rect.height > innerHeight) {
73+
menuStyle.top -= rect.height;
8674
}
87-
}, []);
88-
89-
return createPortal(
90-
(
91-
<div className={Array.isArray(className) ? className.join(' ') : className}>
92-
<div className="sendbird-dropdown__menu-backdrop" />
93-
<ul
94-
className="sendbird-dropdown__menu"
95-
ref={menuRef}
96-
style={{
97-
display: 'inline-block',
98-
position: 'fixed',
99-
left: `${Math.round(menuStyle.left)}px`,
100-
top: `${Math.round(menuStyle.top)}px`,
101-
...style,
102-
}}
103-
>
104-
{children}
105-
</ul>
106-
</div>
107-
),
108-
document.getElementById('sendbird-dropdown-portal')
109-
);
110-
};
75+
if (x + rect.width > innerWidth && !openLeft) {
76+
menuStyle.left -= rect.width;
77+
}
78+
if (menuStyle.top < 0) {
79+
menuStyle.top = rect.height < innerHeight ? (innerHeight - rect.height) / 2 : 0;
80+
}
81+
if (menuStyle.left < 0) {
82+
menuStyle.left = rect.width < innerWidth ? (innerWidth - rect.width) / 2 : 0;
83+
}
84+
menuStyle.top += 32;
85+
if (openLeft) {
86+
const padding = Number.isNaN(rect.width - 30)
87+
? 108 // default
88+
: rect.width - 30;
89+
menuStyle.left -= padding;
90+
}
91+
this.setState({ menuStyle })
92+
return menuStyle;
93+
}
11194

112-
export default MenuItems;
95+
render(): ReactElement {
96+
const { menuStyle } = this.state;
97+
const { children, style } = this.props;
98+
return (
99+
createPortal(
100+
(
101+
<>
102+
<div className="sendbird-dropdown__menu-backdrop" />
103+
<ul
104+
className="sendbird-dropdown__menu"
105+
ref={this.menuRef}
106+
style={{
107+
display: 'inline-block',
108+
position: 'fixed',
109+
left: `${Math.round(menuStyle.left)}px`,
110+
top: `${Math.round(menuStyle.top)}px`,
111+
...style,
112+
}}
113+
>
114+
{children}
115+
</ul>
116+
</>
117+
),
118+
document.getElementById('sendbird-dropdown-portal'),
119+
)
120+
);
121+
}
122+
}

src/ui/ContextMenu/index.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,10 @@ export default function ContextMenu({
6262
}: ContextMenuProps): ReactElement {
6363
const [showMenu, setShowMenu] = useState(false);
6464
return (
65-
<div className="sendbird-context-menu">
65+
<div
66+
className="sendbird-context-menu"
67+
style={{ display: 'inline' }}
68+
>
6669
{menuTrigger(() => setShowMenu(!showMenu))}
6770
{showMenu && menuItems(() => setShowMenu(false))}
6871
</div>

src/ui/MessageContent/__tests__/__snapshots__/MessageContent.spec.js.snap

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ exports[`MessageContent should do a snapshot test of the MessageContent DOM 1`]
1111
>
1212
<div
1313
className="sendbird-context-menu"
14+
style={
15+
Object {
16+
"display": "inline",
17+
}
18+
}
1419
>
1520
<div
1621
className="sendbird-message-content__left__avatar sendbird-avatar"
@@ -102,6 +107,11 @@ exports[`MessageContent should do a snapshot test of the MessageContent DOM 1`]
102107
>
103108
<div
104109
className="sendbird-context-menu"
110+
style={
111+
Object {
112+
"display": "inline",
113+
}
114+
}
105115
>
106116
<button
107117
className="sendbird-message-item-menu__trigger sendbird-iconbutton "

src/ui/MessageItemMenu/__tests__/__snapshots__/MessageItemMenu.spec.js.snap

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ exports[`MessageItemMenu should do a snapshot test of the MessageItemMenu DOM 1`
66
>
77
<div
88
className="sendbird-context-menu"
9+
style={
10+
Object {
11+
"display": "inline",
12+
}
13+
}
914
>
1015
<button
1116
className="sendbird-message-item-menu__trigger sendbird-iconbutton "

src/ui/MessageItemReactionMenu/__tests__/__snapshots__/MessageItemReactionMenu.spec.js.snap

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ exports[`MessageItemReactionMenu should do a snapshot test of the MessageItemRea
66
>
77
<div
88
className="sendbird-context-menu"
9+
style={
10+
Object {
11+
"display": "inline",
12+
}
13+
}
914
>
1015
<button
1116
className="sendbird-message-item-reaction-menu__trigger sendbird-iconbutton "

src/ui/OpenchannelFileMessage/__tests__/__snapshots__/OpenchannelFileMessage.spec.js.snap

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ exports[`OpenchannelFileMessage should do a snapshot test of the OpenchannelFile
99
>
1010
<div
1111
className="sendbird-context-menu"
12+
style={
13+
Object {
14+
"display": "inline",
15+
}
16+
}
1217
>
1318
<div
1419
className="sendbird-openchannel-file-message__left__avatar sendbird-avatar"
@@ -119,6 +124,11 @@ exports[`OpenchannelFileMessage should do a snapshot test of the OpenchannelFile
119124
>
120125
<div
121126
className="sendbird-context-menu"
127+
style={
128+
Object {
129+
"display": "inline",
130+
}
131+
}
122132
>
123133
<button
124134
className="sendbird-openchannel-file-message__context-menu__icon sendbird-iconbutton "

src/ui/OpenchannelOGMessage/__tests__/__snapshots__/OpenchannelOGMessage.spec.js.snap

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ exports[`OpenchannelOGMessage should do a snapshot test of the OpenchannelOGMess
1212
>
1313
<div
1414
className="sendbird-context-menu"
15+
style={
16+
Object {
17+
"display": "inline",
18+
}
19+
}
1520
>
1621
<div
1722
className="sendbird-openchannel-og-message__top__left__avatar sendbird-avatar"
@@ -117,6 +122,11 @@ exports[`OpenchannelOGMessage should do a snapshot test of the OpenchannelOGMess
117122
>
118123
<div
119124
className="sendbird-context-menu"
125+
style={
126+
Object {
127+
"display": "inline",
128+
}
129+
}
120130
>
121131
<button
122132
className="sendbird-openchannel-og-message__top__context-menu--icon sendbird-iconbutton "

src/ui/OpenchannelThumbnailMessage/__tests__/__snapshots__/OpenchannelThumbnailMessage.spec.js.snap

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ exports[`OpenchannelThumbnailMessage should do a snapshot test of the Openchanne
99
>
1010
<div
1111
className="sendbird-context-menu"
12+
style={
13+
Object {
14+
"display": "inline",
15+
}
16+
}
1217
>
1318
<div
1419
className="sendbird-openchannel-thumbnail-message__left__avatar sendbird-avatar"
@@ -168,6 +173,11 @@ exports[`OpenchannelThumbnailMessage should do a snapshot test of the Openchanne
168173
>
169174
<div
170175
className="sendbird-context-menu"
176+
style={
177+
Object {
178+
"display": "inline",
179+
}
180+
}
171181
>
172182
<button
173183
className="sendbird-openchannel-thumbnail-message__context-menu--icon sendbird-iconbutton "

src/ui/OpenchannelUserMessage/__tests__/__snapshots__/OpenchannelUserMessage.spec.js.snap

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ exports[`OpenchannelUserMessage should do a snapshot test of the OpenchannelUser
99
>
1010
<div
1111
className="sendbird-context-menu"
12+
style={
13+
Object {
14+
"display": "inline",
15+
}
16+
}
1217
>
1318
<div
1419
className="sendbird-openchannel-user-message__left__avatar sendbird-avatar"
@@ -95,6 +100,11 @@ exports[`OpenchannelUserMessage should do a snapshot test of the OpenchannelUser
95100
>
96101
<div
97102
className="sendbird-context-menu"
103+
style={
104+
Object {
105+
"display": "inline",
106+
}
107+
}
98108
>
99109
<button
100110
className="sendbird-openchannel-user-message__context-menu--icon sendbird-iconbutton "

0 commit comments

Comments
 (0)