Skip to content

Commit 05473bc

Browse files
committed
feat: implement emoji picker
1 parent cef3297 commit 05473bc

File tree

5 files changed

+413
-110
lines changed

5 files changed

+413
-110
lines changed

package/src/components/Channel/Channel.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,10 +204,14 @@ import { StickyHeader as StickyHeaderDefault } from '../MessageList/StickyHeader
204204
import { TypingIndicator as TypingIndicatorDefault } from '../MessageList/TypingIndicator';
205205
import { TypingIndicatorContainer as TypingIndicatorContainerDefault } from '../MessageList/TypingIndicatorContainer';
206206
import { UnreadMessagesNotification as UnreadMessagesNotificationDefault } from '../MessageList/UnreadMessagesNotification';
207+
import { emojis } from '../MessageMenu/emojis';
207208
import { MessageActionList as MessageActionListDefault } from '../MessageMenu/MessageActionList';
208209
import { MessageActionListItem as MessageActionListItemDefault } from '../MessageMenu/MessageActionListItem';
209210
import { MessageMenu as MessageMenuDefault } from '../MessageMenu/MessageMenu';
210-
import { MessageReactionPicker as MessageReactionPickerDefault } from '../MessageMenu/MessageReactionPicker';
211+
import {
212+
MessageReactionPicker as MessageReactionPickerDefault,
213+
toUnicodeScalarString,
214+
} from '../MessageMenu/MessageReactionPicker';
211215
import { MessageUserReactions as MessageUserReactionsDefault } from '../MessageMenu/MessageUserReactions';
212216
import { MessageUserReactionsAvatar as MessageUserReactionsAvatarDefault } from '../MessageMenu/MessageUserReactionsAvatar';
213217
import { MessageUserReactionsItem as MessageUserReactionsItemDefault } from '../MessageMenu/MessageUserReactionsItem';
@@ -247,6 +251,11 @@ export const reactionData: ReactionData[] = [
247251
Icon: WutReaction,
248252
type: 'wow',
249253
},
254+
...emojis.map((emoji) => ({
255+
Icon: () => <Text style={{ fontSize: 12, padding: 2 }}>{emoji}</Text>,
256+
isUnicode: true,
257+
type: toUnicodeScalarString(emoji),
258+
})),
250259
];
251260

252261
/**

package/src/components/MessageMenu/MessageReactionPicker.tsx

Lines changed: 93 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
import React from 'react';
2-
import { FlatList, StyleSheet, View } from 'react-native';
1+
import React, { useCallback, useMemo } from 'react';
2+
import { Pressable, StyleSheet, Text, View } from 'react-native';
3+
import { FlatList } from 'react-native-gesture-handler';
34

5+
import { emojis } from './emojis';
46
import { ReactionButton } from './ReactionButton';
57

68
import { scheduleActionOnClose } from '../../contexts';
@@ -12,9 +14,12 @@ import {
1214

1315
import { useOwnCapabilitiesContext } from '../../contexts/ownCapabilitiesContext/OwnCapabilitiesContext';
1416
import { useTheme } from '../../contexts/themeContext/ThemeContext';
17+
import { useStableCallback } from '../../hooks';
18+
import { Attach } from '../../icons';
1519
import { NativeHandlers } from '../../native';
1620

1721
import { ReactionData } from '../../utils/utils';
22+
import { BottomSheetModal } from '../UIComponents';
1823

1924
export type MessageReactionPickerProps = Pick<MessagesContextValue, 'supportedReactions'> &
2025
Pick<MessageContextValue, 'handleReaction' | 'dismissOverlay'> & {
@@ -29,6 +34,8 @@ export type ReactionPickerItemType = ReactionData & {
2934
ownReactionTypes: string[];
3035
};
3136

37+
const keyExtractor = (item: ReactionPickerItemType) => item.type;
38+
3239
const renderItem = ({ index, item }: { index: number; item: ReactionPickerItemType }) => (
3340
<ReactionButton
3441
Icon={item.Icon}
@@ -39,10 +46,21 @@ const renderItem = ({ index, item }: { index: number; item: ReactionPickerItemTy
3946
/>
4047
);
4148

49+
const emojiKeyExtractor = (item: string) => `unicode-${item}`;
50+
51+
// TODO: V9: Move this to utils and also clean it up a bit.
52+
// This was done quickly and in a bit of a hurry.
53+
export const toUnicodeScalarString = (emoji: string): string => {
54+
const out: number[] = [];
55+
for (const ch of emoji) out.push(ch.codePointAt(0)!);
56+
return out.map((cp) => `U+${cp.toString(16).toUpperCase().padStart(4, '0')}`).join('-');
57+
};
58+
4259
/**
4360
* MessageReactionPicker - A high level component which implements all the logic required for a message overlay reaction list
4461
*/
4562
export const MessageReactionPicker = (props: MessageReactionPickerProps) => {
63+
const [emojiViewerOpened, setEmojiViewerOpened] = React.useState<boolean | null>(null);
4664
const {
4765
dismissOverlay,
4866
handleReaction,
@@ -52,7 +70,7 @@ export const MessageReactionPicker = (props: MessageReactionPickerProps) => {
5270
const { supportedReactions: contextSupportedReactions } = useMessagesContext();
5371
const {
5472
theme: {
55-
colors: { white },
73+
colors: { white, grey },
5674
messageMenu: {
5775
reactionPicker: { container, contentContainer },
5876
},
@@ -62,25 +80,63 @@ export const MessageReactionPicker = (props: MessageReactionPickerProps) => {
6280

6381
const supportedReactions = propSupportedReactions || contextSupportedReactions;
6482

65-
const onSelectReaction = (type: string) => {
83+
const onSelectReaction = useStableCallback((type: string) => {
6684
NativeHandlers.triggerHaptic('impactLight');
85+
setEmojiViewerOpened(false);
6786
dismissOverlay();
6887
if (handleReaction) {
6988
scheduleActionOnClose(() => handleReaction(type));
7089
}
71-
};
90+
});
91+
92+
const onOpenEmojiViewer = useStableCallback(() => {
93+
NativeHandlers.triggerHaptic('impactLight');
94+
setEmojiViewerOpened(true);
95+
});
96+
97+
const EmojiViewerButton = useCallback(
98+
() => (
99+
<Pressable onPress={onOpenEmojiViewer} style={styles.emojiViewerButton}>
100+
<Attach fill={grey} size={32} />
101+
</Pressable>
102+
),
103+
[grey, onOpenEmojiViewer],
104+
);
105+
106+
const reactions: ReactionPickerItemType[] = useMemo(
107+
() =>
108+
supportedReactions
109+
?.filter((reaction) => !reaction.isUnicode)
110+
?.map((reaction) => ({
111+
...reaction,
112+
onSelectReaction,
113+
ownReactionTypes,
114+
})) ?? [],
115+
[onSelectReaction, ownReactionTypes, supportedReactions],
116+
);
117+
118+
const selectEmoji = useStableCallback((emoji: string) => {
119+
const scalarString = toUnicodeScalarString(emoji);
120+
onSelectReaction(scalarString);
121+
});
122+
123+
const closeModal = useStableCallback(() => setEmojiViewerOpened(false));
124+
125+
const renderEmoji = useCallback(
126+
({ item }: { item: string }) => {
127+
return (
128+
<Pressable onPress={() => selectEmoji(item)} style={styles.emojiContainer}>
129+
<Text style={styles.emojiText}>{item}</Text>
130+
</Pressable>
131+
);
132+
},
133+
[selectEmoji],
134+
);
72135

73136
if (!own_capabilities.sendReaction) {
74137
return null;
75138
}
76139

77-
const reactions: ReactionPickerItemType[] =
78-
supportedReactions?.map((reaction) => ({
79-
...reaction,
80-
onSelectReaction,
81-
ownReactionTypes,
82-
})) ?? [];
83-
84140
return (
85141
<View
86142
accessibilityLabel='Reaction Selector on long pressing message'
@@ -94,14 +150,35 @@ export const MessageReactionPicker = (props: MessageReactionPickerProps) => {
94150
]}
95151
data={reactions}
96152
horizontal
97-
keyExtractor={(item) => item.type}
153+
keyExtractor={keyExtractor}
154+
ListFooterComponent={EmojiViewerButton}
98155
renderItem={renderItem}
99156
/>
157+
{emojiViewerOpened ? (
158+
<BottomSheetModal height={300} onClose={closeModal} visible={true}>
159+
<FlatList
160+
columnWrapperStyle={styles.bottomSheetColumnWrapper}
161+
contentContainerStyle={styles.bottomSheetContentContainer}
162+
data={emojis}
163+
keyExtractor={emojiKeyExtractor}
164+
numColumns={4}
165+
renderItem={renderEmoji}
166+
style={styles.bottomSheet}
167+
/>
168+
</BottomSheetModal>
169+
) : null}
100170
</View>
101171
);
102172
};
103173

104174
const styles = StyleSheet.create({
175+
bottomSheet: { height: 300 },
176+
bottomSheetColumnWrapper: {
177+
alignItems: 'center',
178+
justifyContent: 'space-evenly',
179+
width: '100%',
180+
},
181+
bottomSheetContentContainer: { paddingVertical: 16 },
105182
container: {
106183
alignSelf: 'stretch',
107184
},
@@ -112,4 +189,7 @@ const styles = StyleSheet.create({
112189
marginVertical: 8,
113190
paddingHorizontal: 5,
114191
},
192+
emojiContainer: { height: 30 },
193+
emojiText: { fontSize: 20, padding: 2 },
194+
emojiViewerButton: { alignItems: 'flex-start', justifyContent: 'flex-start', paddingTop: 4 },
115195
});
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
// TODO: V9: This should really come from emoji mart or something else.
2+
// No reason to pollute the SDK like this. It'll have to do for now though,
3+
// as for the purposes of a PoC it's fine.
4+
export const emojis = [
5+
'😀',
6+
'😃',
7+
'😄',
8+
'😁',
9+
'😆',
10+
'😅',
11+
'🤣',
12+
'😂',
13+
'🙂',
14+
'🙃',
15+
'😉',
16+
'😊',
17+
'😇',
18+
'🥰',
19+
'😍',
20+
'🤩',
21+
'😘',
22+
'😗',
23+
'😚',
24+
'😙',
25+
'😋',
26+
'😛',
27+
'😜',
28+
'🤪',
29+
'😝',
30+
'🤑',
31+
'🤗',
32+
'🤭',
33+
'🤫',
34+
'🤔',
35+
'🤐',
36+
'🤨',
37+
'😐',
38+
'😑',
39+
'😶',
40+
'😶‍🌫️',
41+
'😏',
42+
'😒',
43+
'🙄',
44+
'😬',
45+
'🤥',
46+
'😌',
47+
'😔',
48+
'😪',
49+
'🤤',
50+
'😴',
51+
'😷',
52+
'🤒',
53+
'🤕',
54+
'🤢',
55+
'🤮',
56+
'🤧',
57+
'🥵',
58+
'🥶',
59+
'🥴',
60+
'😵‍💫',
61+
'🤯',
62+
'🤠',
63+
'🥳',
64+
'😎',
65+
'🤓',
66+
'🧐',
67+
'😕',
68+
'😟',
69+
'🙁',
70+
'☹️',
71+
'😮',
72+
'😯',
73+
'😲',
74+
'😳',
75+
'🥺',
76+
'😦',
77+
'😧',
78+
'😨',
79+
'😰',
80+
'😥',
81+
'😢',
82+
'😭',
83+
'😱',
84+
'😖',
85+
'😣',
86+
'😞',
87+
'😓',
88+
'😩',
89+
'😫',
90+
'🥱',
91+
'😤',
92+
'😡',
93+
'😠',
94+
'🤬',
95+
'😈',
96+
'👿',
97+
'💀',
98+
'☠️',
99+
'💩',
100+
'🤡',
101+
'👹',
102+
'👺',
103+
'👻',
104+
'👽',
105+
'👾',
106+
'🤖',
107+
'🎃',
108+
'😺',
109+
'😸',
110+
'😹',
111+
'😻',
112+
'😼',
113+
'😽',
114+
'🙀',
115+
'😿',
116+
'😾',
117+
'👍',
118+
'👎',
119+
'👌',
120+
'🤌',
121+
'🤏',
122+
'✌️',
123+
'🤞',
124+
'🤟',
125+
'🤘',
126+
'🤙',
127+
'👈',
128+
'👉',
129+
'👆',
130+
'👇',
131+
'☝️',
132+
'✋',
133+
'🤚',
134+
'🖐️',
135+
'🖖',
136+
'👋',
137+
'🤝',
138+
'🙏',
139+
'💪',
140+
'👣',
141+
'👀',
142+
'🧠',
143+
'🫶',
144+
'💋',
145+
'❤️',
146+
'🧡',
147+
'💛',
148+
'💚',
149+
'💙',
150+
'💜',
151+
'🖤',
152+
'🤍',
153+
'🤎',
154+
'💔',
155+
'❣️',
156+
'💕',
157+
'💞',
158+
'💓',
159+
'💗',
160+
'💖',
161+
'💘',
162+
'💝',
163+
];

0 commit comments

Comments
 (0)