diff --git a/client/src/components/Chat/Messages/MessagesView.tsx b/client/src/components/Chat/Messages/MessagesView.tsx index 71601b1da5..ead1879c36 100644 --- a/client/src/components/Chat/Messages/MessagesView.tsx +++ b/client/src/components/Chat/Messages/MessagesView.tsx @@ -28,11 +28,13 @@ const ScrollButton = memo(function ScrollButton({ messagesEndRef, scrollHandler, onNearBottomChange, + overlayHeight, }: { scrollableRef: React.RefObject; messagesEndRef: React.RefObject; scrollHandler: (event: React.MouseEvent) => void; onNearBottomChange: (isNearBottom: boolean) => void; + overlayHeight: number; }) { const scrollButtonPreference = useRecoilValue(store.showScrollButton); const [showScrollButton, setShowScrollButton] = useState(false); @@ -75,7 +77,11 @@ const ScrollButton = memo(function ScrollButton({ appear={true} nodeRef={scrollToBottomRef} > - + ); }); @@ -166,6 +172,7 @@ function MessagesViewContent({ messagesEndRef={messagesEndRef} scrollHandler={handleSmoothToRef} onNearBottomChange={handleNearBottomChange} + overlayHeight={steerOverlayHeight} /> diff --git a/client/src/components/Messages/ScrollToBottom.tsx b/client/src/components/Messages/ScrollToBottom.tsx index b6537a1493..d13add1593 100644 --- a/client/src/components/Messages/ScrollToBottom.tsx +++ b/client/src/components/Messages/ScrollToBottom.tsx @@ -7,30 +7,38 @@ import store from '~/store'; type Props = { scrollHandler: React.MouseEventHandler; + /** + * Height of the in-flight steer overlay, which stacks upward from the + * composer into this same corner. Lifts the button clear of it. + */ + overlayHeight?: number; }; -const ScrollToBottom = forwardRef(({ scrollHandler }, ref) => { - const localize = useLocalize(); - const maximizeChatSpace = useRecoilValue(store.maximizeChatSpace); +const ScrollToBottom = forwardRef( + ({ scrollHandler, overlayHeight = 0 }, ref) => { + const localize = useLocalize(); + const maximizeChatSpace = useRecoilValue(store.maximizeChatSpace); - return ( -
- -
- ); -}); + + + ); + }, +); ScrollToBottom.displayName = 'ScrollToBottom'; diff --git a/client/src/components/Messages/__tests__/ScrollToBottom.spec.tsx b/client/src/components/Messages/__tests__/ScrollToBottom.spec.tsx new file mode 100644 index 0000000000..9d9e67a618 --- /dev/null +++ b/client/src/components/Messages/__tests__/ScrollToBottom.spec.tsx @@ -0,0 +1,49 @@ +import { render, screen } from '@testing-library/react'; + +jest.mock('recoil', () => ({ + useRecoilValue: () => false, +})); + +jest.mock('~/hooks', () => ({ + useLocalize: () => (key: string) => key, +})); + +jest.mock('~/store', () => ({ + __esModule: true, + default: { maximizeChatSpace: 'maximizeChatSpace' }, +})); + +import ScrollToBottom from '../ScrollToBottom'; + +const renderButton = (overlayHeight?: number) => + render(); + +const container = () => screen.getByLabelText('com_ui_scroll_to_bottom').parentElement; + +describe('ScrollToBottom', () => { + it('rests just above the composer when nothing is queued', () => { + renderButton(); + + expect(container()).toHaveStyle({ bottom: 'calc(1.25rem + 0px)' }); + }); + + it('lifts clear of the in-flight steer overlay', () => { + renderButton(96); + + expect(container()).toHaveStyle({ bottom: 'calc(1.25rem + 96px)' }); + }); + + it('stays inside the message column on mobile', () => { + renderButton(); + + /** Without a mobile gutter the button escapes to the viewport edge while + * the steer bubbles inset, so the two visibly disagree. */ + expect(container()).toHaveClass('px-4', 'md:px-0'); + }); + + it('keeps the desktop width constraint', () => { + renderButton(); + + expect(container()).toHaveClass('md:max-w-3xl', 'xl:max-w-4xl'); + }); +});