fix(composer): isolate the web emoji pop-out state so opening it no longer re-renders the whole composer

This commit is contained in:
heaven 2026-06-04 02:45:04 +03:00
parent 587d117f96
commit fa17029a45

View file

@ -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