diff --git a/packages/app/e2e/browser/open-project-scroll.spec.ts b/packages/app/e2e/browser/open-project-scroll.spec.ts new file mode 100644 index 0000000000..b0b9e9c9c4 --- /dev/null +++ b/packages/app/e2e/browser/open-project-scroll.spec.ts @@ -0,0 +1,42 @@ +import { type Page } from "@playwright/test"; +import { test, expect } from "../support/fixtures"; + +async function openProjectHome(page: Page) { + await page.goto("/open-project"); + await expect(page.getByText("Add a project", { exact: true })).toBeVisible(); +} + +async function scrollToHomeFooter(page: Page) { + await page.mouse.move(160, 350); + await page.mouse.wheel(0, 1500); +} + +async function expectHomeActionsReachable(page: Page) { + const providers = page.getByTestId("open-project-setup-providers"); + const community = page.getByRole("button", { name: "Community", exact: true }); + await expect(providers).toBeInViewport({ ratio: 1 }); + await expect(community).toBeInViewport({ ratio: 1 }); + const providerBounds = await providers.boundingBox(); + const communityBounds = await community.boundingBox(); + expect(providerBounds).not.toBeNull(); + expect(communityBounds).not.toBeNull(); + expect(communityBounds!.y).toBeGreaterThanOrEqual(providerBounds!.y + providerBounds!.height); +} + +test("home actions remain reachable on a short phone screen", async ({ page }, testInfo) => { + await page.setViewportSize({ width: 320, height: 568 }); + await openProjectHome(page); + await scrollToHomeFooter(page); + await expectHomeActionsReachable(page); + await page.screenshot({ path: testInfo.outputPath("home-scrolled.png") }); +}); + +test("home actions fit on a desktop screen without overlap", async ({ page }) => { + await page.setViewportSize({ width: 1280, height: 900 }); + await openProjectHome(page); + await expectHomeActionsReachable(page); + await expect(page.getByTestId("open-project-setup-providers")).toHaveCSS( + "-webkit-app-region", + "none", + ); +}); diff --git a/packages/app/src/screens/open-project-screen.tsx b/packages/app/src/screens/open-project-screen.tsx index 4c340e0f15..4fcbb4d2fc 100644 --- a/packages/app/src/screens/open-project-screen.tsx +++ b/packages/app/src/screens/open-project-screen.tsx @@ -1,7 +1,7 @@ import { useHosts, useHostRuntimeLastError } from "@/runtime/host-runtime"; import { useCallback, useEffect, useState, type ComponentType } from "react"; import { useTranslation } from "react-i18next"; -import { View, Text, Pressable } from "react-native"; +import { View, Text, Pressable, ScrollView } from "react-native"; import { StyleSheet, useUnistyles } from "react-native-unistyles"; import { useRouter } from "expo-router"; import { FolderOpen, Inbox, Plug, Smartphone } from "lucide-react-native"; @@ -18,11 +18,12 @@ import { HEADER_INNER_HEIGHT_MOBILE, HEADER_TOP_PADDING_MOBILE, } from "@/constants/layout"; -import { TitlebarDragRegion } from "@/components/desktop/titlebar-drag-region"; import { useLocalDaemonServerId } from "@/hooks/use-is-local-daemon"; import { PairDeviceModal } from "@/desktop/components/pair-device-modal"; import { buildSettingsHostSectionRoute } from "@/utils/host-routes"; +const WINDOW_CONTENT_DATASET = { "window-content": "" }; + export function OpenProjectScreen() { const { t } = useTranslation(); const hosts = useHosts(); @@ -61,51 +62,56 @@ export function OpenProjectScreen() { return ( - - - - - - {hosts.map((host) => ( - - ))} - - - - - {localServerId ? ( + + + + + + {hosts.map((host) => ( + + ))} + - ) : null} + + + {localServerId ? ( + + ) : null} + - - - - + + + + ({ backgroundColor: theme.colors.surface0, userSelect: "none", }, + scroll: { + flex: 1, + }, + scrollContent: { + flexGrow: 1, + }, content: { position: "relative", - flex: 1, + flexGrow: 1, + flexShrink: 0, justifyContent: { xs: "flex-start", md: "center" }, alignItems: "center", gap: 0, padding: theme.spacing[6], paddingTop: { xs: theme.spacing[12], md: theme.spacing[6] }, - paddingBottom: { - xs: HEADER_INNER_HEIGHT_MOBILE + HEADER_TOP_PADDING_MOBILE + theme.spacing[6], - md: HEADER_INNER_HEIGHT + theme.spacing[6], - }, }, logo: { marginBottom: theme.spacing[8], @@ -238,10 +247,8 @@ const styles = StyleSheet.create((theme) => ({ lineHeight: 18, }, communityRow: { - position: "absolute", - left: 0, - right: 0, - bottom: { + flexShrink: 0, + paddingBottom: { xs: HEADER_INNER_HEIGHT_MOBILE + HEADER_TOP_PADDING_MOBILE + theme.spacing[2], md: HEADER_INNER_HEIGHT + theme.spacing[2], },