fix(app): make compact git actions menu-only

Keep compact toolbars narrow and require users to choose potentially consequential Git and workspace actions from the menu.
This commit is contained in:
Mohamed Boudra
2026-08-23 09:39:42 +02:00
parent 7c430777bf
commit dc9fb9aa1b
3 changed files with 90 additions and 7 deletions
+15 -4
View File
@@ -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();
+74 -2
View File
@@ -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 (
<DropdownMenu>
<DropdownMenuTrigger
testID="changes-actions-menu-trigger"
style={menuOnlyTriggerStyle}
accessibilityRole="button"
accessibilityLabel={t("workspace.header.actions.workspaceActions")}
>
<GitBranch size={16} color={theme.colors.foregroundMuted} />
<ChevronDown size={12} color={theme.colors.foregroundMuted} />
</DropdownMenuTrigger>
<DropdownMenuContent align="end" testID="changes-primary-cta-menu">
{menuOnlyActions.map((action, index) => (
<GitActionMenuItem
key={action.id}
action={action}
onSelect={runGitAction}
archiveShortcutKeys={archiveShortcutKeys}
needsSeparator={action.startsGroup}
showSeparator={index > 0}
closeOnSelect={
action.status === "idle" &&
action.id === "pr" &&
action.label === action.pendingLabel &&
action.label === action.successLabel
}
/>
))}
</DropdownMenuContent>
</DropdownMenu>
);
}
return (
<View style={styles.row}>
{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,
},
+1 -1
View File
@@ -415,7 +415,7 @@ function ChangesToolbar(props: ChangesToolbarProps) {
onToggle={onToggleDesktopTree}
/>
) : null}
{isMobile ? <GitActionsSplitButton gitActions={gitActions} /> : null}
{isMobile ? <GitActionsSplitButton gitActions={gitActions} menuOnly /> : null}
<ChangesOptionsMenu {...props} />
</View>
</PaneContentToolbar>