From e9ef7e9b4715e49a41802453aae95f283917e03b Mon Sep 17 00:00:00 2001 From: bang9 Date: Tue, 29 Nov 2022 04:47:36 +0900 Subject: [PATCH] feat(uikit): added reaction user list bottom sheet --- .../src/components/Modal/index.tsx | 13 +- .../src/theme/DarkUIKitTheme.ts | 4 +- .../src/components/MessageRenderer/index.tsx | 4 +- .../BottomSheetReactionAddon.tsx | 17 +- .../ReactionAddons/MessageReactionAddon.tsx | 19 +- .../src/components/ReactionAddons/index.tsx | 9 +- .../ReactionListBottomSheet.tsx} | 52 +--- .../ReactionUserListBottomSheet.tsx | 249 ++++++++++++++++++ .../components/ReactionBottomSheets/index.tsx | 24 ++ .../src/contexts/ReactionCtx.tsx | 66 +++-- .../component/GroupChannelMessageList.tsx | 4 +- packages/uikit-utils/src/sendbird/message.ts | 5 + 12 files changed, 373 insertions(+), 93 deletions(-) rename packages/uikit-react-native/src/components/{ReactionBottomSheets.tsx => ReactionBottomSheets/ReactionListBottomSheet.tsx} (67%) create mode 100644 packages/uikit-react-native/src/components/ReactionBottomSheets/ReactionUserListBottomSheet.tsx create mode 100644 packages/uikit-react-native/src/components/ReactionBottomSheets/index.tsx diff --git a/packages/uikit-react-native-foundation/src/components/Modal/index.tsx b/packages/uikit-react-native-foundation/src/components/Modal/index.tsx index 00af021d3..3cbe04667 100644 --- a/packages/uikit-react-native-foundation/src/components/Modal/index.tsx +++ b/packages/uikit-react-native-foundation/src/components/Modal/index.tsx @@ -19,8 +19,9 @@ import createStyleSheet from '../../styles/createStyleSheet'; import useHeaderStyle from '../../styles/useHeaderStyle'; import useUIKitTheme from '../../theme/useUIKitTheme'; +type ModalAnimationType = 'slide' | 'slide-no-gesture' | 'fade'; type Props = { - type?: 'slide' | 'fade'; + type?: ModalAnimationType; onClose: () => void; backgroundStyle?: StyleProp; disableBackgroundClose?: boolean; @@ -117,12 +118,12 @@ const isHideGesture = (distanceY: number, velocityY: number) => { return distanceY > 125 || (distanceY > 0 && velocityY > 0.1); }; const useModalPanResponder = ( - type: 'slide' | 'fade', + type: ModalAnimationType, translateY: Animated.Value, show: () => void, hide: () => void, ) => { - if (type === 'fade') return { panHandlers: {} }; + if (type === 'fade' || type === 'slide-no-gesture') return { panHandlers: {} }; return React.useRef( PanResponder.create({ onMoveShouldSetPanResponderCapture: (_, { dy }) => dy > 8, @@ -137,15 +138,15 @@ const useModalPanResponder = ( ).current; }; -const useModalAnimation = (type: 'slide' | 'fade') => { - const initialY = type === 'slide' ? Dimensions.get('window').height : 0; +const useModalAnimation = (type: ModalAnimationType) => { + const initialY = type === 'fade' ? 0 : Dimensions.get('window').height; const baseAnimBackground = useRef(new Animated.Value(0)).current; const baseAnimContent = useRef(new Animated.Value(initialY)).current; const content = { opacity: baseAnimBackground.interpolate({ inputRange: [0, 1], - outputRange: [type === 'slide' ? 1 : 0, 1], + outputRange: [type === 'fade' ? 0 : 1, 1], }), translateY: baseAnimContent, }; diff --git a/packages/uikit-react-native-foundation/src/theme/DarkUIKitTheme.ts b/packages/uikit-react-native-foundation/src/theme/DarkUIKitTheme.ts index b8497390a..2261d2da2 100644 --- a/packages/uikit-react-native-foundation/src/theme/DarkUIKitTheme.ts +++ b/packages/uikit-react-native-foundation/src/theme/DarkUIKitTheme.ts @@ -186,7 +186,7 @@ const DarkUIKitTheme = createTheme({ highlight: palette.onBackgroundDark03, }, selected: { - background: palette.primary400, + background: palette.primary500, highlight: palette.primary200, }, }, @@ -196,7 +196,7 @@ const DarkUIKitTheme = createTheme({ highlight: palette.transparent, }, selected: { - background: palette.primary400, + background: palette.primary500, highlight: palette.transparent, }, }, diff --git a/packages/uikit-react-native/src/components/MessageRenderer/index.tsx b/packages/uikit-react-native/src/components/MessageRenderer/index.tsx index 9e97e67a9..7062282b7 100644 --- a/packages/uikit-react-native/src/components/MessageRenderer/index.tsx +++ b/packages/uikit-react-native/src/components/MessageRenderer/index.tsx @@ -14,7 +14,7 @@ import { import { DEFAULT_LONG_PRESS_DELAY } from '../../constants'; import type { GroupChannelProps } from '../../domain/groupChannel/types'; import { useSendbirdChat } from '../../hooks/useContext'; -import { MessageReactionAddon } from '../ReactionAddons'; +import { ReactionAddons } from '../ReactionAddons'; import AdminMessage from './AdminMessage'; import FileMessage from './FileMessage'; import MessageContainer from './MessageContainer'; @@ -62,7 +62,7 @@ const MessageRenderer: GroupChannelProps['Fragment']['renderMessage'] = ({ const reactionChildren = useIIFE(() => { if (shouldRenderReaction(channel, features.reactionEnabled) && message.reactions && message.reactions.length > 0) { - return ; + return ; } return null; }); diff --git a/packages/uikit-react-native/src/components/ReactionAddons/BottomSheetReactionAddon.tsx b/packages/uikit-react-native/src/components/ReactionAddons/BottomSheetReactionAddon.tsx index 4f733ba32..414749763 100644 --- a/packages/uikit-react-native/src/components/ReactionAddons/BottomSheetReactionAddon.tsx +++ b/packages/uikit-react-native/src/components/ReactionAddons/BottomSheetReactionAddon.tsx @@ -41,19 +41,20 @@ const BottomSheetReactionAddon = ({ onClose, message, channel }: Props) => { return ( {emojiAll.map(({ key, url }) => { - const reactedUserIds = message?.reactions?.find((it) => it.key === key)?.userIds ?? []; + const reactionUserIds = message?.reactions?.find((it) => it.key === key)?.userIds ?? []; + const currentUserIdx = reactionUserIds.indexOf(currentUser?.userId ?? UNKNOWN_USER_ID); + const reacted = currentUserIdx > -1; - const idx = reactedUserIds.indexOf(currentUser?.userId ?? UNKNOWN_USER_ID); - const reacted = idx > -1; + const onPress = () => { + if (reacted) channel.deleteReaction(message, key); + else channel.addReaction(message, key); + onClose(); + }; return ( { - if (reacted) channel.deleteReaction(message, key); - else channel.addReaction(message, key); - onClose(); - }} + onPress={onPress} style={({ pressed }) => [ styles.button, { backgroundColor: reacted || pressed ? color.selected.background : color.enabled.background }, diff --git a/packages/uikit-react-native/src/components/ReactionAddons/MessageReactionAddon.tsx b/packages/uikit-react-native/src/components/ReactionAddons/MessageReactionAddon.tsx index 3854ec428..c8469d87b 100644 --- a/packages/uikit-react-native/src/components/ReactionAddons/MessageReactionAddon.tsx +++ b/packages/uikit-react-native/src/components/ReactionAddons/MessageReactionAddon.tsx @@ -4,16 +4,17 @@ import { Pressable } from 'react-native'; import type { Emoji } from '@sendbird/chat'; import { createStyleSheet, useUIKitTheme } from '@sendbird/uikit-react-native-foundation'; import type { SendbirdBaseChannel, SendbirdBaseMessage, SendbirdReaction } from '@sendbird/uikit-utils'; +import { getReactionCount } from '@sendbird/uikit-utils'; -import { UNKNOWN_USER_ID } from '../../constants'; +import { DEFAULT_LONG_PRESS_DELAY, UNKNOWN_USER_ID } from '../../constants'; import { useReaction, useSendbirdChat } from '../../hooks/useContext'; import ReactionRoundedButton from './ReactionRoundedButton'; const NUM_COL = 4; const REACTION_MORE_KEY = 'reaction-more-button'; -const getUserReacted = (reaction: SendbirdReaction, userId?: string) => { - return reaction.userIds.indexOf(userId ?? UNKNOWN_USER_ID) > -1; +const getUserReacted = (reaction: SendbirdReaction, userId = UNKNOWN_USER_ID) => { + return reaction.userIds.indexOf(userId) > -1; }; const createOnPressReaction = ( @@ -36,7 +37,8 @@ const createReactionButtons = ( message: SendbirdBaseMessage, getEmoji: (key: string) => Emoji, emojiLimit: number, - onPressMore: () => void, + onOpenReactionList: () => void, + onOpenReactionUserList: (focusIndex: number) => void, currentUserId?: string, ) => { const reactions = message.reactions ?? []; @@ -47,11 +49,13 @@ const createReactionButtons = ( onOpenReactionUserList(index)} + delayLongPress={DEFAULT_LONG_PRESS_DELAY} > {({ pressed }) => ( @@ -61,7 +65,7 @@ const createReactionButtons = ( }); if (buttons.length < emojiLimit) { buttons.push( - + {({ pressed }) => } , ); @@ -73,7 +77,7 @@ const createReactionButtons = ( const MessageReactionAddon = ({ channel, message }: { channel: SendbirdBaseChannel; message: SendbirdBaseMessage }) => { const { colors } = useUIKitTheme(); const { emojiManager, currentUser } = useSendbirdChat(); - const { openReactionList } = useReaction(); + const { openReactionList, openReactionUserList } = useReaction(); if (!message.reactions?.length) return null; @@ -83,6 +87,7 @@ const MessageReactionAddon = ({ channel, message }: { channel: SendbirdBaseChann (key) => emojiManager.allEmojiMap[key], emojiManager.allEmoji.length, () => openReactionList({ channel, message }), + (focusIndex) => openReactionUserList({ channel, message, focusIndex }), currentUser?.userId, ); diff --git a/packages/uikit-react-native/src/components/ReactionAddons/index.tsx b/packages/uikit-react-native/src/components/ReactionAddons/index.tsx index 05b5a8bdc..92b7443a0 100644 --- a/packages/uikit-react-native/src/components/ReactionAddons/index.tsx +++ b/packages/uikit-react-native/src/components/ReactionAddons/index.tsx @@ -1,2 +1,7 @@ -export { default as BottomSheetReactionAddon } from './BottomSheetReactionAddon'; -export { default as MessageReactionAddon } from './MessageReactionAddon'; +import BottomSheet from './BottomSheetReactionAddon'; +import Message from './MessageReactionAddon'; + +export const ReactionAddons = { + BottomSheet, + Message, +}; diff --git a/packages/uikit-react-native/src/components/ReactionBottomSheets.tsx b/packages/uikit-react-native/src/components/ReactionBottomSheets/ReactionListBottomSheet.tsx similarity index 67% rename from packages/uikit-react-native/src/components/ReactionBottomSheets.tsx rename to packages/uikit-react-native/src/components/ReactionBottomSheets/ReactionListBottomSheet.tsx index cdf451736..ed47e1c0a 100644 --- a/packages/uikit-react-native/src/components/ReactionBottomSheets.tsx +++ b/packages/uikit-react-native/src/components/ReactionBottomSheets/ReactionListBottomSheet.tsx @@ -4,51 +4,8 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { Image, Modal, createStyleSheet, useUIKitTheme } from '@sendbird/uikit-react-native-foundation'; -import { UNKNOWN_USER_ID } from '../constants'; -import type { ReactionContext } from '../contexts/ReactionCtx'; -import type { SendbirdChatContext } from '../contexts/SendbirdChatCtx'; - -type GetFromContext = T extends React.Context ? NonNullable : never; -type ReactionBottomSheetProps = { - visible: boolean; - onDismiss: () => void; - onClose: () => void; - chatCtx: GetFromContext; - reactionCtx: GetFromContext; -}; - -const ReactionUserListBottomSheet = ({ visible, onClose, onDismiss, reactionCtx }: ReactionBottomSheetProps) => { - const { width } = useWindowDimensions(); - const { bottom, left, right } = useSafeAreaInsets(); - const { colors } = useUIKitTheme(); - - // const { currentUser, emojiManager } = chatCtx; - const { channel, message } = reactionCtx; - // const color = colors.ui.reaction.default; - - return ( - - - - ); -}; +import { UNKNOWN_USER_ID } from '../../constants'; +import type { ReactionBottomSheetProps } from './index'; const NUM_COLUMN = 6; const ReactionListBottomSheet = ({ visible, onClose, onDismiss, reactionCtx, chatCtx }: ReactionBottomSheetProps) => { @@ -153,7 +110,4 @@ const styles = createStyleSheet({ }, }); -export const ReactionBottomSheets = { - ReactionList: ReactionListBottomSheet, - UserList: ReactionUserListBottomSheet, -}; +export default ReactionListBottomSheet; diff --git a/packages/uikit-react-native/src/components/ReactionBottomSheets/ReactionUserListBottomSheet.tsx b/packages/uikit-react-native/src/components/ReactionBottomSheets/ReactionUserListBottomSheet.tsx new file mode 100644 index 000000000..5c3b5b352 --- /dev/null +++ b/packages/uikit-react-native/src/components/ReactionBottomSheets/ReactionUserListBottomSheet.tsx @@ -0,0 +1,249 @@ +import React, { useEffect, useRef, useState } from 'react'; +import { Animated, Easing, Pressable, ScrollView, View, useWindowDimensions } from 'react-native'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; + +import { + Avatar, + Divider, + Image, + Modal, + Text, + createStyleSheet, + useUIKitTheme, +} from '@sendbird/uikit-react-native-foundation'; +import { SendbirdReaction, getReactionCount, truncatedCount } from '@sendbird/uikit-utils'; + +import type { ReactionBottomSheetProps } from './index'; + +const ReactionUserListBottomSheet = ({ + visible, + onClose, + onDismiss, + reactionCtx, + chatCtx, + localizationCtx, + userProfileCtx, +}: ReactionBottomSheetProps) => { + const { width } = useWindowDimensions(); + const { bottom, left, right } = useSafeAreaInsets(); + const { colors } = useUIKitTheme(); + + const [tabIndex, setTabIndex] = useState(0); + const scrollRef = useRef(); + const tabIndicatorValue = useRef>([]); + const tabIndicatorAnimated = useRef({ x: new Animated.Value(0), width: new Animated.Value(0) }).current; + const focusedWithLayoutCalculated = useRef(false); + + const { emojiManager } = chatCtx; + const { channel, message, focusIndex } = reactionCtx; + const { STRINGS } = localizationCtx; + + const color = colors.ui.reaction.default; + const reactions = message?.reactions ?? []; + const focusedReaction = reactions[tabIndex] as SendbirdReaction | undefined; + const containerSafeArea = { + paddingLeft: left + styles.layout.paddingHorizontal, + paddingRight: right + styles.layout.paddingHorizontal, + }; + + const focusTab = (index: number, animated = true) => { + const indicatorValue = tabIndicatorValue.current[index]; + if (indicatorValue) { + setTabIndex(index); + animateTabIndicator(indicatorValue.x, indicatorValue.width, animated); + scrollRef.current?.scrollTo({ x: indicatorValue.x, animated }); + } + }; + + const animateTabIndicator = (x: number, width: number, animated = true) => { + const baseConfig = { duration: animated ? 300 : 0, easing: Easing.inOut(Easing.ease), useNativeDriver: false }; + Animated.parallel([ + Animated.timing(tabIndicatorAnimated.x, { toValue: x, ...baseConfig }), + Animated.timing(tabIndicatorAnimated.width, { toValue: width, ...baseConfig }), + ]).start(); + }; + + const layoutCalculated = () => { + return tabIndicatorValue.current.length === reactions.length && tabIndicatorValue.current.every(Boolean); + }; + + useEffect(() => { + if (!visible) { + tabIndicatorValue.current = []; + tabIndicatorAnimated.x = new Animated.Value(0); + tabIndicatorAnimated.width = new Animated.Value(0); + focusedWithLayoutCalculated.current = false; + } + }, [visible]); + + const renderTabs = () => { + return ( + + {reactions.map((reaction, index) => { + const isFocused = focusedReaction?.key === reaction.key; + const isLastItem = reactions.length - 1 === index; + const emoji = emojiManager.allEmojiMap[reaction.key]; + + return ( + focusTab(index)} + onLayout={(e) => { + tabIndicatorValue.current[index] = e.nativeEvent.layout; + if (layoutCalculated()) { + if (focusedWithLayoutCalculated.current) { + focusTab(tabIndex, false); + } else { + focusedWithLayoutCalculated.current = true; + focusTab(focusIndex); + } + } + }} + > + + + {truncatedCount(getReactionCount(reaction))} + + + ); + })} + + + ); + }; + + const renderPage = () => { + return ( + <> + {focusedReaction?.userIds.map((userId) => { + if (channel?.isGroupChannel()) { + const user = channel.members.find((x) => x.userId === userId); + return ( + { + if (user) { + await onClose(); + userProfileCtx.show(user); + } + }} + style={styles.pageItem} + > + + + {user?.nickname || STRINGS.LABELS.USER_NO_NAME} + + + ); + } + return null; + })} + + ); + }; + + return ( + + + + {renderTabs()} + + + + {renderPage()} + + + + ); +}; + +const styles = createStyleSheet({ + layout: { + paddingHorizontal: 16, + marginRight: 0, + }, + container: { + overflow: 'hidden', + borderTopLeftRadius: 8, + borderTopRightRadius: 8, + paddingTop: 16, + alignItems: 'center', + }, + modal: { + alignItems: 'center', + justifyContent: 'flex-end', + }, + tabsContainer: { + flexGrow: 1, + }, + tabsWrapper: { + flexGrow: 1, + flexDirection: 'row', + alignItems: 'flex-start', + justifyContent: 'center', + height: 44, + }, + tabItem: { + marginRight: 16, + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + }, + tabEmoji: { + width: 28, + height: 28, + marginRight: 4, + }, + tabIndicator: { + position: 'absolute', + bottom: 0, + height: 3, + }, + pageContainer: { + height: 216, + width: '100%', + }, + pageItem: { + flexDirection: 'row', + width: '100%', + height: 48, + alignItems: 'center', + }, + avatar: { + marginRight: 16, + }, +}); + +export default ReactionUserListBottomSheet; diff --git a/packages/uikit-react-native/src/components/ReactionBottomSheets/index.tsx b/packages/uikit-react-native/src/components/ReactionBottomSheets/index.tsx new file mode 100644 index 000000000..e17c2b2e4 --- /dev/null +++ b/packages/uikit-react-native/src/components/ReactionBottomSheets/index.tsx @@ -0,0 +1,24 @@ +import type React from 'react'; + +import type { LocalizationContext } from '../../contexts/LocalizationCtx'; +import type { ProfileCardContext } from '../../contexts/ProfileCardCtx'; +import type { ReactionContext } from '../../contexts/ReactionCtx'; +import type { SendbirdChatContext } from '../../contexts/SendbirdChatCtx'; +import ReactionList from './ReactionListBottomSheet'; +import UserList from './ReactionUserListBottomSheet'; + +type GetFromContext = T extends React.Context ? NonNullable : never; +export type ReactionBottomSheetProps = { + visible: boolean; + onDismiss: () => void; + onClose: () => Promise; + chatCtx: GetFromContext; + reactionCtx: GetFromContext; + localizationCtx: GetFromContext; + userProfileCtx: GetFromContext; +}; + +export const ReactionBottomSheets = { + ReactionList, + UserList, +}; diff --git a/packages/uikit-react-native/src/contexts/ReactionCtx.tsx b/packages/uikit-react-native/src/contexts/ReactionCtx.tsx index 492a17151..538f54e4c 100644 --- a/packages/uikit-react-native/src/contexts/ReactionCtx.tsx +++ b/packages/uikit-react-native/src/contexts/ReactionCtx.tsx @@ -1,8 +1,10 @@ -import React, { useCallback, useContext, useReducer, useState } from 'react'; +import React, { useCallback, useContext, useReducer, useRef, useState } from 'react'; import type { SendbirdBaseChannel, SendbirdBaseMessage } from '@sendbird/uikit-utils'; import { ReactionBottomSheets } from '../components/ReactionBottomSheets'; +import { LocalizationContext } from '../contexts/LocalizationCtx'; +import { ProfileCardContext } from '../contexts/ProfileCardCtx'; import { SendbirdChatContext } from '../contexts/SendbirdChatCtx'; type State = { @@ -11,8 +13,9 @@ type State = { }; export type ReactionContextType = { openReactionList(param: Required): void; - openReactionUserList(param: Required): void; + openReactionUserList(param: Required & { focusIndex?: number }): void; updateReactionFocusedItem(param?: State): void; + focusIndex: number; } & State; type Props = React.PropsWithChildren<{}>; @@ -20,45 +23,78 @@ type Props = React.PropsWithChildren<{}>; export const ReactionContext = React.createContext(null); export const ReactionProvider = ({ children }: Props) => { const chatCtx = useContext(SendbirdChatContext); + const localizationCtx = useContext(LocalizationContext); + const userProfileCtx = useContext(ProfileCardContext); if (!chatCtx) throw new Error('SendbirdChatContext is not provided'); + if (!localizationCtx) throw new Error('LocalizationContext is not provided'); + if (!userProfileCtx) throw new Error('ProfileCardContext is not provided'); const [state, setState] = useReducer((prev: State, next: State) => ({ ...prev, ...next }), {}); const [reactionListVisible, setReactionListVisible] = useState(false); const [reactionUserListVisible, setReactionUserListVisible] = useState(false); + const [reactionUserListFocusIndex, setReactionUserListFocusIndex] = useState(0); + + const closeResolver = useRef<() => void>(() => {}); const openReactionList: ReactionContextType['openReactionList'] = useCallback((params) => { setState(params); setReactionListVisible(true); }, []); - const openReactionUserList: ReactionContextType['openReactionUserList'] = useCallback((params) => { - setState(params); - setReactionUserListVisible(true); - }, []); + const openReactionUserList: ReactionContextType['openReactionUserList'] = useCallback( + ({ channel, message, focusIndex = 0 }) => { + setState({ channel, message }); + setReactionUserListFocusIndex(focusIndex); + setReactionUserListVisible(true); + }, + [], + ); const updateReactionFocusedItem: ReactionContextType['updateReactionFocusedItem'] = useCallback((params) => { if (params) setState(params); else setState({}); }, []); - const reactionCtx = { ...state, openReactionList, openReactionUserList, updateReactionFocusedItem }; + const createOnCloseWithResolver = (callback: () => void) => { + return () => { + return new Promise((resolve) => { + closeResolver.current = resolve; + callback(); + }); + }; + }; + + const reactionCtx = { + ...state, + openReactionList, + openReactionUserList, + updateReactionFocusedItem, + focusIndex: reactionUserListFocusIndex, + }; + + const sheetProps = { + chatCtx, + reactionCtx, + localizationCtx, + userProfileCtx, + onDismiss: () => { + setState({}); + closeResolver.current?.(); + }, + }; return ( {children} setState({})} - onClose={() => setReactionUserListVisible(false)} + onClose={createOnCloseWithResolver(() => setReactionUserListVisible(false))} /> setState({})} - onClose={() => setReactionListVisible(false)} + onClose={createOnCloseWithResolver(() => setReactionListVisible(false))} /> ); diff --git a/packages/uikit-react-native/src/domain/groupChannel/component/GroupChannelMessageList.tsx b/packages/uikit-react-native/src/domain/groupChannel/component/GroupChannelMessageList.tsx index deeec30c4..af46cce01 100644 --- a/packages/uikit-react-native/src/domain/groupChannel/component/GroupChannelMessageList.tsx +++ b/packages/uikit-react-native/src/domain/groupChannel/component/GroupChannelMessageList.tsx @@ -26,7 +26,7 @@ import { import type { ChatFlatListRef } from '../../../components/ChatFlatList'; import ChatFlatList from '../../../components/ChatFlatList'; -import { BottomSheetReactionAddon } from '../../../components/ReactionAddons'; +import { ReactionAddons } from '../../../components/ReactionAddons'; import { DEPRECATION_WARNING } from '../../../constants'; import { useLocalization, usePlatformService, useSendbirdChat } from '../../../hooks/useContext'; import { GroupChannelContexts } from '../module/moduleContext'; @@ -292,7 +292,7 @@ const useGetMessagePressActions = ({ openSheet({ sheetItems, HeaderComponent: shouldRenderReaction(channel, features.reactionEnabled) - ? ({ onClose }) => + ? ({ onClose }) => : undefined, }); }; diff --git a/packages/uikit-utils/src/sendbird/message.ts b/packages/uikit-utils/src/sendbird/message.ts index 417b171d4..180c25c0d 100644 --- a/packages/uikit-utils/src/sendbird/message.ts +++ b/packages/uikit-utils/src/sendbird/message.ts @@ -4,6 +4,7 @@ import type { SendbirdDataPayload, SendbirdFileMessage, SendbirdMessage, + SendbirdReaction, SendbirdSendableMessage, } from '../types'; import { messageTime } from '../ui-format/common'; @@ -115,3 +116,7 @@ export function parseSendbirdNotification(dataPayload: RawSendbirdDataPayload): export function shouldRenderReaction(channel: SendbirdBaseChannel, reactionEnabled: boolean) { return channel.isGroupChannel() && !channel.isBroadcast && !channel.isSuper && reactionEnabled; } + +export function getReactionCount(reaction: SendbirdReaction) { + return reaction.userIds.length; +}