diff --git a/packages/app/e2e/browser/changes-pane.spec.ts b/packages/app/e2e/browser/changes-pane.spec.ts index 0da78b92ea..51503d751b 100644 --- a/packages/app/e2e/browser/changes-pane.spec.ts +++ b/packages/app/e2e/browser/changes-pane.spec.ts @@ -838,25 +838,36 @@ test("desktop Changes toggles a navigation tree beside the expanded diff documen await testInfo.attach("changes-toolbar", { path: screenshot, contentType: "image/png" }); }); -test("compact Changes keeps its options control touch-sized", async ({ page }) => { +test("compact Changes keeps its actions compact and menu-only", async ({ page }) => { const workspace = await createWorkspaceWithMountedTabDiff(); await useUnwrappedDiffLines(page); await openWorkspaceChanges(page, workspace); await page.setViewportSize({ width: 480, height: 900 }); + const actions = page.getByTestId("changes-actions-menu-trigger").filter({ visible: true }); const options = page.getByRole("button", { name: "Diff options" }).filter({ visible: true }); - const [optionsBox, glyphBox] = await Promise.all([ + const [actionsBox, optionsBox, glyphBox] = await Promise.all([ + actions.boundingBox(), options.boundingBox(), options.locator("svg").boundingBox(), ]); - if (!optionsBox || !glyphBox) { - throw new Error("Compact Changes options geometry could not be measured"); + if (!actionsBox || !optionsBox || !glyphBox) { + throw new Error("Compact Changes toolbar geometry could not be measured"); } + expect(actionsBox.width).toBe(48); + expect(actionsBox.height).toBe(28); expect(optionsBox.width).toBe(32); expect(optionsBox.height).toBe(32); expect(glyphBox.width).toBe(18); expect(glyphBox.height).toBe(18); + await expect(actions).not.toContainText("Commit"); + await expect(actions.locator("svg")).toHaveCount(2); + await actions.click(); + await expect(page.getByTestId("changes-primary-cta-menu")).toBeVisible(); + await expect(page.getByTestId("changes-menu-commit")).toContainText("Commit"); + await page.keyboard.press("Escape"); + await options.click(); const wrapLines = page.getByText("Wrap long lines", { exact: true }); await expect(wrapLines).toBeVisible(); diff --git a/packages/app/src/git/actions-split-button.tsx b/packages/app/src/git/actions-split-button.tsx index 04f806e207..59df725ca7 100644 --- a/packages/app/src/git/actions-split-button.tsx +++ b/packages/app/src/git/actions-split-button.tsx @@ -2,7 +2,7 @@ import { LoadingSpinner } from "@/components/ui/loading-spinner"; import { useCallback, useMemo } from "react"; import { View, Text, Pressable, type PressableStateCallbackType } from "react-native"; import { StyleSheet, useUnistyles } from "react-native-unistyles"; -import { ChevronDown, MoreVertical } from "lucide-react-native"; +import { ChevronDown, GitBranch, MoreVertical } from "lucide-react-native"; import { useTranslation } from "react-i18next"; import { DropdownMenu, @@ -22,6 +22,7 @@ import { buttonControlHeight } from "@/components/ui/control-geometry"; interface GitActionsSplitButtonProps { gitActions: GitActions; hideLabels?: boolean; + menuOnly?: boolean; } interface GitActionMenuItemProps { @@ -74,7 +75,11 @@ function GitActionMenuItem({ ); } -export function GitActionsSplitButton({ gitActions, hideLabels }: GitActionsSplitButtonProps) { +export function GitActionsSplitButton({ + gitActions, + hideLabels, + menuOnly = false, +}: GitActionsSplitButtonProps) { const { theme } = useUnistyles(); const { t } = useTranslation(); const runGitAction = useGitActionRunner(); @@ -115,6 +120,62 @@ export function GitActionsSplitButton({ gitActions, hideLabels }: GitActionsSpli [theme.colors.surface2], ); + const menuOnlyTriggerStyle = useCallback( + ({ hovered, pressed, open }: { hovered: boolean; pressed: boolean; open: boolean }) => [ + styles.menuOnlyTrigger, + (hovered || pressed || open) && + inlineUnistylesStyle({ backgroundColor: theme.colors.surface2 }), + ], + [theme.colors.surface2], + ); + + const menuOnlyActions = useMemo( + () => [ + ...(gitActions.primary ? [gitActions.primary] : []), + ...gitActions.secondary, + ...gitActions.menu, + ], + [gitActions.menu, gitActions.primary, gitActions.secondary], + ); + + if (menuOnly) { + if (menuOnlyActions.length === 0) { + return null; + } + + return ( + + + + + + + {menuOnlyActions.map((action, index) => ( + 0} + closeOnSelect={ + action.status === "idle" && + action.id === "pr" && + action.label === action.pendingLabel && + action.label === action.successLabel + } + /> + ))} + + + ); + } + return ( {gitActions.primary ? ( @@ -224,6 +285,17 @@ const styles = StyleSheet.create((theme) => ({ justifyContent: "center", position: "relative", }, + menuOnlyTrigger: { + width: 48, + height: buttonControlHeight.xs, + flexDirection: "row", + alignItems: "center", + justifyContent: "center", + gap: theme.spacing[1], + borderRadius: theme.borderRadius.lg, + borderWidth: theme.borderWidth[1], + borderColor: theme.colors.borderAccent, + }, splitButtonPrimaryDisabled: { opacity: 0.6, }, diff --git a/packages/app/src/git/diff-pane.tsx b/packages/app/src/git/diff-pane.tsx index 0e557516ef..e615d98861 100644 --- a/packages/app/src/git/diff-pane.tsx +++ b/packages/app/src/git/diff-pane.tsx @@ -415,7 +415,7 @@ function ChangesToolbar(props: ChangesToolbarProps) { onToggle={onToggleDesktopTree} /> ) : null} - {isMobile ? : null} + {isMobile ? : null}