UNPKG

@cometchat/chat-uikit-react-native

Version:

Ready-to-use Chat UI Components for React Native

197 lines (196 loc) 9.85 kB
import { CometChat } from "@cometchat/chat-sdk-react-native"; import React, { useCallback, useEffect, useRef, useState } from "react"; import { KeyboardAvoidingView, Platform, View } from "react-native"; import { CometChatList, localize, } from "../shared"; import { CometChatUIEventHandler } from "../shared/events/CometChatUIEventHandler/CometChatUIEventHandler"; import { deepMerge } from "../shared/helper/helperFunctions"; import { Icon } from "../shared/icons/Icon"; import { ErrorEmptyView } from "../shared/views/ErrorEmptyView/ErrorEmptyView"; import { useTheme } from "../theme"; import { Skeleton } from "./Skeleton"; import { CommonUtils } from "../shared/utils/CommonUtils"; import { CometChatTooltipMenu } from "../shared/views/CometChatTooltipMenu"; /** * CometChatUsers component renders a list of users with support for search, * selection, and custom empty/error/loading states. */ export const CometChatUsers = React.forwardRef((props, ref) => { const userListenerId = "userStatus_" + new Date().getTime(); const theme = useTheme(); const [hideSearchError, setHideSearchError] = useState(false); const { usersRequestBuilder, LoadingView, ErrorView, EmptyView, selectionMode = "none", title, addOptions, options, TrailingView, LeadingView, AppBarOptions, hideSearch = false, stickyHeaderVisibility = true, style = {}, onError, onLoad, onEmpty, hideError, hideLoadingState, usersStatusVisibility = true, showBackButton = false, searchKeyword = "", searchRequestBuilder, hideHeader, onSelection, onSubmit, searchPlaceholderText = localize("SEARCH"), ...newProps } = props; const userRef = useRef(null); const mergedStyle = deepMerge(theme.userStyles, style); // ----- Tooltip functionality for users ----- const [tooltipVisible, setTooltipVisible] = useState(false); const [selectedUser, setSelectedUser] = useState(null); const tooltipPosition = useRef({ pageX: 0, pageY: 0 }); const buildMenuItems = (user) => { if (options) return options(user); if (addOptions) return addOptions(user); return []; }; const handleItemLongPress = (user, e) => { if (props.onItemLongPress) { props.onItemLongPress(user); return; } const items = buildMenuItems(user); if (items.length === 0) return; if (e && e.nativeEvent) { tooltipPosition.current = { pageX: e.nativeEvent.pageX, pageY: e.nativeEvent.pageY, }; } else { tooltipPosition.current = { pageX: 200, pageY: 100 }; } setSelectedUser(user); setTooltipVisible(true); }; useEffect(() => { // Listen for changes in user online/offline status. CometChat.addUserListener(userListenerId, new CometChat.UserListener({ onUserOnline: (onlineUser) => { if (!onlineUser.getBlockedByMe()) { userRef.current.updateList(onlineUser); } }, onUserOffline: (offlineUser) => { if (!offlineUser.getBlockedByMe()) { userRef.current.updateList(offlineUser); } }, })); return () => CometChat.removeUserListener(userListenerId); }, []); /** * Handler for when a user is blocked. * * @param {{ user: CometChat.User }} param0 - The user object that is blocked. */ const handleccUserBlocked = ({ user }) => { const clonedUser = CommonUtils.clone(user); clonedUser.blockedByMe = true; clonedUser.hasBlockedMe = true; userRef.current.updateList(clonedUser); }; /** * Handler for when a user is unblocked. * * @param {{ user: CometChat.User }} param0 - The user object that is unblocked. */ const handleccUserUnBlocked = ({ user }) => { const clonedUser = CommonUtils.clone(user); clonedUser.blockedByMe = false; clonedUser.hasBlockedMe = false; userRef.current.updateList(clonedUser); CometChat.getUser(clonedUser.getUid()).then((updatedUser) => { userRef.current.updateList(updatedUser); }); }; useEffect(() => { CometChatUIEventHandler.addUserListener(userListenerId, { ccUserBlocked: (item) => handleccUserBlocked(item), ccUserUnBlocked: (item) => handleccUserUnBlocked(item), }); return () => { CometChatUIEventHandler.removeUserListener(userListenerId); }; }, []); /** * Renders the empty state view when there are no users. * * @returns {JSX.Element} The empty state view. */ const EmptyStateView = useCallback(() => { useEffect(() => { onEmpty?.(); }, []); return (<KeyboardAvoidingView keyboardVerticalOffset={Platform.OS === "ios" ? 90 : 0} behavior={Platform.select({ ios: "padding", android: "height" })} style={{ flex: 1 }}> <ErrorEmptyView title={localize("NO_USERS_AVAILABLE")} subTitle={localize("ADD_CONTACTS")} Icon={<Icon name='user-empty-icon' icon={mergedStyle.emptyStateStyle.icon} color={theme.color.neutral300} size={theme.spacing.spacing.s15 << 1} containerStyle={{ marginBottom: theme.spacing.spacing.s5, }}/>} containerStyle={{ flex: 1, justifyContent: "center", alignItems: "center", paddingHorizontal: "10%", }} titleStyle={mergedStyle.emptyStateStyle.titleStyle} subTitleStyle={mergedStyle.emptyStateStyle.subTitleStyle}/> </KeyboardAvoidingView>); }, [theme, mergedStyle]); /** * Renders the error state view when there is an error fetching users. * * @returns {JSX.Element} The error state view. */ const ErrorStateView = useCallback(() => { useEffect(() => { // Hide search when error view is active. setHideSearchError(true); }, []); return (<View style={{ flex: 1 }}> <ErrorEmptyView title={localize("OOPS")} subTitle={localize("SOMETHING_WENT_WRONG")} tertiaryTitle={localize("WRONG_TEXT_TRY_AGAIN")} Icon={<Icon name='error-state' size={theme.spacing.margin.m15 << 1} containerStyle={{ marginBottom: theme.spacing.margin.m5, }} icon={mergedStyle.errorStateStyle.icon}/>} containerStyle={{ flex: 1, justifyContent: "center", alignItems: "center", paddingHorizontal: "10%", }} titleStyle={mergedStyle.errorStateStyle.titleStyle} subTitleStyle={mergedStyle.errorStateStyle.subTitleStyle}/> </View>); }, [theme, mergedStyle]); return (<View style={theme.userStyles.containerStyle}> <CometChatList hideHeader={hideHeader ?? hideHeader} searchPlaceholderText={searchPlaceholderText} searchRequestBuilder={searchRequestBuilder} hideBackButton={!showBackButton} hideError={hideError} hideSubmitButton={props.hideSubmitButton} AppBarOptions={AppBarOptions} title={title ? title : localize("USERS")} onError={onError} TrailingView={TrailingView} selectionMode={selectionMode} LeadingView={LeadingView} onSelection={onSelection} onSubmit={onSubmit} // Pass our custom long press handler which shows tooltip if options exist. onItemLongPress={(user, e) => handleItemLongPress(user, e)} ItemView={props.ItemView} onListFetched={(users) => { if (users.length === 0) { onEmpty?.(); } else { onLoad?.(users); } }} ref={userRef} hideSearch={hideSearch ? hideSearch : hideSearchError} requestBuilder={(usersRequestBuilder && usersRequestBuilder.setSearchKeyword(searchKeyword)) || new CometChat.UsersRequestBuilder() .setLimit(30) .hideBlockedUsers(false) .setRoles([]) .friendsOnly(false) .setStatus("") .setTags([]) .setUIDs([]) .setSearchKeyword(searchKeyword)} listStyle={mergedStyle} hideStickyHeader={!stickyHeaderVisibility} listItemKey='uid' LoadingView={hideLoadingState ? () => <></> // will not render anything if true : LoadingView ? LoadingView : () => <Skeleton style={mergedStyle.skeletonStyle}/>} EmptyView={EmptyView ? EmptyView : () => <EmptyStateView />} ErrorView={ErrorView ? ErrorView : () => <ErrorStateView />} statusIndicatorType={(user) => usersStatusVisibility ? user?.getBlockedByMe() ? "offline" : user?.getStatus() : null} {...newProps}/> {/* Tooltip Menu: shows on long press if options exist and selectionMode is "none" */} {selectedUser && selectionMode === "none" && tooltipVisible && (<View style={{ position: "absolute", top: tooltipPosition.current.pageY, left: tooltipPosition.current.pageX, zIndex: 9999, }}> <CometChatTooltipMenu visible={tooltipVisible} onClose={() => setTooltipVisible(false)} onDismiss={() => setTooltipVisible(false)} event={{ nativeEvent: tooltipPosition.current, }} menuItems={buildMenuItems(selectedUser).map((menuItem) => ({ text: menuItem.text, onPress: () => { menuItem.onPress(); setTooltipVisible(false); }, textColor: menuItem.textColor, iconColor: menuItem.iconColor, disabled: menuItem.disabled, }))}/> </View>)} </View>); }); //# sourceMappingURL=CometChatUsers.js.map