Skip to content

Commit b4d23e5

Browse files
tharindu20118claude
andcommitted
feat: swap the hand-picked 60-emoji grid for a full search+category picker
Replaces the composer's hardcoded emoji list with a generated dataset (1,849 entries — github/gemoji, MIT, filtered to ios_version <= 15.4 for consistent cross-platform rendering) plus a new ChattyEmojiPicker with search and category tabs, matching the web widget's emoji-picker-react and the native SDKs' equivalents. FlatList-backed grid so ~1,850 entries don't all mount at once; data is a compiled-in constant, so nothing needs to be fetched. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1 parent b3387ea commit b4d23e5

9 files changed

Lines changed: 4007 additions & 74 deletions

‎README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ avatar, and composer with real `View`/`Text`/`FlatList` components — no `WebVi
2727
| **No WebView, anywhere** | Every bubble, avatar, and the composer are real RN components — no iframe, no JS bridge, no WebView memory/perf overhead. |
2828
| **Matches your dashboard automatically** | Fetches the bot's theme and renders with the exact colors, corner radii, and launcher shape chosen in the dashboard — no manual styling. |
2929
| **Three integration shapes** | A floating [`ChattyLauncher`](#chattylauncher), an embedded [`ChattyChatView`](#chattychatview), or the headless [`useChattyChat`](#usechattychat-headless) hook. |
30-
| **A real composer, not a stub** | Emoji picker and an animated attach menu, built in — camera/photo/mic wire up to whatever picker your app already uses. |
30+
| **A real composer, not a stub** | Full-Unicode emoji picker (search + categories, ~1,850 emoji) and an animated attach menu, built in — camera/photo/mic wire up to whatever picker your app already uses. |
3131
| **Works with bare RN and Expo** | No native linking required beyond `@react-native-async-storage/async-storage`, which most apps already have. |
3232

3333
## Install

‎dist/ChattyChatView.js‎

Lines changed: 2 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,10 @@ import React, { useEffect, useRef, useState } from "react";
22
import { View, Text, TextInput, TouchableOpacity, FlatList, Image, StyleSheet, KeyboardAvoidingView, Platform, ActivityIndicator, Linking, Animated, LayoutAnimation, UIManager, PermissionsAndroid, } from "react-native";
33
import { useChattyChat } from "./useChattyChat";
44
import { CHATTY_DESIGN_TOKENS, chattyNormalizeWidgetStyle, chattyBubbleRadii } from "./designTokens";
5+
import { ChattyEmojiPicker } from "./ChattyEmojiPicker";
56
if (Platform.OS === "android" && UIManager.setLayoutAnimationEnabledExperimental) {
67
UIManager.setLayoutAnimationEnabledExperimental(true);
78
}
8-
const CHATTY_EMOJIS = [
9-
"😀", "😃", "😄", "😁", "😆", "😅", "😂", "🙂", "🙃", "😉", "😊", "😇",
10-
"🥰", "😍", "🤩", "😘", "😋", "😛", "🤪", "😜", "🤔", "🤨", "😐", "😑",
11-
"😶", "🙄", "😏", "😒", "😬", "🙁", "😢", "😭", "😤", "😡", "🥳", "😴",
12-
"🤗", "🤝", "👍", "👎", "👏", "🙌", "🙏", "💪", "👋", "✌️", "🤞", "❤️",
13-
"🔥", "✨", "🎉", "🎊", "⭐", "💯", "✅", "❌", "❓", "❗", "💬", "👀",
14-
];
159
/** bot/logo(no logoUrl)/custom(no avatarUrl) fall back to a generic chat
1610
* glyph; the rest map 1:1 to the dashboard's avatar_icon options. */
1711
function avatarGlyph(avatarIcon) {
@@ -154,11 +148,7 @@ export function ChattyChatView(props) {
154148
{/* Bordered rounded-16 bar with the input on top and an icon row
155149
(emoji + attach + mic + send) below, matching .chat-input-bar on web. */}
156150
<View style={[styles.composer, { borderColor: withAlpha(t.headerText, 0.12) }]}>
157-
{showEmojiPicker && (<View style={styles.emojiGrid}>
158-
{CHATTY_EMOJIS.map((emoji, i) => (<TouchableOpacity key={i} style={styles.emojiCell} onPress={() => setInput((v) => v + emoji)}>
159-
<Text style={{ fontSize: 18 }}>{emoji}</Text>
160-
</TouchableOpacity>))}
161-
</View>)}
151+
{showEmojiPicker && <ChattyEmojiPicker onPick={(emoji) => setInput((v) => v + emoji)}/>}
162152
{showAttachMenu && (<View style={styles.attachMenu}>
163153
<AttachMenuOption glyph="📷" label="Camera" onPress={() => {
164154
setShowAttachMenu(false);
@@ -351,28 +341,6 @@ const styles = StyleSheet.create({
351341
paddingTop: 10,
352342
paddingBottom: 6,
353343
},
354-
emojiGrid: {
355-
flexDirection: "row",
356-
flexWrap: "wrap",
357-
maxHeight: 160,
358-
marginBottom: 8,
359-
padding: 6,
360-
backgroundColor: "#fff",
361-
borderRadius: 16,
362-
borderWidth: 1,
363-
borderColor: "#e5e7eb",
364-
shadowColor: "#000",
365-
shadowOpacity: 0.18,
366-
shadowRadius: 12,
367-
shadowOffset: { width: 0, height: 4 },
368-
elevation: 6,
369-
},
370-
emojiCell: {
371-
width: "12.5%",
372-
aspectRatio: 1,
373-
alignItems: "center",
374-
justifyContent: "center",
375-
},
376344
attachMenu: {
377345
flexDirection: "row",
378346
gap: 12,

‎dist/ChattyEmojiPicker.d.ts‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import React from "react";
2+
/**
3+
* Search field + category tabs + scrollable grid over the full generated
4+
* emoji dataset (chattyEmojiData.ts) — the "real emoji library" experience
5+
* (matching the web widget's emoji-picker-react and the Android SDK's
6+
* androidx.emoji2 EmojiPickerView), not the old fixed ~60-emoji grid.
7+
*
8+
* A FlatList (not a plain mapped View) backs the grid since the full/
9+
* unfiltered category can be a few hundred entries — FlatList only mounts
10+
* what's on/near screen instead of ~1,850 TouchableOpacity instances at once.
11+
* The dataset itself is a compiled-in constant, so there's nothing to fetch —
12+
* this renders synchronously the moment its parent shows it.
13+
*/
14+
export declare function ChattyEmojiPicker({ onPick }: {
15+
onPick: (emoji: string) => void;
16+
}): React.JSX.Element;

‎dist/ChattyEmojiPicker.js‎

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import React, { useMemo, useState } from "react";
2+
import { View, Text, TextInput, TouchableOpacity, FlatList, StyleSheet } from "react-native";
3+
import { CHATTY_EMOJI_CATEGORIES, CHATTY_EMOJI_DATA } from "./chattyEmojiData";
4+
const CATEGORY_GLYPH = {
5+
"Smileys & Emotion": "😀",
6+
"People & Body": "🙌",
7+
"Animals & Nature": "🐾",
8+
"Food & Drink": "🍔",
9+
"Travel & Places": "✈️",
10+
"Activities": "🎮",
11+
"Objects": "💡",
12+
"Symbols": "🔣",
13+
"Flags": "🏳️",
14+
};
15+
/**
16+
* Search field + category tabs + scrollable grid over the full generated
17+
* emoji dataset (chattyEmojiData.ts) — the "real emoji library" experience
18+
* (matching the web widget's emoji-picker-react and the Android SDK's
19+
* androidx.emoji2 EmojiPickerView), not the old fixed ~60-emoji grid.
20+
*
21+
* A FlatList (not a plain mapped View) backs the grid since the full/
22+
* unfiltered category can be a few hundred entries — FlatList only mounts
23+
* what's on/near screen instead of ~1,850 TouchableOpacity instances at once.
24+
* The dataset itself is a compiled-in constant, so there's nothing to fetch —
25+
* this renders synchronously the moment its parent shows it.
26+
*/
27+
export function ChattyEmojiPicker({ onPick }) {
28+
const [query, setQuery] = useState("");
29+
const [category, setCategory] = useState(CHATTY_EMOJI_CATEGORIES[0]);
30+
const isSearching = query.trim().length > 0;
31+
const visible = useMemo(() => {
32+
if (isSearching) {
33+
const q = query.trim().toLowerCase();
34+
return CHATTY_EMOJI_DATA.filter((e) => e.name.toLowerCase().includes(q) || e.tags.some((t) => t.toLowerCase().includes(q)));
35+
}
36+
return CHATTY_EMOJI_DATA.filter((e) => e.category === category);
37+
}, [query, category, isSearching]);
38+
return (<View style={styles.container}>
39+
<View style={styles.searchRow}>
40+
<Text style={styles.searchIcon}>🔍</Text>
41+
<TextInput value={query} onChangeText={setQuery} placeholder="Search emoji…" placeholderTextColor="#9ca3af" style={styles.searchInput} autoCapitalize="none" autoCorrect={false}/>
42+
</View>
43+
44+
{!isSearching && (<View style={styles.categoryRow}>
45+
<FlatList horizontal showsHorizontalScrollIndicator={false} data={CHATTY_EMOJI_CATEGORIES} keyExtractor={(c) => c} renderItem={({ item }) => (<TouchableOpacity onPress={() => setCategory(item)} style={[styles.categoryTab, item === category && styles.categoryTabActive]}>
46+
<Text style={{ fontSize: 15 }}>{CATEGORY_GLYPH[item] ?? "•"}</Text>
47+
</TouchableOpacity>)} contentContainerStyle={{ paddingHorizontal: 4 }}/>
48+
</View>)}
49+
50+
<FlatList key="emoji-grid-8col" data={visible} keyExtractor={(e, i) => `${e.emoji}-${i}`} numColumns={8} renderItem={({ item }) => (<TouchableOpacity style={styles.cell} onPress={() => onPick(item.emoji)}>
51+
<Text style={{ fontSize: 18 }}>{item.emoji}</Text>
52+
</TouchableOpacity>)} style={styles.grid} contentContainerStyle={{ padding: 4 }} initialNumToRender={64} windowSize={5}/>
53+
</View>);
54+
}
55+
const styles = StyleSheet.create({
56+
container: {
57+
height: 320,
58+
marginBottom: 8,
59+
backgroundColor: "#fff",
60+
borderRadius: 16,
61+
borderWidth: 1,
62+
borderColor: "#e5e7eb",
63+
overflow: "hidden",
64+
shadowColor: "#000",
65+
shadowOpacity: 0.18,
66+
shadowRadius: 12,
67+
shadowOffset: { width: 0, height: 4 },
68+
elevation: 6,
69+
},
70+
searchRow: {
71+
flexDirection: "row",
72+
alignItems: "center",
73+
gap: 6,
74+
paddingHorizontal: 10,
75+
paddingVertical: 7,
76+
backgroundColor: "#f7f7f8",
77+
},
78+
searchIcon: { fontSize: 12 },
79+
searchInput: { flex: 1, fontSize: 12, color: "#1f2937", padding: 0 },
80+
categoryRow: {
81+
borderBottomWidth: 1,
82+
borderBottomColor: "#f0f0f1",
83+
paddingVertical: 4,
84+
},
85+
categoryTab: {
86+
width: 32,
87+
height: 32,
88+
borderRadius: 16,
89+
alignItems: "center",
90+
justifyContent: "center",
91+
marginHorizontal: 2,
92+
},
93+
categoryTabActive: { backgroundColor: "#eef0f2" },
94+
grid: { flex: 1 },
95+
cell: {
96+
width: "12.5%",
97+
aspectRatio: 1,
98+
alignItems: "center",
99+
justifyContent: "center",
100+
},
101+
});

‎dist/chattyEmojiData.d.ts‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export interface ChattyEmojiEntry {
2+
emoji: string;
3+
name: string;
4+
category: string;
5+
tags: string[];
6+
}
7+
export declare const CHATTY_EMOJI_CATEGORIES: string[];
8+
export declare const CHATTY_EMOJI_DATA: ChattyEmojiEntry[];

0 commit comments

Comments
 (0)