mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-04 07:33:18 +00:00
Keep workspaces usable when mobile navigation overflows (#8727)
* fix(app): cap the compact sidebar nav block so plugins cant crowd out workspaces The compact sidebar's top nav group (builtin rows plus plugin header items) was a fixed block with no height bound. On short screens - a foldable's cover display, for instance - enough plugin items pushed the scrollable workspace list to zero height, leaving it unusable. Wrap the nav rows in a bounded ScrollView capped at half the sidebar height. Rows scroll inside the cap when they overflow; the separator moves to the scroll frame so it stays fixed at the region's bottom. On native the scroll view is the gesture-handler variant with the close pan as a simultaneous handler, matching the workspace list's contract. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix(app): keep the mobile nav separator theme-safe and disable iOS bounce Review follow-ups: the border lived on the gesture-handler ScrollView, which Unistyles never rewrites, so move the divider to a SidebarSeparator sibling. On iOS the block rubber-banded under a fixed divider even when every row fit, so disable alwaysBounceVertical. Inline simultaneousHandlers now that GHScrollView types it, and tag the region for Maestro. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(app): keep workspaces reachable with overflowing sidebar navigation Reproduce the short compact sidebar failure with the existing ten-row plugin fixture. Check that navigation scrolls independently and both navigation actions and workspaces remain usable. Co-authored-by: hugo <jaredwangcoode@gmail.com> --------- Co-authored-by: hugo <jaredwangcoode@gmail.com> Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
Devin
hugo
parent
eba5bdf810
commit
67628b48fe
@@ -2,6 +2,7 @@ import path from "node:path";
|
||||
import type { Locator, Page } from "@playwright/test";
|
||||
import { expect, test } from "../support/fixtures";
|
||||
import { gotoWorkspace } from "../support/helpers/launcher";
|
||||
import { gotoAppShell } from "../support/helpers/app";
|
||||
import {
|
||||
BOTS_PLUGIN_ID,
|
||||
COMPACT,
|
||||
@@ -176,6 +177,37 @@ test.describe("Plugin sidebar items", () => {
|
||||
await cleanup();
|
||||
});
|
||||
|
||||
test("a short compact sidebar keeps workspaces usable while navigation scrolls", async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.setViewportSize({ width: 390, height: 600 });
|
||||
await gotoAppShell(page);
|
||||
await page.getByRole("button", { name: "Open menu", exact: true }).first().click();
|
||||
await expect(botRow(page, "status")).toBeAttached();
|
||||
const workspace = visibleTestId(page, `sidebar-workspace-row-${getServerId()}:${workspaceId}`);
|
||||
await expect(workspace).toBeInViewport({ ratio: 1 });
|
||||
|
||||
await test.step("scrolling navigation reaches its last row without moving workspaces", async () => {
|
||||
const before = await boxOf(workspace);
|
||||
await visibleTestId(page, "sidebar-global-new-workspace").hover();
|
||||
await page.mouse.wheel(0, 800);
|
||||
await expect(botRow(page, "status")).toBeInViewport({ ratio: 1 });
|
||||
await expect(workspace).toBeInViewport({ ratio: 1 });
|
||||
expect((await boxOf(workspace)).y).toBe(before.y);
|
||||
await botRow(page, "status").click();
|
||||
await expect(page.getByText("Bot status details", { exact: true })).toBeVisible();
|
||||
await page.getByRole("button", { name: "Close bot status", exact: true }).click();
|
||||
});
|
||||
|
||||
await test.step("the workspace still opens and closes the sidebar", async () => {
|
||||
await workspace.click();
|
||||
await expect(
|
||||
page.getByRole("button", { name: "Open menu", exact: true }).first(),
|
||||
).toBeVisible();
|
||||
await expect(workspace).not.toBeInViewport();
|
||||
});
|
||||
});
|
||||
|
||||
test("a restored plugin screen waits for its catalog before rendering", async ({ page }) => {
|
||||
await page.setViewportSize(WIDE);
|
||||
await gotoWorkspace(page, workspaceId);
|
||||
|
||||
@@ -12,12 +12,13 @@ import { useTranslation } from "react-i18next";
|
||||
import { memo, useCallback, useEffect, useMemo, useRef, useState, type RefObject } from "react";
|
||||
import {
|
||||
Pressable,
|
||||
ScrollView,
|
||||
StyleSheet as RNStyleSheet,
|
||||
Text,
|
||||
useWindowDimensions,
|
||||
View,
|
||||
} from "react-native";
|
||||
import { Gesture } from "react-native-gesture-handler";
|
||||
import { Gesture, ScrollView as GHScrollView } from "react-native-gesture-handler";
|
||||
import Animated, { runOnJS, useAnimatedStyle, useSharedValue } from "react-native-reanimated";
|
||||
import { scheduleOnRN } from "react-native-worklets";
|
||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
@@ -41,6 +42,7 @@ import { SidebarSectionHeader } from "@/components/sidebar/sidebar-section-heade
|
||||
import { Shortcut } from "@/components/ui/shortcut";
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
||||
import { HEADER_INNER_HEIGHT, useIsCompactFormFactor } from "@/constants/layout";
|
||||
import { isNative as platformIsNative } from "@/constants/platform";
|
||||
import { useOpenAddProject } from "@/hooks/use-open-add-project";
|
||||
import { useImportSession } from "@/hooks/use-import-session";
|
||||
import { useShortcutKeys } from "@/hooks/use-shortcut-keys";
|
||||
@@ -555,6 +557,14 @@ function MobileSidebar({
|
||||
[insetsTop, insetsBottom, theme.colors.surfaceSidebar],
|
||||
);
|
||||
|
||||
const navRows = (
|
||||
<SidebarNavRows
|
||||
style={styles.mobileNavRowsGroup}
|
||||
onBeforeNavigate={closeSidebar}
|
||||
firstRowTrailing={closeButton}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<MobilePanelOverlay
|
||||
panel="agent-list"
|
||||
@@ -563,11 +573,29 @@ function MobileSidebar({
|
||||
>
|
||||
<View style={styles.sidebarContent} pointerEvents="auto">
|
||||
<WindowChromeSafeArea placement="below" />
|
||||
<SidebarNavRows
|
||||
style={styles.sidebarHeaderGroup}
|
||||
onBeforeNavigate={closeSidebar}
|
||||
firstRowTrailing={closeButton}
|
||||
/>
|
||||
{platformIsNative ? (
|
||||
<GHScrollView
|
||||
// Same contract as the workspace list: vertical drags scroll the nav
|
||||
// block, a clear horizontal intent still activates the close pan.
|
||||
simultaneousHandlers={closeGestureRef}
|
||||
style={styles.mobileNavScroll}
|
||||
showsVerticalScrollIndicator={false}
|
||||
alwaysBounceVertical={false}
|
||||
testID="sidebar-nav-scroll"
|
||||
>
|
||||
{navRows}
|
||||
</GHScrollView>
|
||||
) : (
|
||||
<ScrollView
|
||||
style={styles.mobileNavScroll}
|
||||
showsVerticalScrollIndicator={false}
|
||||
alwaysBounceVertical={false}
|
||||
testID="sidebar-nav-scroll"
|
||||
>
|
||||
{navRows}
|
||||
</ScrollView>
|
||||
)}
|
||||
<SidebarSeparator />
|
||||
|
||||
{isInitialLoad && !hasActiveHostFilter ? (
|
||||
<SidebarAgentListSkeleton />
|
||||
@@ -839,6 +867,18 @@ const styles = StyleSheet.create((theme) => ({
|
||||
sidebarHeaderGroupBelowChrome: {
|
||||
paddingTop: 0,
|
||||
},
|
||||
// The nav block caps at half the sidebar; a long item list scrolls inside the
|
||||
// cap instead of crowding the workspace list off a short screen.
|
||||
mobileNavScroll: {
|
||||
flexGrow: 0,
|
||||
flexShrink: 1,
|
||||
maxHeight: "50%",
|
||||
},
|
||||
mobileNavRowsGroup: {
|
||||
paddingTop: theme.spacing[2],
|
||||
gap: 2,
|
||||
paddingBottom: theme.spacing[1.5],
|
||||
},
|
||||
sidebarContent: {
|
||||
flex: 1,
|
||||
minHeight: 0,
|
||||
|
||||
Reference in New Issue
Block a user