Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/modules/Channel/components/ChannelUI/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ import MessageInputWrapper from '../MessageInputWrapper';

export interface ChannelUIProps extends GroupChannelUIBasicProps {
isLoading?: boolean;
/**
* Customizes all child components of the message component.
* */
renderMessage?: GroupChannelUIBasicProps['renderMessage'];
}

const ChannelUI = (props: ChannelUIProps) => {
Expand Down
7 changes: 4 additions & 3 deletions src/modules/Channel/components/MessageInputWrapper/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ import { getSuggestedReplies } from '../../../../utils';
import MessageInputWrapperView from '../../../GroupChannel/components/MessageInputWrapper/MessageInputWrapperView';
import { useChannelContext } from '../../context/ChannelProvider';
import useSendbirdStateContext from '../../../../hooks/useSendbirdStateContext';
import { GroupChannelUIBasicProps } from '../../../GroupChannel/components/GroupChannelUI/GroupChannelUIView';

export interface MessageInputWrapperProps {
value?: string;
disabled?: boolean;
renderFileUploadIcon?: () => React.ReactElement;
renderVoiceMessageIcon?: () => React.ReactElement;
renderSendMessageIcon?: () => React.ReactElement;
acceptableMimeTypes?: string[];
renderFileUploadIcon?: GroupChannelUIBasicProps['renderFileUploadIcon'];
renderVoiceMessageIcon?: GroupChannelUIBasicProps['renderVoiceMessageIcon'];
renderSendMessageIcon?: GroupChannelUIBasicProps['renderSendMessageIcon'];
}

export const MessageInputWrapper = (props: MessageInputWrapperProps) => {
Expand Down
184 changes: 75 additions & 109 deletions src/modules/Channel/components/MessageList/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,24 @@ import { useOnScrollPositionChangeDetector } from '../../../../hooks/useOnScroll

import { getMessagePartsInfo } from '../../../GroupChannel/components/MessageList/getMessagePartsInfo';
import { GroupChannelMessageListProps } from '../../../GroupChannel/components/MessageList';
import { GroupChannelUIBasicProps } from '../../../GroupChannel/components/GroupChannelUI/GroupChannelUIView';

const SCROLL_BOTTOM_PADDING = 50;

export type MessageListProps = GroupChannelMessageListProps;
export interface MessageListProps extends GroupChannelMessageListProps {
/**
* Customizes all child components of the message component.
* */
renderMessage?: GroupChannelUIBasicProps['renderMessage'];
}
export const MessageList = ({
className = '',
renderMessage,
renderMessageContent,
renderPlaceholderEmpty,
renderCustomSeparator,
renderPlaceholderLoader,
renderFrozenNotification,
renderPlaceholderLoader = () => <PlaceHolder type={PlaceHolderTypes.LOADING} />,
renderPlaceholderEmpty = () => <PlaceHolder className="sendbird-conversation__no-messages" type={PlaceHolderTypes.NO_MESSAGES} />,
renderFrozenNotification = () => <FrozenNotification className="sendbird-conversation__messages__notification" />,
}: MessageListProps) => {
const {
allMessages,
Expand Down Expand Up @@ -62,9 +68,7 @@ export const MessageList = ({
} = useChannelContext();

const store = useSendbirdStateContext();
const allMessagesFiltered = (typeof filterMessageList === 'function')
? allMessages.filter((filterMessageList as (message: EveryMessage) => boolean))
: allMessages;
const allMessagesFiltered = typeof filterMessageList === 'function' ? allMessages.filter(filterMessageList as (message: EveryMessage) => boolean) : allMessages;
const markAsReadScheduler = store.config.markAsReadScheduler;

const [isScrollBottom, setIsScrollBottom] = useState(false);
Expand All @@ -80,11 +84,7 @@ export const MessageList = ({
return;
}

const {
scrollTop,
clientHeight,
scrollHeight,
} = element;
const { scrollTop, clientHeight, scrollHeight } = element;

if (hasMorePrev && isAboutSame(scrollTop, 0, SCROLL_BUFFER)) {
onScrollCallback(callback);
Expand All @@ -94,10 +94,7 @@ export const MessageList = ({
onScrollDownCallback(callback);
}

if (!disableMarkAsRead
&& isAboutSame(clientHeight + scrollTop, scrollHeight, SCROLL_BUFFER)
&& !!currentGroupChannel
) {
if (!disableMarkAsRead && isAboutSame(clientHeight + scrollTop, scrollHeight, SCROLL_BUFFER) && !!currentGroupChannel) {
messagesDispatcher({
type: messageActionTypes.MARK_AS_READ,
payload: { channel: currentGroupChannel },
Expand All @@ -124,8 +121,7 @@ export const MessageList = ({
const current = scrollRef?.current;
if (current) {
const bottom = current.scrollHeight - current.scrollTop - current.offsetHeight;
if (scrollBottom < bottom
&& (!isBottomMessageAffected || scrollBottom < SCROLL_BUFFER)) {
if (scrollBottom < bottom && (!isBottomMessageAffected || scrollBottom < SCROLL_BUFFER)) {
// Move the scroll as much as the height of the message has changed
current.scrollTop += bottom - scrollBottom;
}
Expand Down Expand Up @@ -165,22 +161,16 @@ export const MessageList = ({
const { scrollToBottomHandler, scrollBottom } = useSetScrollToBottom({ loading });

if (loading) {
return (typeof renderPlaceholderLoader === 'function')
? renderPlaceholderLoader()
: <PlaceHolder type={PlaceHolderTypes.LOADING} />;
return renderPlaceholderLoader();
}

if (allMessagesFiltered.length < 1) {
if (renderPlaceholderEmpty && typeof renderPlaceholderEmpty === 'function') {
return renderPlaceholderEmpty();
}
return <PlaceHolder className="sendbird-conversation__no-messages" type={PlaceHolderTypes.NO_MESSAGES} />;
return renderPlaceholderEmpty();
}

return (
<>
{
!isScrolled && <PlaceHolder type={PlaceHolderTypes.LOADING} />
}
{!isScrolled && <PlaceHolder type={PlaceHolderTypes.LOADING} />}
<div className={`sendbird-conversation__messages ${className}`}>
<div className="sendbird-conversation__scroll-container">
<div className="sendbird-conversation__padding" />
Expand All @@ -193,86 +183,67 @@ export const MessageList = ({
onScrollReachedEndDetector(e);
}}
>
{
allMessagesFiltered.map((m, idx) => {
const {
chainTop,
chainBottom,
hasSeparator,
} = getMessagePartsInfo({
allMessages: allMessagesFiltered,
replyType,
isMessageGroupingEnabled,
currentIndex: idx,
currentMessage: m,
currentChannel: currentGroupChannel,
});
const isByMe = (m as UserMessage)?.sender?.userId === store?.config?.userId;
return (
<MessageProvider message={m} key={m?.messageId} isByMe={isByMe}>
<Message
handleScroll={moveScroll}
renderMessage={renderMessage}
renderMessageContent={renderMessageContent}
message={m as EveryMessage}
hasSeparator={hasSeparator}
chainTop={chainTop}
chainBottom={chainBottom}
renderCustomSeparator={renderCustomSeparator}
/>
</MessageProvider>
);
})
}
{
localMessages.map((m, idx) => {
const {
chainTop,
chainBottom,
} = getMessagePartsInfo({
allMessages: allMessagesFiltered,
replyType,
isMessageGroupingEnabled,
currentIndex: idx,
currentMessage: m,
currentChannel: currentGroupChannel,
});
const isByMe = (m as UserMessage)?.sender?.userId === store?.config?.userId;
return (
<MessageProvider message={m} key={m?.messageId} isByMe={isByMe}>
<Message
handleScroll={moveScroll}
renderMessage={renderMessage}
renderMessageContent={renderMessageContent}
message={m as EveryMessage}
chainTop={chainTop}
chainBottom={chainBottom}
renderCustomSeparator={renderCustomSeparator}
/>
</MessageProvider>
);
})
}
{
!hasMoreNext
{allMessagesFiltered.map((m, idx) => {
const { chainTop, chainBottom, hasSeparator } = getMessagePartsInfo({
allMessages: allMessagesFiltered,
replyType,
isMessageGroupingEnabled,
currentIndex: idx,
currentMessage: m,
currentChannel: currentGroupChannel,
});
const isByMe = (m as UserMessage)?.sender?.userId === store?.config?.userId;
return (
<MessageProvider message={m} key={m?.messageId} isByMe={isByMe}>
<Message
handleScroll={moveScroll}
message={m as EveryMessage}
hasSeparator={hasSeparator}
chainTop={chainTop}
chainBottom={chainBottom}
renderMessageContent={renderMessageContent}
renderCustomSeparator={renderCustomSeparator}
// backward compatibility
renderMessage={renderMessage}
/>
</MessageProvider>
);
})}
{localMessages.map((m, idx) => {
const { chainTop, chainBottom } = getMessagePartsInfo({
allMessages: allMessagesFiltered,
replyType,
isMessageGroupingEnabled,
currentIndex: idx,
currentMessage: m,
currentChannel: currentGroupChannel,
});
const isByMe = (m as UserMessage)?.sender?.userId === store?.config?.userId;
return (
<MessageProvider message={m} key={m?.messageId} isByMe={isByMe}>
<Message
handleScroll={moveScroll}
message={m as EveryMessage}
chainTop={chainTop}
chainBottom={chainBottom}
renderMessageContent={renderMessageContent}
renderCustomSeparator={renderCustomSeparator}
// backward compatibility
renderMessage={renderMessage}
/>
</MessageProvider>
);
})}
{!hasMoreNext
&& store?.config?.groupChannel?.enableTypingIndicator
&& store?.config?.groupChannel?.typingIndicatorTypes?.has(TypingIndicatorType.Bubble)
&& <TypingIndicatorBubble
typingMembers={typingMembers}
handleScroll={moveScroll}
/>
}
&& store?.config?.groupChannel?.typingIndicatorTypes?.has(TypingIndicatorType.Bubble) && (
<TypingIndicatorBubble typingMembers={typingMembers} handleScroll={moveScroll} />
)}
{/* show frozen notifications, */}
{/* show new message notifications, */}
</div>
</div>
{
currentGroupChannel?.isFrozen && (
renderFrozenNotification
? renderFrozenNotification()
: <FrozenNotification className="sendbird-conversation__messages__notification" />
)
}
{currentGroupChannel?.isFrozen && renderFrozenNotification()}
{
/**
* Show unread count IFF scroll is not bottom or is bottom but hasNext is true.
Expand Down Expand Up @@ -309,12 +280,7 @@ export const MessageList = ({
tabIndex={0}
role="button"
>
<Icon
width="24px"
height="24px"
type={IconTypes.CHEVRON_DOWN}
fillColor={IconColors.PRIMARY}
/>
<Icon width="24px" height="24px" type={IconTypes.CHEVRON_DOWN} fillColor={IconColors.PRIMARY} />
</div>
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,24 +15,66 @@ import type { MessageContentProps } from '../../../../ui/MessageContent';

export interface GroupChannelUIBasicProps {
// Components
/**
* A function that customizes the rendering of a loading placeholder component.
*/
renderPlaceholderLoader?: () => React.ReactElement;
/**
* A function that customizes the rendering of a invalid placeholder component.
*/
renderPlaceholderInvalid?: () => React.ReactElement;
/**
* A function that customizes the rendering of an empty placeholder component when there are no messages in the channel.
*/
renderPlaceholderEmpty?: () => React.ReactElement;
/**
* A function that customizes the rendering of a header component.
*/
renderChannelHeader?: (props: GroupChannelHeaderProps) => React.ReactElement;
/**
* A function that customizes the rendering of a message list component.
*/
renderMessageList?: (props: GroupChannelMessageListProps) => React.ReactElement;
/**
* A function that customizes the rendering of a message input component.
*/
renderMessageInput?: () => React.ReactElement;

// Sub components
// MessageList
/**
* A function that customizes the rendering of each message component in the message list component.
*/
renderMessage?: (props: RenderMessageParamsType) => React.ReactElement;
/**
* A function that customizes the rendering of the content portion of each message component.
*/
renderMessageContent?: (props: MessageContentProps) => React.ReactElement;
/**
* A function that customizes the rendering of a separator component between messages.
*/
renderCustomSeparator?: (props: RenderCustomSeparatorProps) => React.ReactElement;
/**
* A function that customizes the rendering of a frozen notification component when the channel is frozen.
*/
renderFrozenNotification?: () => React.ReactElement;

// MessageInput
// MessageInput
/**
* A function that customizes the rendering of the file upload icon in the message input component.
*/
renderFileUploadIcon?: () => React.ReactElement;
/**
* A function that customizes the rendering of the voice message icon in the message input component.
*/
renderVoiceMessageIcon?: () => React.ReactElement;
/**
* A function that customizes the rendering of the send message icon in the message input component.
*/
renderSendMessageIcon?: () => React.ReactElement;
/**
* A function that customizes the rendering of the typing indicator component.
*/
renderTypingIndicator?: () => React.ReactElement;
}

Expand Down
Loading