mirror of
https://github.com/signalapp/Signal-Desktop.git
synced 2026-05-08 08:58:38 +01:00
FunPicker: Keep emoji picker open on select for composition inputs
This commit is contained in:
@@ -735,7 +735,6 @@ export const CompositionArea = memo(function CompositionArea({
|
|||||||
recentEmojis={recentEmojis}
|
recentEmojis={recentEmojis}
|
||||||
emojiSkinToneDefault={emojiSkinToneDefault}
|
emojiSkinToneDefault={emojiSkinToneDefault}
|
||||||
onEmojiSkinToneDefaultChange={onEmojiSkinToneDefaultChange}
|
onEmojiSkinToneDefaultChange={onEmojiSkinToneDefaultChange}
|
||||||
closeOnPick
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -210,6 +210,7 @@ export function CompositionTextArea({
|
|||||||
open={emojiPickerOpen}
|
open={emojiPickerOpen}
|
||||||
onOpenChange={handleEmojiPickerOpenChange}
|
onOpenChange={handleEmojiPickerOpenChange}
|
||||||
onSelectEmoji={handleSelectEmoji}
|
onSelectEmoji={handleSelectEmoji}
|
||||||
|
closeOnSelect={false}
|
||||||
>
|
>
|
||||||
<FunEmojiPickerButton i18n={i18n} />
|
<FunEmojiPickerButton i18n={i18n} />
|
||||||
</FunEmojiPicker>
|
</FunEmojiPicker>
|
||||||
|
|||||||
@@ -262,6 +262,7 @@ function CustomizingPreferredReactionsModalItem(props: {
|
|||||||
onOpenChange={handleEmojiPickerOpenChange}
|
onOpenChange={handleEmojiPickerOpenChange}
|
||||||
placement="bottom"
|
placement="bottom"
|
||||||
onSelectEmoji={props.onSelectEmoji}
|
onSelectEmoji={props.onSelectEmoji}
|
||||||
|
closeOnSelect
|
||||||
>
|
>
|
||||||
{button}
|
{button}
|
||||||
</FunEmojiPicker>
|
</FunEmojiPicker>
|
||||||
|
|||||||
@@ -1409,6 +1409,7 @@ export function MediaEditor({
|
|||||||
onSelectEmoji={handleSelectEmoji}
|
onSelectEmoji={handleSelectEmoji}
|
||||||
placement="top"
|
placement="top"
|
||||||
theme={ThemeType.dark}
|
theme={ThemeType.dark}
|
||||||
|
closeOnSelect={false}
|
||||||
>
|
>
|
||||||
<FunEmojiPickerButton i18n={i18n} />
|
<FunEmojiPickerButton i18n={i18n} />
|
||||||
</FunEmojiPicker>
|
</FunEmojiPicker>
|
||||||
|
|||||||
@@ -492,6 +492,7 @@ export function ProfileEditor({
|
|||||||
onOpenChange={handleEmojiPickerOpenChange}
|
onOpenChange={handleEmojiPickerOpenChange}
|
||||||
placement="bottom"
|
placement="bottom"
|
||||||
onSelectEmoji={handleSelectEmoji}
|
onSelectEmoji={handleSelectEmoji}
|
||||||
|
closeOnSelect
|
||||||
>
|
>
|
||||||
<FunEmojiPickerButton
|
<FunEmojiPickerButton
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
|
|||||||
@@ -323,6 +323,7 @@ export function StoryViewsNRepliesModal({
|
|||||||
onSelectEmoji={handleSelectEmoji}
|
onSelectEmoji={handleSelectEmoji}
|
||||||
placement="top"
|
placement="top"
|
||||||
theme={ThemeType.dark}
|
theme={ThemeType.dark}
|
||||||
|
closeOnSelect={false}
|
||||||
>
|
>
|
||||||
<FunEmojiPickerButton i18n={i18n} />
|
<FunEmojiPickerButton i18n={i18n} />
|
||||||
</FunEmojiPicker>
|
</FunEmojiPicker>
|
||||||
|
|||||||
@@ -493,6 +493,7 @@ export function TextStoryCreator({
|
|||||||
placement="top"
|
placement="top"
|
||||||
onSelectEmoji={handleSelectEmoji}
|
onSelectEmoji={handleSelectEmoji}
|
||||||
theme={ThemeType.dark}
|
theme={ThemeType.dark}
|
||||||
|
closeOnSelect
|
||||||
>
|
>
|
||||||
<FunEmojiPickerButton i18n={i18n} />
|
<FunEmojiPickerButton i18n={i18n} />
|
||||||
</FunEmojiPicker>
|
</FunEmojiPicker>
|
||||||
|
|||||||
@@ -155,6 +155,7 @@ export const ReactionPicker = React.forwardRef<HTMLDivElement, Props>(
|
|||||||
onSelectEmoji={onSelectEmoji}
|
onSelectEmoji={onSelectEmoji}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
showCustomizePreferredReactionsButton
|
showCustomizePreferredReactionsButton
|
||||||
|
closeOnSelect
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
aria-label={i18n('icu:Reactions--more')}
|
aria-label={i18n('icu:Reactions--more')}
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ export default {
|
|||||||
theme: undefined,
|
theme: undefined,
|
||||||
onSelectEmoji: action('onSelectEmoji'),
|
onSelectEmoji: action('onSelectEmoji'),
|
||||||
showCustomizePreferredReactionsButton: false,
|
showCustomizePreferredReactionsButton: false,
|
||||||
|
closeOnSelect: true,
|
||||||
},
|
},
|
||||||
} satisfies ComponentMeta<TemplateProps>;
|
} satisfies ComponentMeta<TemplateProps>;
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ export type FunEmojiPickerProps = Readonly<{
|
|||||||
onSelectEmoji: (emojiSelection: FunEmojiSelection) => void;
|
onSelectEmoji: (emojiSelection: FunEmojiSelection) => void;
|
||||||
theme?: ThemeType;
|
theme?: ThemeType;
|
||||||
showCustomizePreferredReactionsButton?: boolean;
|
showCustomizePreferredReactionsButton?: boolean;
|
||||||
|
closeOnSelect: boolean;
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
@@ -51,6 +52,7 @@ export const FunEmojiPicker = memo(function FunEmojiPicker(
|
|||||||
showCustomizePreferredReactionsButton={
|
showCustomizePreferredReactionsButton={
|
||||||
props.showCustomizePreferredReactionsButton ?? false
|
props.showCustomizePreferredReactionsButton ?? false
|
||||||
}
|
}
|
||||||
|
closeOnSelect={props.closeOnSelect}
|
||||||
/>
|
/>
|
||||||
</FunErrorBoundary>
|
</FunErrorBoundary>
|
||||||
</FunPopover>
|
</FunPopover>
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ export const FunPicker = memo(function FunPicker(
|
|||||||
onSelectEmoji={props.onSelectEmoji}
|
onSelectEmoji={props.onSelectEmoji}
|
||||||
onClose={handleClose}
|
onClose={handleClose}
|
||||||
showCustomizePreferredReactionsButton={false}
|
showCustomizePreferredReactionsButton={false}
|
||||||
|
closeOnSelect={false}
|
||||||
/>
|
/>
|
||||||
</FunErrorBoundary>
|
</FunErrorBoundary>
|
||||||
</FunTabPanel>
|
</FunTabPanel>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Copyright 2025 Signal Messenger, LLC
|
// Copyright 2025 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
import React from 'react';
|
import React, { useCallback } from 'react';
|
||||||
import type { Placement } from 'react-aria';
|
import type { Placement } from 'react-aria';
|
||||||
import { Dialog, Popover } from 'react-aria-components';
|
import { Dialog, Popover } from 'react-aria-components';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
@@ -14,6 +14,18 @@ export type FunPopoverProps = Readonly<{
|
|||||||
}>;
|
}>;
|
||||||
|
|
||||||
export function FunPopover(props: FunPopoverProps): JSX.Element {
|
export function FunPopover(props: FunPopoverProps): JSX.Element {
|
||||||
|
const shouldCloseOnInteractOutside = useCallback(
|
||||||
|
(element: Element): boolean => {
|
||||||
|
// Don't close when quill steals focus
|
||||||
|
const match = element.closest('.module-composition-input__input');
|
||||||
|
if (match != null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover
|
<Popover
|
||||||
data-fun-overlay
|
data-fun-overlay
|
||||||
@@ -22,6 +34,7 @@ export function FunPopover(props: FunPopoverProps): JSX.Element {
|
|||||||
'dark-theme': props.theme === ThemeType.dark,
|
'dark-theme': props.theme === ThemeType.dark,
|
||||||
})}
|
})}
|
||||||
placement={props.placement}
|
placement={props.placement}
|
||||||
|
shouldCloseOnInteractOutside={shouldCloseOnInteractOutside}
|
||||||
>
|
>
|
||||||
<Dialog className="FunPopover__Dialog">{props.children}</Dialog>
|
<Dialog className="FunPopover__Dialog">{props.children}</Dialog>
|
||||||
</Popover>
|
</Popover>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
OverlayArrow,
|
OverlayArrow,
|
||||||
Popover,
|
Popover,
|
||||||
} from 'react-aria-components';
|
} from 'react-aria-components';
|
||||||
|
import type { PressEvent } from 'react-aria';
|
||||||
import { VisuallyHidden } from 'react-aria';
|
import { VisuallyHidden } from 'react-aria';
|
||||||
import type { LocalizerType } from '../../../types/I18N';
|
import type { LocalizerType } from '../../../types/I18N';
|
||||||
import { strictAssert } from '../../../util/assert';
|
import { strictAssert } from '../../../util/assert';
|
||||||
@@ -145,12 +146,14 @@ export type FunPanelEmojisProps = Readonly<{
|
|||||||
onSelectEmoji: (emojiSelection: FunEmojiSelection) => void;
|
onSelectEmoji: (emojiSelection: FunEmojiSelection) => void;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
showCustomizePreferredReactionsButton: boolean;
|
showCustomizePreferredReactionsButton: boolean;
|
||||||
|
closeOnSelect: boolean;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export function FunPanelEmojis({
|
export function FunPanelEmojis({
|
||||||
onSelectEmoji,
|
onSelectEmoji,
|
||||||
onClose,
|
onClose,
|
||||||
showCustomizePreferredReactionsButton,
|
showCustomizePreferredReactionsButton,
|
||||||
|
closeOnSelect,
|
||||||
}: FunPanelEmojisProps): JSX.Element {
|
}: FunPanelEmojisProps): JSX.Element {
|
||||||
const fun = useFunContext();
|
const fun = useFunContext();
|
||||||
const {
|
const {
|
||||||
@@ -160,14 +163,15 @@ export function FunPanelEmojis({
|
|||||||
selectedEmojisSection,
|
selectedEmojisSection,
|
||||||
onChangeSelectedEmojisSection,
|
onChangeSelectedEmojisSection,
|
||||||
onOpenCustomizePreferredReactionsModal,
|
onOpenCustomizePreferredReactionsModal,
|
||||||
recentEmojis,
|
recentEmojis: unstableRecentEmojis,
|
||||||
onSelectEmoji: onFunSelectEmoji,
|
onSelectEmoji: onFunSelectEmoji,
|
||||||
} = fun;
|
} = fun;
|
||||||
|
|
||||||
const scrollerRef = useRef<HTMLDivElement>(null);
|
const scrollerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
// Don't update recent emojis while the emoji panel is open
|
||||||
|
const [recentEmojis] = useState(unstableRecentEmojis);
|
||||||
const [focusedCellKey, setFocusedCellKey] = useState<CellKey | null>(null);
|
const [focusedCellKey, setFocusedCellKey] = useState<CellKey | null>(null);
|
||||||
|
|
||||||
const [skinTonePopoverOpen, setSkinTonePopoverOpen] = useState(false);
|
const [skinTonePopoverOpen, setSkinTonePopoverOpen] = useState(false);
|
||||||
|
|
||||||
const handleSkinTonePopoverOpenChange = useCallback((open: boolean) => {
|
const handleSkinTonePopoverOpenChange = useCallback((open: boolean) => {
|
||||||
@@ -255,13 +259,15 @@ export function FunPanelEmojis({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const handleSelectEmoji = useCallback(
|
const handleSelectEmoji = useCallback(
|
||||||
(emojiSelection: FunEmojiSelection) => {
|
(emojiSelection: FunEmojiSelection, shouldClose: boolean) => {
|
||||||
onFunSelectEmoji(emojiSelection);
|
onFunSelectEmoji(emojiSelection);
|
||||||
onSelectEmoji(emojiSelection);
|
onSelectEmoji(emojiSelection);
|
||||||
onClose();
|
if (closeOnSelect || shouldClose) {
|
||||||
setFocusedCellKey(null);
|
setFocusedCellKey(null);
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[onFunSelectEmoji, onSelectEmoji, onClose]
|
[onFunSelectEmoji, onSelectEmoji, onClose, closeOnSelect]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleOpenCustomizePreferredReactionsModal = useCallback(() => {
|
const handleOpenCustomizePreferredReactionsModal = useCallback(() => {
|
||||||
@@ -478,7 +484,10 @@ type RowProps = Readonly<{
|
|||||||
cells: ReadonlyArray<CellLayoutNode>;
|
cells: ReadonlyArray<CellLayoutNode>;
|
||||||
focusedCellKey: CellKey | null;
|
focusedCellKey: CellKey | null;
|
||||||
emojiSkinToneDefault: EmojiSkinTone | null;
|
emojiSkinToneDefault: EmojiSkinTone | null;
|
||||||
onSelectEmoji: (emojiSelection: FunEmojiSelection) => void;
|
onSelectEmoji: (
|
||||||
|
emojiSelection: FunEmojiSelection,
|
||||||
|
shouldClose: boolean
|
||||||
|
) => void;
|
||||||
onEmojiSkinToneDefaultChange: (emojiSkinTone: EmojiSkinTone) => void;
|
onEmojiSkinToneDefaultChange: (emojiSkinTone: EmojiSkinTone) => void;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
@@ -517,7 +526,10 @@ type CellProps = Readonly<{
|
|||||||
rowIndex: number;
|
rowIndex: number;
|
||||||
isTabbable: boolean;
|
isTabbable: boolean;
|
||||||
emojiSkinToneDefault: EmojiSkinTone | null;
|
emojiSkinToneDefault: EmojiSkinTone | null;
|
||||||
onSelectEmoji: (emojiSelection: FunEmojiSelection) => void;
|
onSelectEmoji: (
|
||||||
|
emojiSelection: FunEmojiSelection,
|
||||||
|
shouldClose: boolean
|
||||||
|
) => void;
|
||||||
onEmojiSkinToneDefaultChange: (emojiSkinTone: EmojiSkinTone) => void;
|
onEmojiSkinToneDefaultChange: (emojiSkinTone: EmojiSkinTone) => void;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
@@ -556,26 +568,32 @@ const Cell = memo(function Cell(props: CellProps): JSX.Element {
|
|||||||
return getEmojiVariantByParentKeyAndSkinTone(emojiParent.key, skinTone);
|
return getEmojiVariantByParentKeyAndSkinTone(emojiParent.key, skinTone);
|
||||||
}, [emojiParent, skinTone]);
|
}, [emojiParent, skinTone]);
|
||||||
|
|
||||||
const handlePress = useCallback(() => {
|
const handlePress = useCallback(
|
||||||
if (emojiHasSkinToneVariants && emojiSkinToneDefault == null) {
|
(event: PressEvent) => {
|
||||||
setPopoverOpen(true);
|
if (emojiHasSkinToneVariants && emojiSkinToneDefault == null) {
|
||||||
return;
|
setPopoverOpen(true);
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
onSelectEmoji({
|
const emojiSelection: FunEmojiSelection = {
|
||||||
variantKey: emojiVariant.key,
|
variantKey: emojiVariant.key,
|
||||||
parentKey: emojiParent.key,
|
parentKey: emojiParent.key,
|
||||||
englishShortName: emojiParent.englishShortNameDefault,
|
englishShortName: emojiParent.englishShortNameDefault,
|
||||||
|
skinTone,
|
||||||
|
};
|
||||||
|
const shouldClose =
|
||||||
|
(event.pointerType === 'keyboard' || event.pointerType === 'virtual') &&
|
||||||
|
!(event.ctrlKey || event.metaKey);
|
||||||
|
onSelectEmoji(emojiSelection, shouldClose);
|
||||||
|
},
|
||||||
|
[
|
||||||
|
emojiHasSkinToneVariants,
|
||||||
|
emojiSkinToneDefault,
|
||||||
|
emojiVariant,
|
||||||
|
emojiParent,
|
||||||
|
onSelectEmoji,
|
||||||
skinTone,
|
skinTone,
|
||||||
});
|
]
|
||||||
}, [
|
);
|
||||||
emojiHasSkinToneVariants,
|
|
||||||
emojiSkinToneDefault,
|
|
||||||
emojiVariant,
|
|
||||||
emojiParent,
|
|
||||||
onSelectEmoji,
|
|
||||||
skinTone,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const handleLongPress = useCallback(() => {
|
const handleLongPress = useCallback(() => {
|
||||||
if (emojiHasSkinToneVariants) {
|
if (emojiHasSkinToneVariants) {
|
||||||
@@ -601,12 +619,14 @@ const Cell = memo(function Cell(props: CellProps): JSX.Element {
|
|||||||
skinToneSelection
|
skinToneSelection
|
||||||
);
|
);
|
||||||
onEmojiSkinToneDefaultChange(skinToneSelection);
|
onEmojiSkinToneDefaultChange(skinToneSelection);
|
||||||
onSelectEmoji({
|
const emojiSelection: FunEmojiSelection = {
|
||||||
variantKey: variant.key,
|
variantKey: variant.key,
|
||||||
parentKey: emojiParent.key,
|
parentKey: emojiParent.key,
|
||||||
englishShortName: emojiParent.englishShortNameDefault,
|
englishShortName: emojiParent.englishShortNameDefault,
|
||||||
skinTone: skinToneSelection,
|
skinTone: skinToneSelection,
|
||||||
});
|
};
|
||||||
|
const shouldClose = true;
|
||||||
|
onSelectEmoji(emojiSelection, shouldClose);
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
onEmojiSkinToneDefaultChange,
|
onEmojiSkinToneDefaultChange,
|
||||||
|
|||||||
@@ -359,9 +359,9 @@ export function FunPanelGifs({
|
|||||||
(_event: PressEvent, gifSelection: FunGifSelection) => {
|
(_event: PressEvent, gifSelection: FunGifSelection) => {
|
||||||
onFunSelectGif(gifSelection);
|
onFunSelectGif(gifSelection);
|
||||||
onSelectGif(gifSelection);
|
onSelectGif(gifSelection);
|
||||||
|
setSelectedItemKey(null);
|
||||||
// Should always close, cannot select multiple
|
// Should always close, cannot select multiple
|
||||||
onClose();
|
onClose();
|
||||||
setSelectedItemKey(null);
|
|
||||||
},
|
},
|
||||||
[onFunSelectGif, onSelectGif, onClose]
|
[onFunSelectGif, onSelectGif, onClose]
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -344,8 +344,8 @@ export function FunPanelStickers({
|
|||||||
onFunSelectSticker(stickerSelection);
|
onFunSelectSticker(stickerSelection);
|
||||||
onSelectSticker(stickerSelection);
|
onSelectSticker(stickerSelection);
|
||||||
if (!(event.ctrlKey || event.metaKey)) {
|
if (!(event.ctrlKey || event.metaKey)) {
|
||||||
onClose();
|
|
||||||
setFocusedCellKey(null);
|
setFocusedCellKey(null);
|
||||||
|
onClose();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[onFunSelectSticker, onSelectSticker, onClose]
|
[onFunSelectSticker, onSelectSticker, onClose]
|
||||||
|
|||||||
Reference in New Issue
Block a user