@cometchat/chat-uikit-react-native
Version:
Ready-to-use Chat UI Components for React Native
179 lines (177 loc) • 7.9 kB
JavaScript
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