Skip to content

Commit e5473e2

Browse files
Migrate remaining emoji/sticker pickers to fun picker
1 parent f166db9 commit e5473e2

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+1029
-543
lines changed

_locales/en/messages.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3279,6 +3279,18 @@
32793279
"messageformat": "Dark skin tone",
32803280
"description": "Fun Picker > Emojis Panel > Skin Tone Picker > Skin Tone Option: Dark skin tone > Accessibility label"
32813281
},
3282+
"icu:FunButton__Label--FunPicker": {
3283+
"messageformat": "Add an Emoji, Sticker, or GIF",
3284+
"description": "Fun Picker > Trigger Button > Accessibility label"
3285+
},
3286+
"icu:FunButton__Label--EmojiPicker": {
3287+
"messageformat": "Add an Emoji",
3288+
"description": "Emoji Picker > Trigger Button > Accessibility label"
3289+
},
3290+
"icu:FunButton__Label--StickerPicker": {
3291+
"messageformat": "Add a Sticker",
3292+
"description": "Sticker Picker > Trigger Button > Accessibility label"
3293+
},
32823294
"icu:confirmation-dialog--Cancel": {
32833295
"messageformat": "Cancel",
32843296
"description": "Appears on the cancel button in confirmation dialogs."

stylesheets/components/CompositionArea.scss

Lines changed: 0 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -290,39 +290,3 @@
290290
}
291291
}
292292
}
293-
294-
.CompositionArea__FunButton {
295-
@include mixins.button-reset();
296-
& {
297-
border-radius: 4px;
298-
background: none;
299-
width: 32px;
300-
height: 32px;
301-
display: flex;
302-
justify-content: center;
303-
align-items: center;
304-
}
305-
306-
&:focus {
307-
outline: none;
308-
@include mixins.keyboard-mode {
309-
outline: 2px solid variables.$color-ultramarine;
310-
}
311-
}
312-
313-
&::after {
314-
display: block;
315-
content: '';
316-
width: 20px;
317-
height: 20px;
318-
flex-shrink: 0;
319-
@include mixins.color-svg(
320-
'../images/icons/v3/emoji/emoji.svg',
321-
light-dark(variables.$color-gray-75, variables.$color-gray-15)
322-
);
323-
}
324-
325-
&[aria-expanded='true'] {
326-
background: light-dark(variables.$color-gray-05, variables.$color-gray-75);
327-
}
328-
}

stylesheets/components/fun/Fun.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright 2025 Signal Messenger, LLC
22
// SPDX-License-Identifier: AGPL-3.0-only
33

4+
@use './FunButton.scss';
45
@use './FunConstants.scss';
56
@use './FunEmoji.scss';
67
@use './FunGif.scss';
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// Copyright 2025 Signal Messenger, LLC
2+
// SPDX-License-Identifier: AGPL-3.0-only
3+
4+
@use '../../variables';
5+
@use '../../mixins';
6+
7+
.FunButton {
8+
@include mixins.button-reset();
9+
& {
10+
border-radius: 4px;
11+
background: none;
12+
width: 32px;
13+
height: 32px;
14+
display: flex;
15+
justify-content: center;
16+
align-items: center;
17+
}
18+
19+
&:focus {
20+
outline: none;
21+
@include mixins.keyboard-mode {
22+
outline: 2px solid variables.$color-ultramarine;
23+
}
24+
}
25+
26+
&[aria-expanded='true'] {
27+
background: light-dark(variables.$color-gray-05, variables.$color-gray-75);
28+
}
29+
}
30+
31+
.FunButton__Icon {
32+
display: block;
33+
width: 20px;
34+
height: 20px;
35+
flex-shrink: 0;
36+
}
37+
38+
.FunButton__Icon--FunPicker,
39+
.FunButton__Icon--EmojiPicker {
40+
@include mixins.color-svg(
41+
'../images/icons/v3/emoji/emoji.svg',
42+
light-dark(variables.$color-gray-75, variables.$color-gray-15)
43+
);
44+
}
45+
46+
.FunButton__Icon--StickerPicker {
47+
@include mixins.color-svg(
48+
'../images/icons/v3/sticker/sticker.svg',
49+
light-dark(variables.$color-gray-75, variables.$color-gray-15)
50+
);
51+
}

ts/RemoteConfig.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@ export type ConfigKeyType =
3333
| 'desktop.experimentalTransportEnabled.prod'
3434
| 'desktop.cdsiViaLibsignal'
3535
| 'desktop.cdsiViaLibsignal.disableNewConnectionLogic'
36-
| 'desktop.funPicker'
36+
| 'desktop.funPicker' // alpha
37+
| 'desktop.funPicker.beta'
38+
| 'desktop.funPicker.prod'
3739
| 'desktop.releaseNotes'
3840
| 'desktop.releaseNotes.beta'
3941
| 'desktop.releaseNotes.dev'

ts/components/CallingLobby.tsx

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
// SPDX-License-Identifier: AGPL-3.0-only
33

44
import React from 'react';
5-
import FocusTrap from 'focus-trap-react';
65
import classNames from 'classnames';
6+
import { FocusScope } from 'react-aria';
77
import type {
88
SetLocalAudioType,
99
SetLocalVideoType,
@@ -253,16 +253,7 @@ export function CallingLobby({
253253
useWasInitiallyMutedToast(hasLocalAudio, i18n);
254254

255255
return (
256-
<FocusTrap
257-
focusTrapOptions={{
258-
allowOutsideClick: ({ target }) => {
259-
if (!target || !(target instanceof HTMLElement)) {
260-
return false;
261-
}
262-
return target.matches('.Toast, .Toast *');
263-
},
264-
}}
265-
>
256+
<FocusScope contain restoreFocus>
266257
<div className="module-calling__container dark-theme">
267258
{shouldShowLocalVideo ? (
268259
<div
@@ -387,7 +378,7 @@ export function CallingLobby({
387378
<div className="module-calling__spacer CallControls__OuterSpacer" />
388379
</div>
389380
</div>
390-
</FocusTrap>
381+
</FocusScope>
391382
);
392383
}
393384

ts/components/CallingParticipantsList.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,8 @@
55

66
import React, { useContext } from 'react';
77
import { createPortal } from 'react-dom';
8-
import FocusTrap from 'focus-trap-react';
98
import classNames from 'classnames';
10-
9+
import { FocusScope } from 'react-aria';
1110
import { Avatar, AvatarSize } from './Avatar';
1211
import { ContactName } from './conversation/ContactName';
1312
import { InContactsIcon } from './InContactsIcon';
@@ -80,7 +79,7 @@ export const CallingParticipantsList = React.memo(
8079
}
8180

8281
return createPortal(
83-
<FocusTrap>
82+
<FocusScope contain restoreFocus>
8483
<div
8584
className="module-calling-participants-list__overlay"
8685
onClick={handleCancel}
@@ -188,7 +187,7 @@ export const CallingParticipantsList = React.memo(
188187
</div>
189188
</div>
190189
</div>
191-
</FocusTrap>,
190+
</FocusScope>,
192191
root
193192
);
194193
}

ts/components/CompositionArea.stories.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { ConversationColors } from '../types/Colors';
1616
import { getDefaultConversation } from '../test-both/helpers/getDefaultConversation';
1717
import { PaymentEventKind } from '../types/Payment';
1818
import { EmojiSkinTone } from './fun/data/emojis';
19+
import { MockFunProvider } from './fun/mocks';
1920

2021
const { i18n } = window.SignalContext;
2122

@@ -24,6 +25,7 @@ export default {
2425
decorators: [
2526
// necessary for the add attachment button to render properly
2627
storyFn => <div className="file-input">{storyFn()}</div>,
28+
storyFn => <MockFunProvider>{storyFn()}</MockFunProvider>,
2729
],
2830
argTypes: {
2931
recordingState: {

ts/components/CompositionArea.tsx

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,6 @@
44
import React, { memo, useCallback, useEffect, useRef, useState } from 'react';
55
import classNames from 'classnames';
66
import type { ReadonlyDeep } from 'type-fest';
7-
8-
import { Button } from 'react-aria-components';
9-
import { VisuallyHidden } from 'react-aria';
107
import type {
118
DraftBodyRanges,
129
HydratedBodyRangesType,
@@ -80,13 +77,16 @@ import type { ForwardMessagesPayload } from '../state/ducks/globalModals';
8077
import { ForwardMessagesModalType } from './ForwardMessagesModal';
8178
import { SignalConversationMuteToggle } from './conversation/SignalConversationMuteToggle';
8279
import { FunPicker } from './fun/FunPicker';
83-
import * as RemoteConfig from '../RemoteConfig';
8480
import type { FunEmojiSelection } from './fun/panels/FunPanelEmojis';
8581
import type { FunStickerSelection } from './fun/panels/FunPanelStickers';
8682
import type { FunGifSelection } from './fun/panels/FunPanelGifs';
8783
import type { SmartDraftGifMessageSendModalProps } from '../state/smart/DraftGifMessageSendModal';
8884
import { strictAssert } from '../util/assert';
8985
import { ConfirmationDialog } from './ConfirmationDialog';
86+
import type { EmojiSkinTone } from './fun/data/emojis';
87+
import type { StickerPackType, StickerType } from '../state/ducks/stickers';
88+
import { FunPickerButton } from './fun/FunButton';
89+
import { isFunPickerEnabled } from './fun/isFunPickerEnabled';
9090

9191
export type OwnProps = Readonly<{
9292
acceptedMessageRequest: boolean | null;
@@ -207,6 +207,12 @@ export type OwnProps = Readonly<{
207207
toggleDraftGifMessageSendModal: (
208208
props: SmartDraftGifMessageSendModalProps | null
209209
) => void;
210+
211+
onPickEmoji: (e: EmojiPickDataType) => void;
212+
emojiSkinToneDefault: EmojiSkinTone;
213+
// StickerButton
214+
installedPacks: ReadonlyArray<StickerPackType>;
215+
recentStickers: ReadonlyArray<StickerType>;
210216
}>;
211217

212218
export type Props = Pick<
@@ -597,11 +603,12 @@ export const CompositionArea = memo(function CompositionArea({
597603

598604
const showMediaQualitySelector = draftAttachments.some(isImageAttachment);
599605

600-
const isFunPickerEnabled = RemoteConfig.isEnabled('desktop.funPicker');
606+
const [funPickerOpen, setFunPickerOpen] = useState(false);
601607

602608
const handleFunPickerOpenChange = useCallback(
603-
(isOpen: boolean) => {
604-
if (!isOpen) {
609+
(open: boolean) => {
610+
setFunPickerOpen(open);
611+
if (!open) {
605612
setComposerFocus(conversationId);
606613
}
607614
},
@@ -703,25 +710,22 @@ export const CompositionArea = memo(function CompositionArea({
703710
{i18n('icu:CompositionArea__ConfirmGifSelection__Body')}
704711
</ConfirmationDialog>
705712
)}
706-
{isFunPickerEnabled && (
713+
{isFunPickerEnabled() && (
707714
<div className="CompositionArea__button-cell">
708715
<FunPicker
709716
placement="top start"
717+
open={funPickerOpen}
710718
onOpenChange={handleFunPickerOpenChange}
711719
onSelectEmoji={handleFunPickerSelectEmoji}
712720
onSelectSticker={handleFunPickerSelectSticker}
713721
onSelectGif={handleFunPickerSelectGif}
714722
onAddStickerPack={handleFunPickerAddStickerPack}
715723
>
716-
<Button className="CompositionArea__FunButton">
717-
<VisuallyHidden>
718-
{i18n('icu:CompositionArea__FunButtonLabel')}
719-
</VisuallyHidden>
720-
</Button>
724+
<FunPickerButton i18n={i18n} />
721725
</FunPicker>
722726
</div>
723727
)}
724-
{!isFunPickerEnabled && (
728+
{!isFunPickerEnabled() && (
725729
<div className="CompositionArea__button-cell">
726730
<EmojiButton
727731
emojiButtonApi={emojiButtonRef}
@@ -731,6 +735,7 @@ export const CompositionArea = memo(function CompositionArea({
731735
recentEmojis={recentEmojis}
732736
emojiSkinToneDefault={emojiSkinToneDefault}
733737
onEmojiSkinToneDefaultChange={onEmojiSkinToneDefaultChange}
738+
closeOnPick
734739
/>
735740
</div>
736741
)}
@@ -809,7 +814,7 @@ export const CompositionArea = memo(function CompositionArea({
809814

810815
const stickerButtonPlacement = large ? 'top-start' : 'top-end';
811816
const stickerButtonFragment =
812-
!isFunPickerEnabled && !draftEditMessage && withStickers ? (
817+
!isFunPickerEnabled() && !draftEditMessage && withStickers ? (
813818
<div className="CompositionArea__button-cell">
814819
<StickerButton
815820
i18n={i18n}

0 commit comments

Comments
 (0)