Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
02f95af
feat: Improve message template view
chohongm Aug 27, 2024
3cfb580
remove max 10 items in carousel limitation logic
chohongm Aug 28, 2024
df530ab
Remove MessageContentMiddleContainerType
chohongm Aug 30, 2024
0fb7498
update container type logic
chohongm Aug 30, 2024
e9719a3
Update template logic to render multiple items even when items contai…
chohongm Aug 30, 2024
a52d9f1
Add MessageContentForTemplateMessage
chohongm Aug 30, 2024
24dfd42
minor bug fix
chohongm Aug 30, 2024
f3219fd
apply feeds
chohongm Sep 2, 2024
4d57770
remove unused
chohongm Sep 2, 2024
cb4f1bc
fix
chohongm Sep 3, 2024
f55fb65
Merge branch 'main' into feat/CLNP-4852-improve-message-template-view
chohongm Sep 4, 2024
e6d3359
fix
chohongm Sep 4, 2024
bf75f44
fix
chohongm Sep 6, 2024
471c7dd
Merge branch 'main' into feat/CLNP-4852-improve-message-template-view
chohongm Sep 6, 2024
a4072e2
Update src/ui/MessageContent/index.scss
chohongm Sep 6, 2024
380a591
Update src/ui/MessageContent/index.scss
chohongm Sep 6, 2024
00fafab
Merge branch 'main' into feat/CLNP-4852-improve-message-template-view
chohongm Sep 6, 2024
dd4d29d
Merge branch 'main' into feat/CLNP-4852-improve-message-template-view
chohongm Sep 23, 2024
87d2e2b
remove unused
chohongm Sep 6, 2024
07d3cd2
fix bugs
chohongm Sep 23, 2024
d011233
fix bugs
chohongm Sep 23, 2024
8260f45
apply feeds
chohongm Sep 24, 2024
b388132
update snapshots
chohongm Sep 24, 2024
9e42fba
lock file update
chohongm Sep 24, 2024
7ec3d10
Update src/ui/TemplateMessageItemBody/index.tsx
chohongm Sep 24, 2024
28185c5
apply feeds
chohongm Sep 24, 2024
4be3798
apply feeds
chohongm Sep 25, 2024
3dab003
Update src/ui/MessageContent/index.tsx
chohongm Sep 26, 2024
9252030
Update src/ui/MessageContent/index.tsx
chohongm Sep 26, 2024
412bdf3
rename file
chohongm Sep 26, 2024
7d34767
apply feeds
chohongm Sep 26, 2024
8b20f7b
apply feeds
chohongm Sep 26, 2024
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: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@
},
"dependencies": {
"@sendbird/chat": "^4.14.2",
"@sendbird/react-uikit-message-template-view": "0.0.1-alpha.79",
"@sendbird/uikit-tools": "0.0.1-alpha.79",
"@sendbird/react-uikit-message-template-view": "0.0.2",
"@sendbird/uikit-tools": "0.0.2",
"css-vars-ponyfill": "^2.3.2",
"date-fns": "^2.16.1",
"dompurify": "^3.0.1"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,27 @@
import React, { useLayoutEffect, useState } from 'react';
import React, { useCallback, useEffect, useState } from 'react';

export const useIsElementInViewport = (elementRef: React.MutableRefObject<any>) => {
export const useIsElementInViewport = (): [React.RefCallback<HTMLDivElement>, boolean] => {
const [isVisible, setIsVisible] = useState(false);
const [element, setElement] = useState(null);

useLayoutEffect(() => {
const observer = new IntersectionObserver((entries: IntersectionObserverEntry[]) => {
const ref = useCallback((node) => {
if (node !== null) setElement(node);
}, []);

useEffect(() => {
if (!element) return;

const observer = new IntersectionObserver((entries) => {
const [entry] = entries;
if (entry) setIsVisible(entry.isIntersecting);
setIsVisible(entry.isIntersecting);
});

if (elementRef.current) observer.observe(elementRef.current);
return () => observer.disconnect();
}, [elementRef.current]);
observer.observe(element);

return () => {
observer.disconnect();
};
}, [element]);

return isVisible;
return [ref, isVisible];
};
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React, { useRef } from 'react';
import { RefCallback, useRef } from 'react';
import { useIsElementInViewport } from './useIsElementInViewport';

export const useLazyImageLoader = (elementRef: React.MutableRefObject<any>) => {
export const useLazyImageLoader = (): [RefCallback<HTMLDivElement>, boolean] => {
const isLoaded = useRef(false);
const isVisible = useIsElementInViewport(elementRef);
const [ref, isVisible] = useIsElementInViewport();

if (isVisible) isLoaded.current = true;
return isLoaded.current;
return [ref, isLoaded.current];
};
5 changes: 0 additions & 5 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,4 @@ export interface UploadedFileInfoWithUpload {

export type SendbirdTheme = 'light' | 'dark';

export enum MessageContentMiddleContainerType {
DEFAULT = 'default',
WIDE = 'wide',
}

export type HTMLTextDirection = 'ltr' | 'rtl';
2 changes: 1 addition & 1 deletion src/ui/ChannelAvatar/__tests__/ChannelAvatar.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ describe('ui/ChannelAvatar', () => {
expect(container.getElementsByClassName(targetClassName)[0].className).toContain(targetClassName);
});

it('should render an avatar broadcastChannel with url', function() {
it('should render an avatar broadcastChannel with non default url', function() {
const targetClassName = "sendbird-chat-header--avatar--broadcast-channel";
const coverUrl = '123';
render(<ChannelAvatar channel={{ isBroadcast: true, coverUrl }} />);
Expand Down
4 changes: 4 additions & 0 deletions src/ui/ContextMenu/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
display: inline;
}

.sendbird-message-content__sendbird-ui-container-type__default__header-container .sendbird-context-menu {
display: flex;
}

.sendbird__offline {
.sendbird-dropdown__menu .sendbird-dropdown__menu-item {
cursor: not-allowed;
Expand Down
1 change: 0 additions & 1 deletion src/ui/ContextMenu/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,6 @@ export default function ContextMenu({
return (
<div
className="sendbird-context-menu"
style={{ display: 'inline' }}
onClick={onClick}
>
{menuTrigger?.(() => setShowMenu(!showMenu))}
Expand Down
5 changes: 2 additions & 3 deletions src/ui/ImageRenderer/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,8 @@ const ImageRenderer = ({
shadeOnHover,
isUploaded = true,
}: ImageRendererProps): ReactElement => {
const ref = useRef(null);
const isLoaded = useLazyImageLoader(ref);
const internalUrl = isLoaded ? url : null;
const [ref, isVisible] = useLazyImageLoader();
const internalUrl = isVisible ? url : null;

const [defaultComponentVisible, setDefaultComponentVisible] = useState(false);
const [placeholderVisible, setPlaceholderVisible] = useState(true);
Expand Down
43 changes: 25 additions & 18 deletions src/ui/MessageContent/MessageBody/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
CoreMessageType,
getUIKitMessageType, getUIKitMessageTypes, isTemplateMessage, isMultipleFilesMessage,
isOGMessage, isSendableMessage,
isTextMessage, isThumbnailMessage, isVoiceMessage, isFormMessage,
isTextMessage, isThumbnailMessage, isVoiceMessage, isFormMessage, isValidTemplateMessageType,
} from '../../../utils';
import { BaseMessage, FileMessage, MultipleFilesMessage, UserMessage } from '@sendbird/chat/message';
import OGMessageItemBody from '../../OGMessageItemBody';
Expand All @@ -23,21 +23,20 @@ import { match } from 'ts-pattern';
import TemplateMessageItemBody from '../../TemplateMessageItemBody';
import type { OnBeforeDownloadFileMessageType } from '../../../modules/GroupChannel/context/GroupChannelProvider';
import FormMessageItemBody from '../../FormMessageItemBody';
import { MESSAGE_TEMPLATE_KEY } from '../../../utils/consts';

export type CustomSubcomponentsProps = Record<
'ThumbnailMessageItemBody' | 'MultipleFilesMessageItemBody',
Record<string, any>
>;

const MESSAGE_ITEM_BODY_CLASSNAME = 'sendbird-message-content__middle__message-item-body';
export type RenderedTemplateBodyType = 'failed' | 'composite' | 'simple';

export interface MessageBodyProps {
className?: string;
channel: Nullable<GroupChannel>;
message: CoreMessageType;
showFileViewer?: (bool: boolean) => void;
onTemplateMessageRenderedCallback?: (renderedTemplateBodyType: RenderedTemplateBodyType) => void;
onMessageHeightChange?: () => void;
onBeforeDownloadFileMessage?: OnBeforeDownloadFileMessageType;

Expand All @@ -55,7 +54,6 @@ export const MessageBody = (props: MessageBodyProps) => {
channel,
showFileViewer,
onMessageHeightChange,
onTemplateMessageRenderedCallback,
onBeforeDownloadFileMessage,

mouseHover,
Expand All @@ -76,6 +74,14 @@ export const MessageBody = (props: MessageBodyProps) => {
const isOgMessageEnabledInGroupChannel = channel?.isGroupChannel() && config.groupChannel.enableOgtag;
const isFormMessageEnabledInGroupChannel = channel?.isGroupChannel() && config.groupChannel.enableFormTypeMessage;

const renderUnknownMessageItemBody = () => <UnknownMessageItemBody
className={className}
message={message}
isByMe={isByMe}
mouseHover={mouseHover}
isReactionEnabled={isReactionEnabledInChannel}
/>;

return match(message)
.when((message) => isFormMessageEnabledInGroupChannel && isFormMessage(message),
() => (
Expand All @@ -85,15 +91,22 @@ export const MessageBody = (props: MessageBodyProps) => {
form={message.messageForm}
/>
))
.when(isTemplateMessage, () => (
<TemplateMessageItemBody
.when(isTemplateMessage, () => {
const templatePayload = message.extendedMessagePayload[MESSAGE_TEMPLATE_KEY];
if (!isValidTemplateMessageType(templatePayload)) {
config.logger?.error?.(
'TemplateMessageItemBody: invalid type value in message.extendedMessagePayload.message_template.',
templatePayload,
);
return renderUnknownMessageItemBody();
}
return <TemplateMessageItemBody
className={className}
message={message as BaseMessage}
isByMe={isByMe}
theme={config?.theme as SendbirdTheme}
onTemplateMessageRenderedCallback={onTemplateMessageRenderedCallback}
/>
))
/>;
})
.when((message) => isOgMessageEnabledInGroupChannel
&& isSendableMessage(message)
&& isOGMessage(message), () => (
Expand Down Expand Up @@ -163,15 +176,9 @@ export const MessageBody = (props: MessageBodyProps) => {
{...customSubcomponentsProps['ThumbnailMessageItemBody'] ?? {}}
/>
))
.otherwise((message) => (
<UnknownMessageItemBody
className={className}
message={message}
isByMe={isByMe}
mouseHover={mouseHover}
isReactionEnabled={isReactionEnabledInChannel}
/>
));
.otherwise(() => {
return renderUnknownMessageItemBody();
});
};

export default MessageBody;
115 changes: 115 additions & 0 deletions src/ui/MessageContent/MessageContentForTemplateMessage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import React, { ReactElement } from 'react';
import Label, { LabelColors, LabelTypography } from '../Label';
import { classnames } from '../../utils/utils';
import format from 'date-fns/format';
import { MessageTemplateData, TemplateType } from '../TemplateMessageItemBody/types';
import { MessageComponentRenderers, MessageContentProps } from './index';
import useSendbirdStateContext from '../../hooks/useSendbirdStateContext';
import { uiContainerType } from '../../utils';
import { useLocalization } from '../../lib/LocalizationContext';
import { MESSAGE_TEMPLATE_KEY } from '../../utils/consts';

type MessageContentForTemplateMessageProps = MessageContentProps & MessageComponentRenderers & {
isByMe: boolean;
displayThreadReplies: boolean;
mouseHover: boolean;
isMobile: boolean;
isReactionEnabledInChannel: boolean;
hoveredMenuClassName: string;
templateType: TemplateType | null;
useReplying: boolean;
};

export function MessageContentForTemplateMessage(props: MessageContentForTemplateMessageProps): ReactElement {
const {
channel,
message,
showFileViewer,
onMessageHeightChange,
onBeforeDownloadFileMessage,

renderSenderProfile,
renderMessageHeader,
renderMessageBody,

isByMe,
displayThreadReplies,
mouseHover,
isMobile,
isReactionEnabledInChannel,
hoveredMenuClassName,
templateType,
useReplying,
} = props;

const { config } = useSendbirdStateContext();
const { dateLocale } = useLocalization();

const uiContainerTypeClassName = uiContainerType[templateType] ?? '';

const senderProfile = renderSenderProfile({
...props,
chainBottom: false,
className: '',
isByMe,
displayThreadReplies,
});
const messageHeader = renderMessageHeader(props);
const messageBody = renderMessageBody({
message,
channel,
showFileViewer,
onMessageHeightChange,
mouseHover,
isMobile,
config,
isReactionEnabledInChannel,
isByMe,
onBeforeDownloadFileMessage,
});

const timeStamp = <Label
className={classnames(
'sendbird-message-content__middle__body-container__created-at',
'right',
hoveredMenuClassName,
uiContainerTypeClassName,
)}
type={LabelTypography.CAPTION_3}
color={LabelColors.ONBACKGROUND_2}
>
{format(message?.createdAt || 0, 'p', {
locale: dateLocale,
})}
</Label>;

const templateData: MessageTemplateData = message.extendedMessagePayload?.[MESSAGE_TEMPLATE_KEY] as MessageTemplateData;
const { profile = true, time = true, nickname = true } = templateData?.container_options ?? {};
const hasContainerHeader = profile || nickname;

return (
<div className="sendbird-message-content__sendbird-ui-container-type__default__root">
{
!isByMe
&& hasContainerHeader
&& !useReplying
&& (
<div className="sendbird-message-content__sendbird-ui-container-type__default__header-container">
<div
className="sendbird-message-content__sendbird-ui-container-type__default__header-container__left__profile">
{profile && senderProfile}
</div>
{nickname && messageHeader}
</div>
)
}
{messageBody}
{
(!isByMe && time)
&& <div className="sendbird-message-content__sendbird-ui-container-type__default__bottom">
{timeStamp}
</div>
}
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,14 @@
exports[`ui/MessageContent should do a snapshot test of the MessageContent DOM 1`] = `
<DocumentFragment>
<div
class="classname-for-snapshot sendbird-message-content incoming "
class="classname-for-snapshot sendbird-message-content incoming"
>
<div
class="sendbird-message-content__left incoming"
data-testid="sendbird-message-content__left"
>
<div
class="sendbird-context-menu"
style="display: inline;"
>
<div
class="sendbird-message-content__left__avatar sendbird-avatar"
Expand Down
Loading