diff --git a/app/soapbox/components/ui/emoji-selector/emoji-selector.tsx b/app/soapbox/components/ui/emoji-selector/emoji-selector.tsx index 55e98b048..a4485e79f 100644 --- a/app/soapbox/components/ui/emoji-selector/emoji-selector.tsx +++ b/app/soapbox/components/ui/emoji-selector/emoji-selector.tsx @@ -1,11 +1,10 @@ -import { Placement } from '@popperjs/core'; +import { shift, useFloating, Placement, offset, OffsetOptions } from '@floating-ui/react'; import clsx from 'clsx'; import React, { useEffect, useState } from 'react'; -import { usePopper } from 'react-popper'; import { Emoji as EmojiComponent, HStack, IconButton } from 'soapbox/components/ui'; import EmojiPickerDropdown from 'soapbox/features/emoji/components/emoji-picker-dropdown'; -import { useFeatures, useSoapboxConfig } from 'soapbox/hooks'; +import { useClickOutside, useFeatures, useSoapboxConfig } from 'soapbox/hooks'; import type { Emoji } from 'soapbox/features/emoji'; @@ -45,8 +44,7 @@ interface IEmojiSelector { placement?: Placement /** Whether the selector should be visible. */ visible?: boolean - /** X/Y offset of the floating picker. */ - offset?: [number, number] + offsetOptions?: OffsetOptions /** Whether to allow any emoji to be chosen. */ all?: boolean } @@ -58,7 +56,7 @@ const EmojiSelector: React.FC = ({ onReact, placement = 'top', visible = false, - offset = [-10, 0], + offsetOptions, all = true, }): JSX.Element => { const soapboxConfig = useSoapboxConfig(); @@ -66,36 +64,9 @@ const EmojiSelector: React.FC = ({ const [expanded, setExpanded] = useState(false); - // `useRef` won't trigger a re-render, while `useState` does. - // https://popper.js.org/react-popper/v2/ - const [popperElement, setPopperElement] = useState(null); - - const handleClickOutside = (event: MouseEvent) => { - if ([referenceElement, popperElement, document.querySelector('em-emoji-picker')].some(el => el?.contains(event.target as Node))) { - return; - } - - if (document.querySelector('em-emoji-picker')) { - event.preventDefault(); - event.stopPropagation(); - return setExpanded(false); - } - - if (onClose) { - onClose(); - } - }; - - const { styles, attributes, update } = usePopper(referenceElement, popperElement, { + const { x, y, strategy, refs, update } = useFloating({ placement, - modifiers: [ - { - name: 'offset', - options: { - offset, - }, - }, - ], + middleware: [offset(offsetOptions), shift()], }); const handleExpand: React.MouseEventHandler = () => { @@ -106,6 +77,10 @@ const EmojiSelector: React.FC = ({ onReact(emoji.custom ? emoji.id : emoji.native, emoji.custom ? emoji.imageUrl : undefined); }; + useEffect(() => { + refs.setReference(referenceElement); + }, [referenceElement]); + useEffect(() => () => { document.body.style.overflow = ''; }, []); @@ -114,35 +89,24 @@ const EmojiSelector: React.FC = ({ setExpanded(false); }, [visible]); - useEffect(() => { - document.addEventListener('mousedown', handleClickOutside); - - return () => { - document.removeEventListener('mousedown', handleClickOutside); - }; - }, [referenceElement, popperElement]); - - useEffect(() => { - if (visible && update) { - update(); + useClickOutside(refs, () => { + if (onClose) { + onClose(); } - }, [visible, update]); - - useEffect(() => { - if (expanded && update) { - update(); - } - }, [expanded, update]); - + }); return (
{expanded ? ( ); if (token && tokenStart) { - const results = emojiSearch(token.replace(':', ''), { maxResults: 5 } as any); + const results = emojiSearch(token.replace(':', ''), { maxResults: 5 }); setSuggestions({ list: results, token, diff --git a/app/soapbox/features/chats/components/chat-message-reaction-wrapper/chat-message-reaction-wrapper.tsx b/app/soapbox/features/chats/components/chat-message-reaction-wrapper/chat-message-reaction-wrapper.tsx index 03cbf23a7..91e959f7c 100644 --- a/app/soapbox/features/chats/components/chat-message-reaction-wrapper/chat-message-reaction-wrapper.tsx +++ b/app/soapbox/features/chats/components/chat-message-reaction-wrapper/chat-message-reaction-wrapper.tsx @@ -44,7 +44,7 @@ function ChatMessageReactionWrapper(props: IChatMessageReactionWrapper) { referenceElement={referenceElement} onReact={handleSelect} onClose={() => setIsOpen(false)} - offset={[-10, 12]} + offsetOptions={{ mainAxis: 12, crossAxis: -10 }} all={false} /> diff --git a/app/soapbox/features/chats/components/chat-textarea.tsx b/app/soapbox/features/chats/components/chat-textarea.tsx index 111a4cdc9..f6ee67b93 100644 --- a/app/soapbox/features/chats/components/chat-textarea.tsx +++ b/app/soapbox/features/chats/components/chat-textarea.tsx @@ -14,13 +14,13 @@ interface IChatTextarea extends React.ComponentProps { } /** Custom textarea for chats. */ -const ChatTextarea: React.FC = ({ +const ChatTextarea: React.FC = React.forwardRef(({ attachments, onDeleteAttachment, uploadCount = 0, uploadProgress = 0, ...rest -}) => { +}, ref) => { const isUploading = uploadCount > 0; const handleDeleteAttachment = (i: number) => { @@ -64,9 +64,9 @@ const ChatTextarea: React.FC = ({ )} -