fix(webui): avoid mobile welcome composer overlap
This commit is contained in:
@@ -532,7 +532,7 @@ export const ThreadViewport = forwardRef<ThreadViewportHandle, ThreadViewportPro
|
||||
ref={scrollRef}
|
||||
className={cn(
|
||||
"thread-viewport-scrollbar absolute inset-0 scroll-auto scrollbar-thin",
|
||||
hasMessages && hasVerticalOverflow ? "overflow-y-auto" : "overflow-hidden",
|
||||
hasVerticalOverflow ? "overflow-y-auto" : "overflow-hidden",
|
||||
"[&::-webkit-scrollbar]:w-1.5",
|
||||
"[&::-webkit-scrollbar-thumb]:rounded-full",
|
||||
"[&::-webkit-scrollbar-thumb]:bg-muted-foreground/30",
|
||||
@@ -574,8 +574,11 @@ export const ThreadViewport = forwardRef<ThreadViewportHandle, ThreadViewportPro
|
||||
) : (
|
||||
<div ref={contentRef} className="mx-auto flex min-h-full w-full max-w-[72rem] flex-col px-3 sm:px-4">
|
||||
<div className="flex w-full flex-1 flex-col items-center pb-[calc(0.75rem+env(safe-area-inset-bottom))] pt-6 sm:justify-center sm:py-12">
|
||||
<div className="relative flex w-full max-w-[58rem] flex-1 flex-col items-center justify-end gap-5 sm:block sm:flex-none">
|
||||
<div className="pointer-events-none absolute inset-0 flex items-center justify-center sm:pointer-events-auto sm:static sm:block">
|
||||
<div
|
||||
data-testid="thread-welcome-layout"
|
||||
className="relative grid w-full max-w-[58rem] flex-1 grid-rows-[minmax(min-content,1fr)_auto] gap-8 sm:block sm:flex-none"
|
||||
>
|
||||
<div className="flex min-h-0 items-center justify-center sm:absolute sm:inset-x-0 sm:bottom-[calc(100%+2rem)]">
|
||||
{emptyState}
|
||||
</div>
|
||||
<div className="w-full">{composer}</div>
|
||||
|
||||
@@ -598,6 +598,77 @@ describe("ThreadViewport", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps the welcome composer above a mobile soft keyboard", async () => {
|
||||
const visualViewport = stubVisualViewport({ innerHeight: 800, height: 480 });
|
||||
try {
|
||||
const { container } = render(
|
||||
<ThreadViewport
|
||||
messages={emptyMessages}
|
||||
isStreaming={false}
|
||||
composer={<textarea aria-label="Message input" />}
|
||||
emptyState={<div>welcome</div>}
|
||||
showScrollToBottomButton={false}
|
||||
/>,
|
||||
);
|
||||
const scroller = container.querySelector(".thread-viewport-scrollbar") as HTMLElement;
|
||||
const input = screen.getByLabelText("Message input");
|
||||
|
||||
act(() => {
|
||||
input.focus();
|
||||
fireEvent.focusIn(input);
|
||||
});
|
||||
|
||||
await waitFor(() => expect(scroller).toHaveStyle({ bottom: "320px" }));
|
||||
} finally {
|
||||
visualViewport.restore();
|
||||
}
|
||||
});
|
||||
|
||||
it("separates the mobile welcome copy and composer into responsive rows", () => {
|
||||
render(
|
||||
<ThreadViewport
|
||||
messages={emptyMessages}
|
||||
isStreaming={false}
|
||||
composer={<div>composer</div>}
|
||||
emptyState={<div>welcome</div>}
|
||||
/>,
|
||||
);
|
||||
|
||||
const layout = screen.getByTestId("thread-welcome-layout");
|
||||
expect(layout).toHaveClass(
|
||||
"grid",
|
||||
"gap-8",
|
||||
"grid-rows-[minmax(min-content,1fr)_auto]",
|
||||
);
|
||||
expect(screen.getByText("welcome").parentElement).toHaveClass("min-h-0");
|
||||
expect(screen.getByText("welcome").parentElement?.className).toContain(
|
||||
"sm:bottom-[calc(100%+2rem)]",
|
||||
);
|
||||
});
|
||||
|
||||
it("allows the welcome view to scroll when a short viewport overflows", async () => {
|
||||
const { container } = render(
|
||||
<ThreadViewport
|
||||
messages={emptyMessages}
|
||||
isStreaming={false}
|
||||
composer={<div>composer</div>}
|
||||
emptyState={<div>welcome</div>}
|
||||
showScrollToBottomButton={false}
|
||||
/>,
|
||||
);
|
||||
const scroller = container.querySelector(".thread-viewport-scrollbar") as HTMLElement;
|
||||
Object.defineProperties(scroller, {
|
||||
scrollHeight: { configurable: true, value: 620 },
|
||||
clientHeight: { configurable: true, value: 320 },
|
||||
});
|
||||
|
||||
act(() => {
|
||||
window.dispatchEvent(new Event("resize"));
|
||||
});
|
||||
|
||||
await waitFor(() => expect(scroller).toHaveClass("overflow-y-auto"));
|
||||
});
|
||||
|
||||
it("scrolls recent messages into view when the composer receives focus", async () => {
|
||||
const scrollTo = vi.fn();
|
||||
const { container } = render(
|
||||
|
||||
Reference in New Issue
Block a user