sforkowany z mirror/soapbox
EmojiSelector: switch to floating-ui
rodzic
2b75dcacd2
commit
5c7c0ea1dd
|
@ -1,11 +1,10 @@
|
||||||
import { Placement } from '@popperjs/core';
|
import { shift, useFloating, Placement } from '@floating-ui/react';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { usePopper } from 'react-popper';
|
|
||||||
|
|
||||||
import { Emoji as EmojiComponent, HStack, IconButton } from 'soapbox/components/ui';
|
import { Emoji as EmojiComponent, HStack, IconButton } from 'soapbox/components/ui';
|
||||||
import EmojiPickerDropdown from 'soapbox/features/emoji/components/emoji-picker-dropdown';
|
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';
|
import type { Emoji } from 'soapbox/features/emoji';
|
||||||
|
|
||||||
|
@ -45,8 +44,6 @@ interface IEmojiSelector {
|
||||||
placement?: Placement
|
placement?: Placement
|
||||||
/** Whether the selector should be visible. */
|
/** Whether the selector should be visible. */
|
||||||
visible?: boolean
|
visible?: boolean
|
||||||
/** X/Y offset of the floating picker. */
|
|
||||||
offset?: [number, number]
|
|
||||||
/** Whether to allow any emoji to be chosen. */
|
/** Whether to allow any emoji to be chosen. */
|
||||||
all?: boolean
|
all?: boolean
|
||||||
}
|
}
|
||||||
|
@ -58,7 +55,6 @@ const EmojiSelector: React.FC<IEmojiSelector> = ({
|
||||||
onReact,
|
onReact,
|
||||||
placement = 'top',
|
placement = 'top',
|
||||||
visible = false,
|
visible = false,
|
||||||
offset = [-10, 0],
|
|
||||||
all = true,
|
all = true,
|
||||||
}): JSX.Element => {
|
}): JSX.Element => {
|
||||||
const soapboxConfig = useSoapboxConfig();
|
const soapboxConfig = useSoapboxConfig();
|
||||||
|
@ -66,36 +62,9 @@ const EmojiSelector: React.FC<IEmojiSelector> = ({
|
||||||
|
|
||||||
const [expanded, setExpanded] = useState(false);
|
const [expanded, setExpanded] = useState(false);
|
||||||
|
|
||||||
// `useRef` won't trigger a re-render, while `useState` does.
|
const { x, y, strategy, refs, update } = useFloating<HTMLElement>({
|
||||||
// https://popper.js.org/react-popper/v2/
|
|
||||||
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(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, {
|
|
||||||
placement,
|
placement,
|
||||||
modifiers: [
|
middleware: [shift()],
|
||||||
{
|
|
||||||
name: 'offset',
|
|
||||||
options: {
|
|
||||||
offset,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleExpand: React.MouseEventHandler = () => {
|
const handleExpand: React.MouseEventHandler = () => {
|
||||||
|
@ -106,6 +75,10 @@ const EmojiSelector: React.FC<IEmojiSelector> = ({
|
||||||
onReact(emoji.custom ? emoji.id : emoji.native, emoji.custom ? emoji.imageUrl : undefined);
|
onReact(emoji.custom ? emoji.id : emoji.native, emoji.custom ? emoji.imageUrl : undefined);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
refs.setReference(referenceElement);
|
||||||
|
}, [referenceElement]);
|
||||||
|
|
||||||
useEffect(() => () => {
|
useEffect(() => () => {
|
||||||
document.body.style.overflow = '';
|
document.body.style.overflow = '';
|
||||||
}, []);
|
}, []);
|
||||||
|
@ -114,35 +87,24 @@ const EmojiSelector: React.FC<IEmojiSelector> = ({
|
||||||
setExpanded(false);
|
setExpanded(false);
|
||||||
}, [visible]);
|
}, [visible]);
|
||||||
|
|
||||||
useEffect(() => {
|
useClickOutside(refs, () => {
|
||||||
document.addEventListener('mousedown', handleClickOutside);
|
if (onClose) {
|
||||||
|
onClose();
|
||||||
return () => {
|
|
||||||
document.removeEventListener('mousedown', handleClickOutside);
|
|
||||||
};
|
|
||||||
}, [referenceElement, popperElement]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (visible && update) {
|
|
||||||
update();
|
|
||||||
}
|
}
|
||||||
}, [visible, update]);
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (expanded && update) {
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
}, [expanded, update]);
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={clsx('z-[101] transition-opacity duration-100', {
|
className={clsx('z-[101] transition-opacity duration-100', {
|
||||||
'opacity-0 pointer-events-none': !visible,
|
'opacity-0 pointer-events-none': !visible,
|
||||||
})}
|
})}
|
||||||
ref={setPopperElement}
|
ref={refs.setFloating}
|
||||||
style={styles.popper}
|
style={{
|
||||||
{...attributes.popper}
|
position: strategy,
|
||||||
|
top: y ?? 0,
|
||||||
|
left: x ?? 0,
|
||||||
|
width: 'max-content',
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{expanded ? (
|
{expanded ? (
|
||||||
<EmojiPickerDropdown
|
<EmojiPickerDropdown
|
||||||
|
|
|
@ -44,7 +44,6 @@ function ChatMessageReactionWrapper(props: IChatMessageReactionWrapper) {
|
||||||
referenceElement={referenceElement}
|
referenceElement={referenceElement}
|
||||||
onReact={handleSelect}
|
onReact={handleSelect}
|
||||||
onClose={() => setIsOpen(false)}
|
onClose={() => setIsOpen(false)}
|
||||||
offset={[-10, 12]}
|
|
||||||
all={false}
|
all={false}
|
||||||
/>
|
/>
|
||||||
</Portal>
|
</Portal>
|
||||||
|
|
Ładowanie…
Reference in New Issue