UNPKG

@cometchat/chat-uikit-react-native

Version:

Ready-to-use Chat UI Components for React Native

179 lines (177 loc) 7.9 kB
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { FlatList, SafeAreaView, Text, TouchableOpacity, View, } from "react-native"; import { useTheme } from "../../../theme"; import { Icon } from "../../icons/Icon"; import { Emojis } from "./emojis"; import Styles from "./style"; /** * Mapping from category names to icon names. * Adjust these icons as per the assets available in your project. */ const categoryIconMapping = { Smileys: "smiley-emoji", Activity: "activity-emoji", Animals: "animals-nature-emoji", Flags: "flags-emoji", Food: "food-drink-emoji", Objects: "objects-emoji", Symbols: "symbols-emoji", Travel: "travel-emoji", }; /** * The number of columns in the emoji grid. * Adjust this value based on available space and desired layout. */ const NUM_COLUMNS = 8; /** * A memoized component displaying the category tabs (icons) at the bottom. */ const CategoryList = React.memo(({ onCategorySelected, activeCategory, categoryIconTint, selectedCategoryIconTint }) => { const theme = useTheme(); const styles = useMemo(() => Styles(theme), [theme]); return (<View style={styles.categoryListContainer}> {Emojis.map((category) => { // Each category object is expected to have a key and associated info. const key = Object.keys(category)[0]; const emojiCategory = category[key]; // Determine the appropriate icon for the category. const iconName = categoryIconMapping[emojiCategory.symbol] || "smiley-emoji"; const isActive = activeCategory === emojiCategory.id; return (<TouchableOpacity key={emojiCategory.id} onPress={() => onCategorySelected(emojiCategory.id)} accessibilityLabel={`Category ${emojiCategory.name}`} accessibilityRole='button'> <View style={[styles.iconContainer, isActive && styles.activeIcon]}> <Icon name={iconName} size={24} color={isActive ? selectedCategoryIconTint || theme.color.iconHighlight : categoryIconTint || theme.color.iconSecondary}/> </View> </TouchableOpacity>); })} </View>); }); /** * The main emoji keyboard component. It displays a header, a grid of emojis for the active category, * and category tabs at the bottom for switching between categories. */ const CometChatEmojiKeyboard = ({ onClick, style: { borderRadius, categoryBackground, categoryIconTint, selectedCategoryIconTint, sectionHeaderColor, sectionHeaderFont, backgroundColor, } = {}, }) => { const theme = useTheme(); const styles = Styles(theme); /** * ITEM_HEIGHT is assumed to be the height of one row or the uniform height per row. * If this assumption changes, `getItemLayout` might need to be removed or adjusted. */ const ITEM_HEIGHT = 40; /** * getItemLayout helps to optimize the FlatList performance by skipping layout calculations * for off-screen items. This works best when item sizes are fixed and known in advance. * Note: With multiple columns, ensure that ITEM_HEIGHT matches the row height. */ const getItemLayout = useCallback((_, index) => ({ length: ITEM_HEIGHT, offset: ITEM_HEIGHT * index, index, }), [ITEM_HEIGHT]); // State for the currently active category and header title. const [activeCategory, setActiveCategory] = useState(null); const [headerTitle, setHeaderTitle] = useState("Emojis"); // A ref to the FlatList for programmatic scrolling. const flatListRef = useRef(null); /** * Handle emoji press events by calling the provided `onClick` callback. */ const handleEmojiPress = useCallback((emoji) => { if (onClick) { onClick(emoji); } }, [onClick]); /** * Prepare sections of emojis. Each category is transformed into a Section object * containing the category id, title, and array of emojis. * * Memoized to prevent re-computation on every render unless `Emojis` changes. */ const sections = useMemo(() => { return Emojis.map((category) => { const key = Object.keys(category)[0]; const { id, name, emojis } = category[key]; const emojiArray = Object.values(emojis); return { id, title: name, data: emojiArray, }; }); }, []); /** * Set the initial active category and header title once sections are computed. */ useEffect(() => { if (sections.length > 0) { setActiveCategory(sections[0].id); setHeaderTitle(sections[0].title); } }, [sections]); /** * Handle category selection from the bottom tabs. * Updates the active category and resets the FlatList scroll to the top. */ const handleCategorySelect = useCallback((id) => { if (id !== activeCategory) { const category = sections.find((section) => section.id === id); if (category) { setActiveCategory(id); setHeaderTitle(category.title); if (flatListRef.current) { flatListRef.current.scrollToOffset({ offset: 0, animated: false }); } } } }, [sections, activeCategory]); /** * Render a single emoji item in the FlatList. * The callback is memoized to prevent unnecessary re-renders. */ const renderEmojiItem = useCallback(({ item }) => (<TouchableOpacity onPress={() => handleEmojiPress(item.char)} style={styles.emojiItem} accessibilityLabel={`Emoji ${item.char}`} accessibilityRole='button'> <Text style={[styles.emojiText, { color: theme.color.primary }]}>{item.char}</Text> </TouchableOpacity>), [handleEmojiPress, styles.emojiItem, styles.emojiText, theme.color.primary]); /** * Render the FlatList of emojis for the currently active category. * If no category is active, return null. */ const renderActiveCategory = () => { const category = sections.find((section) => section.id === activeCategory); if (!category) return null; return (<FlatList ref={flatListRef} data={category.data} getItemLayout={getItemLayout} keyExtractor={(item) => item.char} renderItem={renderEmojiItem} numColumns={NUM_COLUMNS} contentContainerStyle={styles.flatListContent} showsVerticalScrollIndicator={false} style={styles.flatList} initialNumToRender={80} removeClippedSubviews={true}/>); }; return (<SafeAreaView style={[ styles.emojiKeyboardContainer, { borderRadius: borderRadius || 0, backgroundColor: backgroundColor || theme.color.background1, flexDirection: "column", }, ]}> {/* Fixed Header displaying the active category title */} <View style={styles.fixedHeader}> <Text style={[ theme.typography.heading4.regular, sectionHeaderFont, { color: sectionHeaderColor || theme.color.textTertiary }, ]}> {headerTitle} </Text> </View> {/* Emoji Grid for the active category */} <View style={styles.emojiGridContainer}>{renderActiveCategory()}</View> {/* Bottom Category Tabs */} <View style={[ styles.categoryContainer, { backgroundColor: categoryBackground || theme.color.background1, }, ]}> <CategoryList onCategorySelected={handleCategorySelect} activeCategory={activeCategory || ""} categoryIconTint={categoryIconTint} selectedCategoryIconTint={selectedCategoryIconTint}/> </View> </SafeAreaView>); }; export { CometChatEmojiKeyboard }; //# sourceMappingURL=CometChatEmojiKeyboard.js.map