mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-13 07:12:00 +01:00
fix(ui): prevent mobile viewport horizontal scroll
The mobile layout root had no horizontal-overflow guard, so any stray descendant wider than the screen (a long token, an over-wide element) made the entire viewport scroll sideways. Add overflow-x: clip to the mobile root. Using clip rather than hidden leaves overflow-y computed as visible, preserving native body scrolling and the sticky breadcrumb. Add a Layout regression test asserting the mobile root carries overflow-x-clip and the desktop root carries overflow-clip. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
713b3b08f0
commit
b765d173d8
2 files changed
+52
-1
No files matched your search
@@ -723,4 +723,51 @@ describe("Layout", () => {
|
||||
root.unmount();
|
||||
});
|
||||
});
|
||||
|
||||
async function renderLayoutRoot(): Promise<{ root: ReturnType<typeof createRoot>; rootEl: HTMLElement }> {
|
||||
const root = createRoot(container);
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: { queries: { retry: false } },
|
||||
});
|
||||
await act(async () => {
|
||||
root.render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Layout />
|
||||
</QueryClientProvider>,
|
||||
);
|
||||
});
|
||||
await flushReact();
|
||||
const rootEl = container.firstElementChild as HTMLElement;
|
||||
return { root, rootEl };
|
||||
}
|
||||
|
||||
it("clips horizontal overflow on the mobile layout root so the viewport can't scroll sideways", async () => {
|
||||
mockSidebarState.isMobile = true;
|
||||
mockSidebarState.sidebarOpen = false;
|
||||
const { root, rootEl } = await renderLayoutRoot();
|
||||
|
||||
expect(rootEl.tagName).toBe("DIV");
|
||||
expect(rootEl.className).toContain("bg-background");
|
||||
// The mobile root must clip horizontal overflow to prevent a stray wide
|
||||
// descendant from making the whole viewport scroll sideways. clip (not
|
||||
// hidden) keeps overflow-y visible so body scroll keeps working.
|
||||
expect(rootEl.classList.contains("overflow-x-clip")).toBe(true);
|
||||
expect(rootEl.classList.contains("overflow-hidden")).toBe(false);
|
||||
|
||||
await act(async () => {
|
||||
root.unmount();
|
||||
});
|
||||
});
|
||||
|
||||
it("clips overflow on the desktop layout root", async () => {
|
||||
mockSidebarState.isMobile = false;
|
||||
const { root, rootEl } = await renderLayoutRoot();
|
||||
|
||||
expect(rootEl.className).toContain("bg-background");
|
||||
expect(rootEl.classList.contains("overflow-clip")).toBe(true);
|
||||
|
||||
await act(async () => {
|
||||
root.unmount();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -469,7 +469,11 @@ export function Layout() {
|
||||
<div
|
||||
className={cn(
|
||||
"bg-background text-foreground pt-[env(safe-area-inset-top)]",
|
||||
isMobile ? "min-h-dvh" : "flex h-dvh flex-col overflow-clip",
|
||||
// overflow-x-clip on mobile keeps a stray wide descendant from making the
|
||||
// whole viewport scroll horizontally. clip (not hidden) leaves overflow-y
|
||||
// computed as visible, so native body scroll + the sticky breadcrumb keep
|
||||
// working.
|
||||
isMobile ? "min-h-dvh overflow-x-clip" : "flex h-dvh flex-col overflow-clip",
|
||||
)}
|
||||
>
|
||||
<a
|
||||
|
||||
Reference in new issue
Block a user