fix(composer): isolate the web emoji pop-out state so opening it no longer re-renders the whole composer
This commit is contained in:
parent
587d117f96
commit
fa17029a45
1 changed files with 59 additions and 36 deletions
|
|
@ -57,6 +57,7 @@ import {
|
||||||
getMentions,
|
getMentions,
|
||||||
} from '../../components/editor';
|
} from '../../components/editor';
|
||||||
import { EmojiBoard, EmojiBoardTab } from '../../components/emoji-board';
|
import { EmojiBoard, EmojiBoardTab } from '../../components/emoji-board';
|
||||||
|
import { UseStateProvider } from '../../components/UseStateProvider';
|
||||||
import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize';
|
import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize';
|
||||||
import {
|
import {
|
||||||
TUploadContent,
|
TUploadContent,
|
||||||
|
|
@ -606,34 +607,29 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
|
||||||
</IconButton>
|
</IconButton>
|
||||||
);
|
);
|
||||||
|
|
||||||
const closeEmojiBoard = () => {
|
// The native dock renders the board in the composer's top slot, so its
|
||||||
setEmojiBoardTab(undefined);
|
// open/close state lives here (only read on native). Desktop keeps its
|
||||||
if (!mobileOrTablet()) ReactEditor.focus(editor);
|
// state isolated inside the UseStateProvider below so opening the pop-out
|
||||||
};
|
// doesn't re-render the whole composer.
|
||||||
|
const dockedEmojiBoard = dockEmojiBoard && emojiBoardTab !== undefined && (
|
||||||
const emojiBoard = (
|
|
||||||
<EmojiBoard
|
<EmojiBoard
|
||||||
tab={emojiBoardTab ?? EmojiBoardTab.Emoji}
|
tab={emojiBoardTab}
|
||||||
onTabChange={setEmojiBoardTab}
|
onTabChange={setEmojiBoardTab}
|
||||||
imagePackRooms={imagePackRooms}
|
imagePackRooms={imagePackRooms}
|
||||||
returnFocusOnDeactivate={false}
|
returnFocusOnDeactivate={false}
|
||||||
dock={dockEmojiBoard}
|
dock
|
||||||
onEmojiSelect={handleEmoticonSelect}
|
onEmojiSelect={handleEmoticonSelect}
|
||||||
onCustomEmojiSelect={handleEmoticonSelect}
|
onCustomEmojiSelect={handleEmoticonSelect}
|
||||||
onStickerSelect={handleStickerSelect}
|
onStickerSelect={handleStickerSelect}
|
||||||
requestClose={closeEmojiBoard}
|
requestClose={() => setEmojiBoardTab(undefined)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
const emojiTriggerButton = (
|
const emojiButton = dockEmojiBoard ? (
|
||||||
<IconButton
|
<IconButton
|
||||||
ref={emojiBtnRef}
|
ref={emojiBtnRef}
|
||||||
aria-pressed={!!emojiBoardTab}
|
aria-pressed={!!emojiBoardTab}
|
||||||
onClick={() =>
|
onClick={() => setEmojiBoardTab(emojiBoardTab ? undefined : EmojiBoardTab.Emoji)}
|
||||||
dockEmojiBoard
|
|
||||||
? setEmojiBoardTab(emojiBoardTab ? undefined : EmojiBoardTab.Emoji)
|
|
||||||
: setEmojiBoardTab(EmojiBoardTab.Emoji)
|
|
||||||
}
|
|
||||||
variant="SurfaceVariant"
|
variant="SurfaceVariant"
|
||||||
fill="None"
|
fill="None"
|
||||||
size="300"
|
size="300"
|
||||||
|
|
@ -641,27 +637,54 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
|
||||||
>
|
>
|
||||||
<Icon src={StreamComposerIcons.Smile} />
|
<Icon src={StreamComposerIcons.Smile} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
);
|
|
||||||
|
|
||||||
// Native docks the board inline (rendered in the composer's top slot); the
|
|
||||||
// desktop trigger keeps the floating pop-out anchored to the button.
|
|
||||||
const emojiButton = dockEmojiBoard ? (
|
|
||||||
emojiTriggerButton
|
|
||||||
) : (
|
) : (
|
||||||
|
<UseStateProvider initial={undefined}>
|
||||||
|
{(emojiTab: EmojiBoardTab | undefined, setEmojiTab) => (
|
||||||
<PopOut
|
<PopOut
|
||||||
offset={16}
|
offset={16}
|
||||||
alignOffset={-44}
|
alignOffset={-44}
|
||||||
position="Top"
|
position="Top"
|
||||||
align="End"
|
align="End"
|
||||||
anchor={
|
anchor={
|
||||||
emojiBoardTab === undefined
|
emojiTab === undefined
|
||||||
? undefined
|
? undefined
|
||||||
: emojiBtnRef.current?.getBoundingClientRect() ?? undefined
|
: emojiBtnRef.current?.getBoundingClientRect() ?? undefined
|
||||||
}
|
}
|
||||||
content={emojiBoard}
|
content={
|
||||||
|
<EmojiBoard
|
||||||
|
tab={emojiTab ?? EmojiBoardTab.Emoji}
|
||||||
|
onTabChange={setEmojiTab}
|
||||||
|
imagePackRooms={imagePackRooms}
|
||||||
|
returnFocusOnDeactivate={false}
|
||||||
|
onEmojiSelect={handleEmoticonSelect}
|
||||||
|
onCustomEmojiSelect={handleEmoticonSelect}
|
||||||
|
onStickerSelect={handleStickerSelect}
|
||||||
|
requestClose={() => {
|
||||||
|
setEmojiTab((tab) => {
|
||||||
|
if (tab) {
|
||||||
|
if (!mobileOrTablet()) ReactEditor.focus(editor);
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return tab;
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{emojiTriggerButton}
|
<IconButton
|
||||||
|
ref={emojiBtnRef}
|
||||||
|
aria-pressed={!!emojiTab}
|
||||||
|
onClick={() => setEmojiTab(EmojiBoardTab.Emoji)}
|
||||||
|
variant="SurfaceVariant"
|
||||||
|
fill="None"
|
||||||
|
size="300"
|
||||||
|
radii="300"
|
||||||
|
>
|
||||||
|
<Icon src={StreamComposerIcons.Smile} />
|
||||||
|
</IconButton>
|
||||||
</PopOut>
|
</PopOut>
|
||||||
|
)}
|
||||||
|
</UseStateProvider>
|
||||||
);
|
);
|
||||||
|
|
||||||
const sendButton = (
|
const sendButton = (
|
||||||
|
|
@ -799,7 +822,7 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
|
||||||
onPaste={handlePaste}
|
onPaste={handlePaste}
|
||||||
top={
|
top={
|
||||||
<>
|
<>
|
||||||
{dockEmojiBoard && emojiBoardTab !== undefined && emojiBoard}
|
{dockedEmojiBoard}
|
||||||
{replyDraft && (
|
{replyDraft && (
|
||||||
<div>
|
<div>
|
||||||
<Box
|
<Box
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue