Skip to content

Commit c0f9d6a

Browse files
fix(lightspeed): overflow and horizontal scrollbar fix (#2559)
* fix(lightspeed): overflow and horizontal scrollbar fix Signed-off-by: rohitratannagar <rohitratannagar2003@gmail.com> * removing setTimeOut Signed-off-by: rohitratannagar <rohitratannagar2003@gmail.com> --------- Signed-off-by: rohitratannagar <rohitratannagar2003@gmail.com>
1 parent 3d6415d commit c0f9d6a

5 files changed

Lines changed: 123 additions & 19 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@red-hat-developer-hub/backstage-plugin-lightspeed': patch
3+
---
4+
5+
fixes lightspeed overlay (remove horizontal scrollbar, and adds vertical scrollbar for newly created chats)

workspaces/lightspeed/plugins/lightspeed/src/components/LightSpeedChat.tsx

Lines changed: 92 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ import {
2020
Ref,
2121
useCallback,
2222
useEffect,
23+
useLayoutEffect,
2324
useMemo,
25+
useRef,
2426
useState,
2527
} from 'react';
2628
import {
@@ -128,6 +130,27 @@ const useStyles = makeStyles(theme => ({
128130
padding: 0,
129131
margin: 0,
130132
},
133+
// Outer content wrapper (library may override overflow; we rely on inner scroll wrapper).
134+
chatbotContent: {
135+
minHeight: 0,
136+
display: 'flex',
137+
flexDirection: 'column',
138+
flex: 1,
139+
},
140+
// Inner scroll container we control: always scrollable so zoomed-in users see full content.
141+
chatbotContentScroll: {
142+
minHeight: 0,
143+
flex: 1,
144+
display: 'flex',
145+
flexDirection: 'column',
146+
overflowY: 'auto',
147+
WebkitOverflowScrolling: 'touch',
148+
},
149+
// When present, pushes welcome content to bottom (zoom out). Scroll up to see important box (zoom in).
150+
chatbotContentSpacer: {
151+
flex: 1,
152+
minHeight: 0,
153+
},
131154
}));
132155

133156
type LightspeedChatProps = {
@@ -165,6 +188,8 @@ export const LightspeedChat = ({
165188
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState<boolean>(false);
166189
const [isRenameModalOpen, setIsRenameModalOpen] = useState<boolean>(false);
167190
const [isSortSelectOpen, setIsSortSelectOpen] = useState<boolean>(false);
191+
const contentScrollRef = useRef<HTMLDivElement>(null);
192+
const bottomSentinelRef = useRef<HTMLDivElement>(null);
168193
const { isReady, lastOpenedId, setLastOpenedId, clearLastOpenedId } =
169194
useLastOpenedConversation(user);
170195
const {
@@ -342,6 +367,10 @@ export const LightspeedChat = ({
342367
if (!isFullscreenMode) {
343368
setIsChatHistoryDrawerOpen(false);
344369
}
370+
} else {
371+
// Already on new chat: reset so scroll/layout works (e.g. after opening new chat again from another convo then back).
372+
setMessages([]);
373+
setNewChatCreated(true);
345374
}
346375
})();
347376
}, [
@@ -572,6 +601,40 @@ export const LightspeedChat = ({
572601
})
573602
: [];
574603

604+
// Scroll to bottom when welcome content appears (sentinel + useLayoutEffect/RAF + ResizeObserver).
605+
useLayoutEffect(() => {
606+
if (welcomePrompts.length === 0) return undefined;
607+
const el = contentScrollRef.current;
608+
const sentinel = bottomSentinelRef.current;
609+
if (!el) return undefined;
610+
611+
const scrollToBottom = () => {
612+
if (sentinel && typeof sentinel.scrollIntoView === 'function') {
613+
sentinel.scrollIntoView({ block: 'end', behavior: 'auto' });
614+
} else {
615+
el.scrollTop = el.scrollHeight;
616+
}
617+
};
618+
619+
const rafId =
620+
typeof requestAnimationFrame !== 'undefined'
621+
? requestAnimationFrame(() => scrollToBottom())
622+
: null;
623+
if (rafId === null) scrollToBottom();
624+
const resizeObserver =
625+
typeof ResizeObserver !== 'undefined'
626+
? new ResizeObserver(() => scrollToBottom())
627+
: undefined;
628+
resizeObserver?.observe(el);
629+
630+
return () => {
631+
if (rafId !== null && typeof cancelAnimationFrame !== 'undefined') {
632+
cancelAnimationFrame(rafId);
633+
}
634+
resizeObserver?.disconnect();
635+
};
636+
}, [welcomePrompts.length]);
637+
575638
const handleFilter = useCallback((value: string) => {
576639
setFilterValue(value);
577640
}, []);
@@ -752,7 +815,7 @@ export const LightspeedChat = ({
752815
style: isFullscreenMode ? undefined : { zIndex: 1300 },
753816
}}
754817
reverseButtonOrder
755-
displayMode={ChatbotDisplayMode.embedded}
818+
displayMode={displayMode}
756819
onDrawerToggle={onChatHistoryDrawerToggle}
757820
title=""
758821
navTitleIcon={null}
@@ -811,19 +874,34 @@ export const LightspeedChat = ({
811874
</div>
812875
)}
813876

814-
<ChatbotContent>
815-
<LightspeedChatBox
816-
userName={userName}
817-
messages={messages}
818-
profileLoading={profileLoading}
819-
announcement={announcement}
820-
ref={scrollToBottomRef}
821-
welcomePrompts={welcomePrompts}
822-
conversationId={conversationId}
823-
isStreaming={isSendButtonDisabled}
824-
topicRestrictionEnabled={topicRestrictionEnabled}
825-
displayMode={displayMode}
826-
/>
877+
<ChatbotContent className={classes.chatbotContent}>
878+
<div
879+
ref={contentScrollRef}
880+
className={classes.chatbotContentScroll}
881+
>
882+
{welcomePrompts.length > 0 && (
883+
<div className={classes.chatbotContentSpacer} aria-hidden />
884+
)}
885+
<LightspeedChatBox
886+
userName={userName}
887+
messages={messages}
888+
profileLoading={profileLoading}
889+
announcement={announcement}
890+
ref={scrollToBottomRef}
891+
welcomePrompts={welcomePrompts}
892+
conversationId={conversationId}
893+
isStreaming={isSendButtonDisabled}
894+
topicRestrictionEnabled={topicRestrictionEnabled}
895+
displayMode={displayMode}
896+
/>
897+
{welcomePrompts.length > 0 && (
898+
<div
899+
ref={bottomSentinelRef}
900+
aria-hidden
901+
style={{ height: 0, flexShrink: 0 }}
902+
/>
903+
)}
904+
</div>
827905
</ChatbotContent>
828906
<ChatbotFooter className={classes.footer}>
829907
<FilePreview />

workspaces/lightspeed/plugins/lightspeed/src/components/LightspeedChatBox.tsx

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,18 @@ const useStyles = makeStyles(theme => ({
8585
opacity: 0.65,
8686
},
8787
},
88+
// Message box fills remaining height and scrolls when there are messages.
89+
messageBoxFlex: {
90+
flex: 1,
91+
minHeight: 0,
92+
},
93+
// New chat: message box sizes to content so ChatbotContent is the scroll container.
94+
// overflow: visible so full height is included in parent's scrollHeight (no clipping).
95+
messageBoxAutoHeight: {
96+
flex: 'none',
97+
height: 'auto',
98+
overflow: 'visible',
99+
},
88100
}));
89101

90102
// Extended message type that includes tool calls
@@ -180,15 +192,19 @@ export const LightspeedChatBox = forwardRef(
180192
const messageBoxClasses = `${classes.container} ${classes.userMessageText}`;
181193
const isEmbeddedMode = displayMode === ChatbotDisplayMode.embedded;
182194

195+
const isNewChat = welcomePrompts.length > 0 && messages.length === 0;
183196
const getMessageBoxClassName = () => {
197+
const base = isNewChat
198+
? `${messageBoxClasses} ${classes.messageBoxAutoHeight}`
199+
: `${messageBoxClasses} ${classes.messageBoxFlex}`;
184200
if (!welcomePrompts.length) {
185-
return messageBoxClasses;
201+
return base;
186202
}
187-
const baseClasses = `${messageBoxClasses} ${classes.prompt}`;
203+
const withPrompt = `${base} ${classes.prompt}`;
188204
if (isEmbeddedMode) {
189-
return baseClasses;
205+
return withPrompt;
190206
}
191-
return `${baseClasses} ${classes.promptSuggestions}`;
207+
return `${withPrompt} ${classes.promptSuggestions}`;
192208
};
193209

194210
return (

workspaces/lightspeed/plugins/lightspeed/src/components/LightspeedDrawerProvider.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ const useStyles = makeStyles(theme => ({
3838
'0 14px 20px -7px rgba(0, 0, 0, 0.22), 0 32px 50px 6px rgba(0, 0, 0, 0.16), 0 12px 60px 12px rgba(0, 0, 0, 0.14) !important',
3939
bottom: `calc(${theme?.spacing?.(2) ?? '16px'} + 5em)`,
4040
right: `calc(${theme?.spacing?.(2) ?? '16px'} + 1.5em)`,
41+
maxWidth: 'min(30rem, calc(100vw - 32px)) !important',
42+
overflowX: 'hidden' as const,
4143
},
4244
}));
4345

workspaces/lightspeed/plugins/lightspeed/src/hooks/useConversationMessages.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,8 +127,11 @@ export const useConversationMessages = (
127127
if (currentConversation !== conversationId) {
128128
setCurrentConversation(conversationId);
129129
setConversations(prev => {
130+
// Always clear TEMP when switching to it so "New chat" again gets empty messages and correct scroll.
131+
if (conversationId === TEMP_CONVERSATION_ID) {
132+
return { ...prev, [TEMP_CONVERSATION_ID]: [] };
133+
}
130134
if (prev[conversationId]) return prev;
131-
132135
return {
133136
...prev,
134137
[conversationId]: [],

0 commit comments

Comments
 (0)