From 2b75dcacd23044dfec860bd979d57af5935907c4 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 19 Mar 2023 17:24:39 -0500 Subject: [PATCH 1/6] EmojiPickerDropdownContainer: switch to floating-ui --- .../components/emoji-picker-dropdown.tsx | 3 +- .../emoji-picker-dropdown-container.tsx | 63 +++++++------------ 2 files changed, 24 insertions(+), 42 deletions(-) diff --git a/app/soapbox/features/emoji/components/emoji-picker-dropdown.tsx b/app/soapbox/features/emoji/components/emoji-picker-dropdown.tsx index 7e6bd2414..3f486502e 100644 --- a/app/soapbox/features/emoji/components/emoji-picker-dropdown.tsx +++ b/app/soapbox/features/emoji/components/emoji-picker-dropdown.tsx @@ -11,7 +11,6 @@ import { RootState } from 'soapbox/store'; import { buildCustomEmojis } from '../../emoji'; import { EmojiPicker as EmojiPickerAsync } from '../../ui/util/async-components'; -import type { State as PopperState } from '@popperjs/core'; import type { Emoji, CustomEmoji, NativeEmoji } from 'soapbox/features/emoji'; let EmojiPicker: any; // load asynchronously @@ -49,7 +48,7 @@ export interface IEmojiPickerDropdown { withCustom?: boolean visible: boolean setVisible: (value: boolean) => void - update: (() => Promise>) | null + update: (() => any) | null } const perLine = 8; diff --git a/app/soapbox/features/emoji/containers/emoji-picker-dropdown-container.tsx b/app/soapbox/features/emoji/containers/emoji-picker-dropdown-container.tsx index 47dea8d6f..5929d1711 100644 --- a/app/soapbox/features/emoji/containers/emoji-picker-dropdown-container.tsx +++ b/app/soapbox/features/emoji/containers/emoji-picker-dropdown-container.tsx @@ -1,17 +1,14 @@ +import { useFloating, shift } from '@floating-ui/react'; import clsx from 'clsx'; -import { supportsPassiveEvents } from 'detect-passive-events'; -import React, { KeyboardEvent, useEffect, useState } from 'react'; +import React, { KeyboardEvent, useState } from 'react'; import { createPortal } from 'react-dom'; import { defineMessages, useIntl } from 'react-intl'; -import { usePopper } from 'react-popper'; import { IconButton } from 'soapbox/components/ui'; -import { isMobile } from 'soapbox/is-mobile'; +import { useClickOutside } from 'soapbox/hooks'; import EmojiPickerDropdown, { IEmojiPickerDropdown } from '../components/emoji-picker-dropdown'; -const listenerOptions = supportsPassiveEvents ? { passive: true } : false; - export const messages = defineMessages({ emoji: { id: 'emoji_button.label', defaultMessage: 'Insert emoji' }, }); @@ -22,51 +19,28 @@ const EmojiPickerDropdownContainer = ( const intl = useIntl(); const title = intl.formatMessage(messages.emoji); - const [popperElement, setPopperElement] = useState(null); - const [popperReference, setPopperReference] = useState(null); - const [containerElement, setContainerElement] = useState(null); - const [visible, setVisible] = useState(false); - const placement = props.condensed ? 'bottom-start' : 'top-start'; - const { styles, attributes, update } = usePopper(popperReference, popperElement, { - placement: isMobile(window.innerWidth) ? 'auto' : placement, + const { x, y, strategy, refs, update } = useFloating({ + middleware: [shift()], }); - const handleDocClick = (e: any) => { - if (!containerElement?.contains(e.target) && !popperElement?.contains(e.target)) { - setVisible(false); - } - }; + useClickOutside(refs, () => { + setVisible(false); + }); const handleToggle = (e: MouseEvent | KeyboardEvent) => { e.stopPropagation(); setVisible(!visible); }; - // TODO: move to class - const style: React.CSSProperties = !isMobile(window.innerWidth) ? styles.popper : { - ...styles.popper, width: '100%', - }; - - useEffect(() => { - document.addEventListener('click', handleDocClick, false); - document.addEventListener('touchend', handleDocClick, listenerOptions); - - return () => { - document.removeEventListener('click', handleDocClick, false); - // @ts-ignore - document.removeEventListener('touchend', handleDocClick, listenerOptions); - }; - }); - return ( -
+
- +
, document.body, )} From 5c7c0ea1ddf415d568200325a01dea0eeca32d47 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 19 Mar 2023 17:52:45 -0500 Subject: [PATCH 2/6] EmojiSelector: switch to floating-ui --- .../ui/emoji-selector/emoji-selector.tsx | 76 +++++-------------- .../chat-message-reaction-wrapper.tsx | 1 - 2 files changed, 19 insertions(+), 58 deletions(-) diff --git a/app/soapbox/components/ui/emoji-selector/emoji-selector.tsx b/app/soapbox/components/ui/emoji-selector/emoji-selector.tsx index 55e98b048..7053fbb7c 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 } 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,6 @@ interface IEmojiSelector { placement?: Placement /** Whether the selector should be visible. */ visible?: boolean - /** X/Y offset of the floating picker. */ - offset?: [number, number] /** Whether to allow any emoji to be chosen. */ all?: boolean } @@ -58,7 +55,6 @@ const EmojiSelector: React.FC = ({ onReact, placement = 'top', visible = false, - offset = [-10, 0], all = true, }): JSX.Element => { const soapboxConfig = useSoapboxConfig(); @@ -66,36 +62,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: [shift()], }); const handleExpand: React.MouseEventHandler = () => { @@ -106,6 +75,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 +87,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 ? ( setIsOpen(false)} - offset={[-10, 12]} all={false} /> From 47561e5c018a45cd2c1717505402d7332a9a7a19 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 19 Mar 2023 17:53:14 -0500 Subject: [PATCH 3/6] Enable custom emoji reacts on custom Pleroma forks --- app/soapbox/utils/features.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/soapbox/utils/features.ts b/app/soapbox/utils/features.ts index e99d4c921..b435406aa 100644 --- a/app/soapbox/utils/features.ts +++ b/app/soapbox/utils/features.ts @@ -327,7 +327,11 @@ const getInstanceFeatures = (instance: Instance) => { /** * Ability to add non-standard reactions to a status. */ - customEmojiReacts: v.software === PLEROMA && gte(v.version, '2.5.50'), + customEmojiReacts: any([ + features.includes('pleroma_custom_emoji_reactions'), + features.includes('custom_emoji_reactions'), + v.software === PLEROMA && gte(v.version, '2.5.50'), + ]), /** * Legacy DMs timeline where messages are displayed chronologically without groupings. From 67ffe9609f6950f246bc6e955fa311b6a85b552a Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 19 Mar 2023 18:03:44 -0500 Subject: [PATCH 4/6] ChatTextarea: pass ref to child Fixes https://gitlab.com/soapbox-pub/soapbox/-/issues/1390 --- app/soapbox/features/chats/components/chat-textarea.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 = ({ )} -