Skip to content

Commit 6eb003d

Browse files
authored
[CLNP-7850]Add feature to scroll to top of new message instead of bottom (#1378)
// PR title (Required) [feat]: Add feature to scroll to top of new message instead of bottom // PR description (Optional) Added `isFocusOnLastMessage` as a prop to `SendbirdProvider`. If this prop is set to true, then when a new message arrives and its height exceeds the screen, the scroll position will not automatically jump to the bottom. Instead, it will move to the top position of that new message. // Footer (Recommended) feat [CLNP-7850](https://sendbird.atlassian.net/browse/CLNP-7850) ### Changelogs - Added a new `isFocusOnLastMessage` prop to `SendbirdProvider` When set to true, if a newly received message is taller than the viewport, the scroll position will not auto-jump to the bottom. Instead, the view scrolls to the top of the new message, keeping the start of the message in focus. ### Checklist Put an `x` in the boxes that apply. You can also fill these out after creating the PR. If unsure, ask the members. This is a reminder of what we look for before merging your code. - [x] **All tests pass locally with my changes** - [ ] **I have added tests that prove my fix is effective or that my feature works** - [ ] **Public components / utils / props are appropriately exported** - [ ] I have added necessary documentation (if appropriate) ## External Contributions [CLNP-7850]: https://sendbird.atlassian.net/browse/CLNP-7850?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
1 parent 935a230 commit 6eb003d

File tree

15 files changed

+95
-1
lines changed

15 files changed

+95
-1
lines changed

apps/testing/src/pages/PlaygroundPage.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,6 @@ export function PlaygroundPage() {
1010
replyType: 'thread',
1111
}
1212
}}
13+
// autoscrollMessageOverflowToTop={true}
1314
/>;
1415
}

src/lib/Sendbird/context/SendbirdProvider.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ const SendbirdContextManager = ({
7272
sdkInitParams,
7373
customExtensionParams,
7474
isMultipleFilesMessageEnabled = false,
75+
autoscrollMessageOverflowToTop = false,
7576
eventHandlers,
7677
htmlTextDirection = 'ltr',
7778
forceLeftToRightMessageLayout = false,
@@ -288,6 +289,7 @@ const SendbirdContextManager = ({
288289
setCurrentTheme,
289290
setCurrenttheme: setCurrentTheme, // deprecated: typo
290291
isMultipleFilesMessageEnabled,
292+
autoscrollMessageOverflowToTop,
291293
uikitMultipleFilesMessageLimit,
292294
logger,
293295
pubSub,
@@ -316,6 +318,7 @@ const SendbirdContextManager = ({
316318
currentTheme,
317319
setCurrentTheme,
318320
isMultipleFilesMessageEnabled,
321+
autoscrollMessageOverflowToTop,
319322
uikitMultipleFilesMessageLimit,
320323
logger,
321324
pubSub,
@@ -395,6 +398,7 @@ const InternalSendbirdProvider = (props: SendbirdProviderProps & { logger: Logge
395398
},
396399
disableMarkAsDelivered: props?.disableMarkAsDelivered,
397400
isMultipleFilesMessageEnabled: props?.isMultipleFilesMessageEnabled,
401+
autoscrollMessageOverflowToTop: props?.autoscrollMessageOverflowToTop,
398402
},
399403
eventHandlers: props?.eventHandlers,
400404
});

src/lib/Sendbird/context/initialState.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ const config: SendbirdStateConfig = {
3333
forceLeftToRightMessageLayout: false,
3434
disableMarkAsDelivered: false,
3535
isMultipleFilesMessageEnabled: false,
36+
autoscrollMessageOverflowToTop: false,
3637
htmlTextDirection: 'ltr',
3738
uikitUploadSizeLimit: DEFAULT_UPLOAD_SIZE_LIMIT,
3839
uikitMultipleFilesMessageLimit: DEFAULT_MULTIPLE_FILES_MESSAGE_LIMIT,

src/lib/Sendbird/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,7 @@ export interface SendbirdProviderProps extends CommonUIKitConfigProps, React.Pro
218218
sdkInitParams?: SendbirdChatInitParams;
219219
customExtensionParams?: CustomExtensionParams;
220220
isMultipleFilesMessageEnabled?: boolean;
221+
autoscrollMessageOverflowToTop?: boolean;
221222
// UserProfile
222223
renderUserProfile?: (props: RenderUserProfileProps) => React.ReactElement;
223224
onStartDirectMessage?: (channel: GroupChannel) => void;
@@ -260,6 +261,7 @@ export interface SendbirdStateConfig {
260261
markAsDeliveredScheduler: MarkAsDeliveredSchedulerType;
261262
disableMarkAsDelivered: boolean;
262263
isMultipleFilesMessageEnabled: boolean;
264+
autoscrollMessageOverflowToTop: boolean;
263265
// Remote configs set from dashboard by UIKit feature configuration
264266
common: {
265267
enableUsingDefaultUserProfile: SBUConfig['common']['enableUsingDefaultUserProfile'];

src/modules/App/AppLayout.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import useSendbird from '../../lib/Sendbird/context/hooks/useSendbird';
1313
export const AppLayout = (props: AppLayoutProps) => {
1414
const {
1515
isMessageGroupingEnabled,
16+
autoscrollMessageOverflowToTop,
1617
allowProfileEdit,
1718
onProfileEditSuccess,
1819
disableAutoSelect,
@@ -50,6 +51,7 @@ export const AppLayout = (props: AppLayoutProps) => {
5051
showSearchIcon={showSearchIcon}
5152
isReactionEnabled={isReactionEnabled}
5253
isMessageGroupingEnabled={isMessageGroupingEnabled}
54+
autoscrollMessageOverflowToTop={autoscrollMessageOverflowToTop}
5355
allowProfileEdit={allowProfileEdit}
5456
onProfileEditSuccess={onProfileEditSuccess}
5557
currentChannel={currentChannel}
@@ -69,6 +71,7 @@ export const AppLayout = (props: AppLayoutProps) => {
6971
isReactionEnabled={isReactionEnabled}
7072
showSearchIcon={showSearchIcon}
7173
isMessageGroupingEnabled={isMessageGroupingEnabled}
74+
autoscrollMessageOverflowToTop={autoscrollMessageOverflowToTop}
7275
allowProfileEdit={allowProfileEdit}
7376
onProfileEditSuccess={onProfileEditSuccess}
7477
disableAutoSelect={disableAutoSelect}

src/modules/App/DesktopLayout.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export const DesktopLayout: React.FC<DesktopLayoutProps> = (props: DesktopLayout
2121
replyType,
2222
isMessageGroupingEnabled,
2323
isMultipleFilesMessageEnabled,
24+
autoscrollMessageOverflowToTop,
2425
allowProfileEdit,
2526
showSearchIcon,
2627
onProfileEditSuccess,
@@ -104,6 +105,7 @@ export const DesktopLayout: React.FC<DesktopLayoutProps> = (props: DesktopLayout
104105
replyType: replyType,
105106
isMessageGroupingEnabled: isMessageGroupingEnabled,
106107
isMultipleFilesMessageEnabled: isMultipleFilesMessageEnabled,
108+
autoscrollMessageOverflowToTop: autoscrollMessageOverflowToTop,
107109
// for GroupChannel
108110
animatedMessageId: highlightedMessage,
109111
onReplyInThreadClick: onClickThreadReply,

src/modules/App/MobileLayout.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export const MobileLayout: React.FC<MobileLayoutProps> = (props: MobileLayoutPro
3232
replyType,
3333
isMessageGroupingEnabled,
3434
isMultipleFilesMessageEnabled,
35+
autoscrollMessageOverflowToTop,
3536
allowProfileEdit,
3637
isReactionEnabled,
3738
showSearchIcon,
@@ -155,6 +156,7 @@ export const MobileLayout: React.FC<MobileLayoutProps> = (props: MobileLayoutPro
155156
replyType,
156157
isMessageGroupingEnabled,
157158
isMultipleFilesMessageEnabled,
159+
autoscrollMessageOverflowToTop,
158160
// for GroupChannel
159161
animatedMessageId: highlightedMessage,
160162
onReplyInThreadClick: ({ message }) => {

src/modules/App/index.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export interface AppProps {
2929
config?: SendbirdProviderProps['config'];
3030
voiceRecord?: SendbirdProviderProps['voiceRecord'];
3131
isMultipleFilesMessageEnabled?: SendbirdProviderProps['isMultipleFilesMessageEnabled'];
32+
autoscrollMessageOverflowToTop?: SendbirdProviderProps['autoscrollMessageOverflowToTop'];
3233
colorSet?: SendbirdProviderProps['colorSet'];
3334
stringSet?: SendbirdProviderProps['stringSet'];
3435
allowProfileEdit?: SendbirdProviderProps['allowProfileEdit'];
@@ -99,6 +100,7 @@ export default function App(props: AppProps) {
99100
customExtensionParams,
100101
eventHandlers,
101102
isMultipleFilesMessageEnabled,
103+
autoscrollMessageOverflowToTop = false,
102104
isUserIdUsedForNickname = true,
103105
enableLegacyChannelModules = false,
104106
uikitOptions,
@@ -138,6 +140,7 @@ export default function App(props: AppProps) {
138140
renderUserProfile={renderUserProfile}
139141
imageCompression={imageCompression}
140142
isMultipleFilesMessageEnabled={isMultipleFilesMessageEnabled}
143+
autoscrollMessageOverflowToTop={autoscrollMessageOverflowToTop}
141144
voiceRecord={voiceRecord}
142145
onStartDirectMessage={(channel) => {
143146
setCurrentChannel(channel);
@@ -159,6 +162,7 @@ export default function App(props: AppProps) {
159162
forceLeftToRightMessageLayout={forceLeftToRightMessageLayout}
160163
>
161164
<AppLayout
165+
autoscrollMessageOverflowToTop={autoscrollMessageOverflowToTop}
162166
isMessageGroupingEnabled={isMessageGroupingEnabled}
163167
allowProfileEdit={allowProfileEdit}
164168
onProfileEditSuccess={onProfileEditSuccess}

src/modules/App/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export interface AppLayoutProps {
1818
forceLeftToRightMessageLayout?: boolean;
1919
isMessageGroupingEnabled?: boolean;
2020
isMultipleFilesMessageEnabled?: boolean;
21+
autoscrollMessageOverflowToTop?: boolean;
2122
allowProfileEdit?: boolean;
2223
showSearchIcon?: boolean;
2324
onProfileEditSuccess?(user: User): void; // TODO: Unused props, deprecate it

src/modules/GroupChannel/components/Message/MessageView.tsx

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@ export interface MessageProps {
5555
* A function that is called when the new message separator visibility changes.
5656
*/
5757
onNewMessageSeparatorVisibilityChange?: (isVisible: boolean) => void;
58+
/**
59+
* A function that forces scroll to message when new message is received.
60+
*/
61+
scrollMessageOverflowToTop?: (ref: React.MutableRefObject<any>, message: CoreMessageType) => void;
5862
/**
5963
* @deprecated Please use `children` instead
6064
* @description Customizes all child components of the message.
@@ -97,6 +101,10 @@ export interface MessageViewProps extends MessageProps {
97101

98102
animatedMessageId: number | null;
99103
setAnimatedMessageId: React.Dispatch<React.SetStateAction<number | null>>;
104+
105+
newMessageIds?: number[] | null;
106+
setNewMessageIds?: (ids: number[]) => void;
107+
100108
onMessageAnimated?: () => void;
101109
/** @deprecated * */
102110
highLightedMessageId?: number | null;
@@ -119,6 +127,7 @@ const MessageView = (props: MessageViewProps) => {
119127
chainBottom,
120128
handleScroll,
121129
onNewMessageSeparatorVisibilityChange,
130+
scrollMessageOverflowToTop,
122131

123132
// MessageViewProps
124133
channel,
@@ -147,6 +156,9 @@ const MessageView = (props: MessageViewProps) => {
147156
animatedMessageId,
148157
onMessageAnimated,
149158
usedInLegacy = true,
159+
160+
newMessageIds,
161+
setNewMessageIds,
150162
} = props;
151163

152164
const {
@@ -257,6 +269,23 @@ const MessageView = (props: MessageViewProps) => {
257269
};
258270
}, [animatedMessageId, messageScrollRef.current, message.messageId]);
259271

272+
useLayoutEffect(() => {
273+
if (newMessageIds?.length > 0 && newMessageIds.includes(message.messageId)) {
274+
let rafId: number | null = null;
275+
276+
rafId = requestAnimationFrame(() => {
277+
scrollMessageOverflowToTop(messageScrollRef, message);
278+
setNewMessageIds([]);
279+
});
280+
281+
return () => {
282+
if (rafId !== null) {
283+
cancelAnimationFrame(rafId);
284+
}
285+
};
286+
}
287+
}, [newMessageIds]);
288+
260289
const renderedCustomSeparator = useMemo(() => renderCustomSeparator?.({ message }) ?? null, [message, renderCustomSeparator]);
261290

262291
const renderChildren = () => {

0 commit comments

Comments
 (0)