Revamp Changes pane view + ship menus and stabilize e2e selectors
This commit is contained in:
@@ -29,12 +29,33 @@ function getChangesActionButton(page: Page, label: string) {
|
|||||||
return getChangesActionLabel(page, label).locator('..');
|
return getChangesActionLabel(page, label).locator('..');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function selectChangesView(page: Page, view: 'working' | 'base') {
|
||||||
|
const scope = getChangesScope(page);
|
||||||
|
await scope.getByTestId('changes-view-selector').click();
|
||||||
|
if (view === 'working') {
|
||||||
|
await page.getByTestId('changes-mode-uncommitted').click();
|
||||||
|
} else {
|
||||||
|
await page.getByTestId('changes-mode-base').click();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function openChangesOverflowMenu(page: Page) {
|
async function openChangesOverflowMenu(page: Page) {
|
||||||
const menuButton = page.getByTestId('changes-overflow-menu').first();
|
const menuButton = getChangesScope(page).locator('[data-testid="changes-overflow-menu"]:visible').first();
|
||||||
await expect(menuButton).toBeVisible();
|
await expect(menuButton).toBeVisible();
|
||||||
await menuButton.click();
|
await menuButton.click();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function openChangesShipMenu(page: Page) {
|
||||||
|
const backdrop = page.getByRole('button', { name: 'Ship menu backdrop' }).first();
|
||||||
|
if (await backdrop.isVisible().catch(() => false)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const scope = getChangesScope(page);
|
||||||
|
const caret = scope.getByTestId('changes-ship-caret').first();
|
||||||
|
await expect(caret).toBeVisible();
|
||||||
|
await caret.click();
|
||||||
|
}
|
||||||
|
|
||||||
async function openChangesPanel(page: Page, options?: { expectGit?: boolean }) {
|
async function openChangesPanel(page: Page, options?: { expectGit?: boolean }) {
|
||||||
const changesHeader = getChangesHeader(page);
|
const changesHeader = getChangesHeader(page);
|
||||||
if (!(await changesHeader.isVisible())) {
|
if (!(await changesHeader.isVisible())) {
|
||||||
@@ -114,6 +135,8 @@ async function createAgentAndWait(page: Page, message: string) {
|
|||||||
async function requestCwd(page: Page) {
|
async function requestCwd(page: Page) {
|
||||||
await sendPrompt(page, 'Run `pwd` and respond with exactly: CWD: <path>');
|
await sendPrompt(page, 'Run `pwd` and respond with exactly: CWD: <path>');
|
||||||
const message = await waitForAssistantText(page, 'CWD:');
|
const message = await waitForAssistantText(page, 'CWD:');
|
||||||
|
// The assistant streams tokens; make sure we capture the full path (not a partial prefix).
|
||||||
|
await expect.poll(async () => (await message.textContent()) ?? '', { timeout: 60000 }).toContain('.paseo/worktrees/');
|
||||||
const content = (await message.textContent()) ?? '';
|
const content = (await message.textContent()) ?? '';
|
||||||
const match = content.match(/CWD:\s*(\S+)/);
|
const match = content.match(/CWD:\s*(\S+)/);
|
||||||
if (!match) {
|
if (!match) {
|
||||||
@@ -168,9 +191,8 @@ async function enableCreateWorktree(page: Page) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function refreshUncommittedMode(page: Page) {
|
async function refreshUncommittedMode(page: Page) {
|
||||||
const changesScope = getChangesScope(page);
|
await selectChangesView(page, 'base');
|
||||||
await changesScope.getByTestId('changes-mode-base').click();
|
await selectChangesView(page, 'working');
|
||||||
await changesScope.getByTestId('changes-mode-uncommitted').click();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function refreshChangesTab(page: Page) {
|
async function refreshChangesTab(page: Page) {
|
||||||
@@ -253,17 +275,17 @@ test('checkout-first Changes panel ship loop', async ({ page }) => {
|
|||||||
});
|
});
|
||||||
await getChangesScope(page).getByTestId('diff-file-0-toggle').first().click();
|
await getChangesScope(page).getByTestId('diff-file-0-toggle').first().click();
|
||||||
await expect(page.getByText('First change')).toBeVisible();
|
await expect(page.getByText('First change')).toBeVisible();
|
||||||
await expect(getChangesActionLabel(page, 'Commit')).toBeVisible();
|
await expect(getChangesScope(page).getByTestId('changes-action-commit')).toBeVisible();
|
||||||
|
|
||||||
await getChangesActionButton(page, 'Commit').click();
|
await getChangesScope(page).getByTestId('changes-action-commit').click();
|
||||||
await getChangesScope(page).getByTestId('changes-mode-uncommitted').click();
|
await selectChangesView(page, 'working');
|
||||||
await refreshChangesTab(page);
|
await refreshChangesTab(page);
|
||||||
await expect(getChangesScope(page).getByText('No uncommitted changes')).toBeVisible({
|
await expect(getChangesScope(page).getByText('No uncommitted changes')).toBeVisible({
|
||||||
timeout: 30000,
|
timeout: 30000,
|
||||||
});
|
});
|
||||||
await expect(getChangesActionLabel(page, 'Commit')).toHaveCount(0);
|
await expect(getChangesScope(page).getByTestId('changes-action-commit')).toHaveCount(0);
|
||||||
|
|
||||||
await getChangesScope(page).getByTestId('changes-mode-base').click();
|
await selectChangesView(page, 'base');
|
||||||
await expect(getChangesScope(page).getByText('README.md', { exact: true })).toBeVisible({
|
await expect(getChangesScope(page).getByText('README.md', { exact: true })).toBeVisible({
|
||||||
timeout: 30000,
|
timeout: 30000,
|
||||||
});
|
});
|
||||||
@@ -277,19 +299,19 @@ test('checkout-first Changes panel ship loop', async ({ page }) => {
|
|||||||
timeout: 30000,
|
timeout: 30000,
|
||||||
});
|
});
|
||||||
await expect(getChangesScope(page).getByText('README.md', { exact: true })).toHaveCount(0);
|
await expect(getChangesScope(page).getByText('README.md', { exact: true })).toHaveCount(0);
|
||||||
await expect(getChangesActionLabel(page, 'Commit')).toBeVisible();
|
await expect(getChangesScope(page).getByTestId('changes-action-commit')).toBeVisible();
|
||||||
|
|
||||||
await getChangesScope(page).getByTestId('changes-mode-base').click();
|
await selectChangesView(page, 'base');
|
||||||
await expect(getChangesScope(page).getByText('README.md', { exact: true })).toBeVisible({
|
await expect(getChangesScope(page).getByText('README.md', { exact: true })).toBeVisible({
|
||||||
timeout: 30000,
|
timeout: 30000,
|
||||||
});
|
});
|
||||||
|
|
||||||
await getChangesActionButton(page, 'Commit').click();
|
await getChangesScope(page).getByTestId('changes-action-commit').click();
|
||||||
await getChangesScope(page).getByTestId('changes-mode-uncommitted').click();
|
await selectChangesView(page, 'working');
|
||||||
await expect(page.getByText('No uncommitted changes')).toBeVisible({ timeout: 30000 });
|
await expect(page.getByText('No uncommitted changes')).toBeVisible({ timeout: 30000 });
|
||||||
await expect(getChangesActionLabel(page, 'Commit')).toHaveCount(0);
|
await expect(getChangesScope(page).getByTestId('changes-action-commit')).toHaveCount(0);
|
||||||
|
|
||||||
await getChangesScope(page).getByTestId('changes-mode-base').click();
|
await selectChangesView(page, 'base');
|
||||||
await expect(getChangesScope(page).getByText('README.md', { exact: true })).toBeVisible({
|
await expect(getChangesScope(page).getByText('README.md', { exact: true })).toBeVisible({
|
||||||
timeout: 30000,
|
timeout: 30000,
|
||||||
});
|
});
|
||||||
@@ -297,20 +319,21 @@ test('checkout-first Changes panel ship loop', async ({ page }) => {
|
|||||||
timeout: 30000,
|
timeout: 30000,
|
||||||
});
|
});
|
||||||
|
|
||||||
await expect(getChangesActionLabel(page, 'Create PR')).toBeVisible();
|
await openChangesShipMenu(page);
|
||||||
await getChangesActionButton(page, 'Create PR').click();
|
await page.getByTestId('changes-ship-create-pr').click();
|
||||||
await expect(getChangesScope(page).getByTestId('changes-pr-status')).toContainText(/open/i, {
|
await expect(getChangesScope(page).getByTestId('changes-pr-status')).toContainText(/open/i, {
|
||||||
timeout: 60000,
|
timeout: 60000,
|
||||||
});
|
});
|
||||||
await expect(getChangesActionLabel(page, 'Create PR')).toHaveCount(0);
|
await openChangesShipMenu(page);
|
||||||
|
await expect(page.getByTestId('changes-ship-open-pr')).toBeVisible();
|
||||||
|
|
||||||
await getChangesActionButton(page, 'Merge to base').click();
|
await page.getByTestId('changes-ship-merge').click();
|
||||||
await getChangesScope(page).getByTestId('changes-mode-base').click();
|
await selectChangesView(page, 'base');
|
||||||
await expect(getChangesScope(page).getByText('No base changes')).toBeVisible({
|
await expect(getChangesScope(page).getByText('No base changes')).toBeVisible({
|
||||||
timeout: 60000,
|
timeout: 60000,
|
||||||
});
|
});
|
||||||
await refreshChangesTab(page);
|
await refreshChangesTab(page);
|
||||||
await expect(getChangesActionLabel(page, 'Merge to base')).toHaveCount(0, { timeout: 30000 });
|
await expect(getChangesScope(page).getByTestId('changes-ship-caret')).toHaveCount(0, { timeout: 30000 });
|
||||||
|
|
||||||
await openChangesOverflowMenu(page);
|
await openChangesOverflowMenu(page);
|
||||||
await expect(page.getByTestId('changes-menu-archive')).toBeVisible();
|
await expect(page.getByTestId('changes-menu-archive')).toBeVisible();
|
||||||
|
|||||||
@@ -15,7 +15,9 @@ import {
|
|||||||
} from "react-native";
|
} from "react-native";
|
||||||
import { ScrollView, type ScrollView as ScrollViewType } from "react-native-gesture-handler";
|
import { ScrollView, type ScrollView as ScrollViewType } from "react-native-gesture-handler";
|
||||||
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
||||||
import { ChevronRight, GitBranch, MoreVertical, RotateCcw } from "lucide-react-native";
|
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||||
|
import * as Linking from "expo-linking";
|
||||||
|
import { ChevronDown, ChevronRight, GitBranch, MoreVertical, RotateCcw } from "lucide-react-native";
|
||||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
import { useSessionStore } from "@/stores/session-store";
|
import { useSessionStore } from "@/stores/session-store";
|
||||||
import {
|
import {
|
||||||
@@ -361,6 +363,13 @@ export function GitDiffPane({ serverId, agentId }: GitDiffPaneProps) {
|
|||||||
const [overflowVisible, setOverflowVisible] = useState(false);
|
const [overflowVisible, setOverflowVisible] = useState(false);
|
||||||
const [overflowAnchor, setOverflowAnchor] = useState({ left: 0, top: 0 });
|
const [overflowAnchor, setOverflowAnchor] = useState({ left: 0, top: 0 });
|
||||||
const [overflowHeight, setOverflowHeight] = useState(0);
|
const [overflowHeight, setOverflowHeight] = useState(0);
|
||||||
|
const [viewMenuVisible, setViewMenuVisible] = useState(false);
|
||||||
|
const [viewMenuAnchor, setViewMenuAnchor] = useState({ left: 0, top: 0 });
|
||||||
|
const [viewMenuHeight, setViewMenuHeight] = useState(0);
|
||||||
|
const [shipMenuVisible, setShipMenuVisible] = useState(false);
|
||||||
|
const [shipMenuAnchor, setShipMenuAnchor] = useState({ left: 0, top: 0 });
|
||||||
|
const [shipMenuHeight, setShipMenuHeight] = useState(0);
|
||||||
|
const [shipDefault, setShipDefault] = useState<"merge" | "pr">("merge");
|
||||||
const { status, isLoading: isStatusLoading, isFetching: isStatusFetching, isError: isStatusError, error: statusError, refresh: refreshStatus } =
|
const { status, isLoading: isStatusLoading, isFetching: isStatusFetching, isError: isStatusError, error: statusError, refresh: refreshStatus } =
|
||||||
useCheckoutStatusQuery({ serverId, agentId });
|
useCheckoutStatusQuery({ serverId, agentId });
|
||||||
const gitStatus = status && status.isGit ? status : null;
|
const gitStatus = status && status.isGit ? status : null;
|
||||||
@@ -428,6 +437,44 @@ export function GitDiffPane({ serverId, agentId }: GitDiffPaneProps) {
|
|||||||
void refreshPrStatus();
|
void refreshPrStatus();
|
||||||
}, [refreshDiff, refreshStatus, refreshPrStatus]);
|
}, [refreshDiff, refreshStatus, refreshPrStatus]);
|
||||||
|
|
||||||
|
const shipDefaultStorageKey = useMemo(() => {
|
||||||
|
if (!gitStatus?.repoRoot) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return `@paseo:changes-ship-default:${gitStatus.repoRoot}`;
|
||||||
|
}, [gitStatus?.repoRoot]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!shipDefaultStorageKey) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let isActive = true;
|
||||||
|
AsyncStorage.getItem(shipDefaultStorageKey)
|
||||||
|
.then((value) => {
|
||||||
|
if (!isActive) return;
|
||||||
|
if (value === "pr" || value === "merge") {
|
||||||
|
setShipDefault(value);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => undefined);
|
||||||
|
return () => {
|
||||||
|
isActive = false;
|
||||||
|
};
|
||||||
|
}, [shipDefaultStorageKey]);
|
||||||
|
|
||||||
|
const persistShipDefault = useCallback(
|
||||||
|
async (next: "merge" | "pr") => {
|
||||||
|
setShipDefault(next);
|
||||||
|
if (!shipDefaultStorageKey) return;
|
||||||
|
try {
|
||||||
|
await AsyncStorage.setItem(shipDefaultStorageKey, next);
|
||||||
|
} catch {
|
||||||
|
// Ignore persistence failures; default will reset to "merge".
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[shipDefaultStorageKey]
|
||||||
|
);
|
||||||
|
|
||||||
const handleOpenOverflowMenu = useCallback((event: any) => {
|
const handleOpenOverflowMenu = useCallback((event: any) => {
|
||||||
const { pageX, pageY } = event?.nativeEvent ?? {};
|
const { pageX, pageY } = event?.nativeEvent ?? {};
|
||||||
setOverflowAnchor({
|
setOverflowAnchor({
|
||||||
@@ -447,6 +494,44 @@ export function GitDiffPane({ serverId, agentId }: GitDiffPaneProps) {
|
|||||||
setOverflowHeight((current) => (current === height ? current : height));
|
setOverflowHeight((current) => (current === height ? current : height));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const handleOpenViewMenu = useCallback((event: any) => {
|
||||||
|
const { pageX, pageY } = event?.nativeEvent ?? {};
|
||||||
|
setViewMenuAnchor({
|
||||||
|
left: typeof pageX === "number" ? pageX : 0,
|
||||||
|
top: typeof pageY === "number" ? pageY : 0,
|
||||||
|
});
|
||||||
|
setViewMenuVisible(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleCloseViewMenu = useCallback(() => {
|
||||||
|
setViewMenuVisible(false);
|
||||||
|
setViewMenuHeight(0);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleViewMenuLayout = useCallback((event: LayoutChangeEvent) => {
|
||||||
|
const { height } = event.nativeEvent.layout;
|
||||||
|
setViewMenuHeight((current) => (current === height ? current : height));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleOpenShipMenu = useCallback((event: any) => {
|
||||||
|
const { pageX, pageY } = event?.nativeEvent ?? {};
|
||||||
|
setShipMenuAnchor({
|
||||||
|
left: typeof pageX === "number" ? pageX : 0,
|
||||||
|
top: typeof pageY === "number" ? pageY : 0,
|
||||||
|
});
|
||||||
|
setShipMenuVisible(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleCloseShipMenu = useCallback(() => {
|
||||||
|
setShipMenuVisible(false);
|
||||||
|
setShipMenuHeight(0);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleShipMenuLayout = useCallback((event: LayoutChangeEvent) => {
|
||||||
|
const { height } = event.nativeEvent.layout;
|
||||||
|
setShipMenuHeight((current) => (current === height ? current : height));
|
||||||
|
}, []);
|
||||||
|
|
||||||
const handleToggleExpanded = useCallback((path: string) => {
|
const handleToggleExpanded = useCallback((path: string) => {
|
||||||
setExpandedByPath((prev) => ({
|
setExpandedByPath((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
@@ -624,14 +709,17 @@ export function GitDiffPane({ serverId, agentId }: GitDiffPaneProps) {
|
|||||||
gitStatus?.currentBranch ?? (notGit ? "Not a git repository" : "Unknown");
|
gitStatus?.currentBranch ?? (notGit ? "Not a git repository" : "Unknown");
|
||||||
const actionsDisabled = !isGit || Boolean(status?.error) || isStatusLoading;
|
const actionsDisabled = !isGit || Boolean(status?.error) || isStatusLoading;
|
||||||
const hasUncommittedChanges = Boolean(gitStatus?.isDirty);
|
const hasUncommittedChanges = Boolean(gitStatus?.isDirty);
|
||||||
const showCommitAction =
|
const aheadCount = gitStatus?.aheadBehind?.ahead ?? 0;
|
||||||
!isGit || hasUncommittedChanges || (diffMode === "uncommitted" && hasChanges);
|
const canShowCommit = isGit && hasUncommittedChanges;
|
||||||
const showCreatePrAction = !isGit || !prStatus;
|
const canShowShip = isGit && aheadCount > 0;
|
||||||
const showMergeAction =
|
const baseRefLabel = useMemo(() => {
|
||||||
diffMode === "base" && isGit && (gitStatus?.aheadBehind?.ahead ?? 0) > 0;
|
if (!baseRef) return "base";
|
||||||
|
const trimmed = baseRef.replace(/^refs\/(heads|remotes)\//, "").trim();
|
||||||
|
return trimmed.startsWith("origin/") ? trimmed.slice("origin/".length) : trimmed;
|
||||||
|
}, [baseRef]);
|
||||||
const commitDisabled = actionsDisabled || commitMutation.isPending;
|
const commitDisabled = actionsDisabled || commitMutation.isPending;
|
||||||
const prDisabled = actionsDisabled || prMutation.isPending;
|
const prDisabled = actionsDisabled || prMutation.isPending;
|
||||||
const mergeDisabled = actionsDisabled || mergeMutation.isPending;
|
const mergeDisabled = actionsDisabled || mergeMutation.isPending || hasUncommittedChanges;
|
||||||
const archiveDisabled =
|
const archiveDisabled =
|
||||||
actionsDisabled ||
|
actionsDisabled ||
|
||||||
archiveMutation.isPending ||
|
archiveMutation.isPending ||
|
||||||
@@ -698,62 +786,43 @@ export function GitDiffPane({ serverId, agentId }: GitDiffPaneProps) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
type PrimaryAction = {
|
const hasPullRequest = Boolean(prStatus?.url);
|
||||||
key: "commit" | "create_pr" | "merge";
|
const prActionLabel = hasPullRequest ? "Open PR" : "Create PR";
|
||||||
label: string;
|
|
||||||
testID: string;
|
|
||||||
disabled: boolean;
|
|
||||||
isPending: boolean;
|
|
||||||
onPress: () => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
const primaryAction: PrimaryAction | null = useMemo(() => {
|
type ShipActionKey = "merge" | "pr";
|
||||||
if (!isGit) {
|
const shipActions: { key: ShipActionKey; label: string; disabled: boolean; isPending: boolean }[] =
|
||||||
return null;
|
useMemo(
|
||||||
|
() => [
|
||||||
|
{ key: "merge", label: "Merge", disabled: mergeDisabled, isPending: mergeMutation.isPending },
|
||||||
|
{ key: "pr", label: prActionLabel, disabled: prDisabled, isPending: prMutation.isPending },
|
||||||
|
],
|
||||||
|
[mergeDisabled, mergeMutation.isPending, prActionLabel, prDisabled, prMutation.isPending]
|
||||||
|
);
|
||||||
|
|
||||||
|
const resolvedShipPrimary: ShipActionKey | null = useMemo(() => {
|
||||||
|
if (!canShowShip) return null;
|
||||||
|
const preferred = shipDefault;
|
||||||
|
const preferredMeta = shipActions.find((action) => action.key === preferred);
|
||||||
|
if (preferredMeta && !preferredMeta.disabled) {
|
||||||
|
return preferred;
|
||||||
}
|
}
|
||||||
if (showCommitAction) {
|
const fallback = shipActions.find((action) => !action.disabled);
|
||||||
return {
|
return fallback?.key ?? preferred;
|
||||||
key: "commit",
|
}, [canShowShip, shipActions, shipDefault]);
|
||||||
label: "Commit",
|
|
||||||
testID: "changes-primary-commit",
|
const shipPrimaryDisabled = useMemo(() => {
|
||||||
disabled: commitDisabled,
|
if (!resolvedShipPrimary) return true;
|
||||||
isPending: commitMutation.isPending,
|
if (actionsDisabled) return true;
|
||||||
onPress: () => commitMutation.mutate(),
|
const meta = shipActions.find((action) => action.key === resolvedShipPrimary);
|
||||||
};
|
return meta?.disabled ?? true;
|
||||||
}
|
}, [actionsDisabled, resolvedShipPrimary, shipActions]);
|
||||||
if (showCreatePrAction) {
|
|
||||||
return {
|
const shipPrimaryPending = useMemo(() => {
|
||||||
key: "create_pr",
|
if (!resolvedShipPrimary) return false;
|
||||||
label: "Create PR",
|
if (resolvedShipPrimary === "merge") return mergeMutation.isPending;
|
||||||
testID: "changes-primary-create-pr",
|
// "pr" maps to open PR (instant) or create PR (mutation)
|
||||||
disabled: prDisabled,
|
return hasPullRequest ? false : prMutation.isPending;
|
||||||
isPending: prMutation.isPending,
|
}, [hasPullRequest, mergeMutation.isPending, prMutation.isPending, resolvedShipPrimary]);
|
||||||
onPress: () => prMutation.mutate(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (showMergeAction) {
|
|
||||||
return {
|
|
||||||
key: "merge",
|
|
||||||
label: "Merge to base",
|
|
||||||
testID: "changes-primary-merge",
|
|
||||||
disabled: mergeDisabled,
|
|
||||||
isPending: mergeMutation.isPending,
|
|
||||||
onPress: () => mergeMutation.mutate(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}, [
|
|
||||||
commitDisabled,
|
|
||||||
commitMutation,
|
|
||||||
isGit,
|
|
||||||
mergeDisabled,
|
|
||||||
mergeMutation,
|
|
||||||
prDisabled,
|
|
||||||
prMutation,
|
|
||||||
showCommitAction,
|
|
||||||
showCreatePrAction,
|
|
||||||
showMergeAction,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const overflowPosition = useMemo(() => {
|
const overflowPosition = useMemo(() => {
|
||||||
if (!overflowVisible) {
|
if (!overflowVisible) {
|
||||||
@@ -780,6 +849,56 @@ export function GitDiffPane({ serverId, agentId }: GitDiffPaneProps) {
|
|||||||
windowWidth,
|
windowWidth,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const viewMenuPosition = useMemo(() => {
|
||||||
|
if (!viewMenuVisible) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const menuWidth = 180;
|
||||||
|
const horizontalPadding = theme.spacing[2];
|
||||||
|
const verticalPadding = theme.spacing[2];
|
||||||
|
const maxLeft = Math.max(horizontalPadding, windowWidth - menuWidth - horizontalPadding);
|
||||||
|
const maxTop = Math.max(verticalPadding, windowHeight - viewMenuHeight - verticalPadding);
|
||||||
|
const left = Math.min(
|
||||||
|
Math.max(viewMenuAnchor.left - menuWidth + horizontalPadding, horizontalPadding),
|
||||||
|
maxLeft
|
||||||
|
);
|
||||||
|
const top = Math.min(Math.max(viewMenuAnchor.top + verticalPadding, verticalPadding), maxTop);
|
||||||
|
return { top, left, width: menuWidth };
|
||||||
|
}, [
|
||||||
|
theme.spacing,
|
||||||
|
viewMenuAnchor.left,
|
||||||
|
viewMenuAnchor.top,
|
||||||
|
viewMenuHeight,
|
||||||
|
viewMenuVisible,
|
||||||
|
windowHeight,
|
||||||
|
windowWidth,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const shipMenuPosition = useMemo(() => {
|
||||||
|
if (!shipMenuVisible) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const menuWidth = 220;
|
||||||
|
const horizontalPadding = theme.spacing[2];
|
||||||
|
const verticalPadding = theme.spacing[2];
|
||||||
|
const maxLeft = Math.max(horizontalPadding, windowWidth - menuWidth - horizontalPadding);
|
||||||
|
const maxTop = Math.max(verticalPadding, windowHeight - shipMenuHeight - verticalPadding);
|
||||||
|
const left = Math.min(
|
||||||
|
Math.max(shipMenuAnchor.left - menuWidth + horizontalPadding, horizontalPadding),
|
||||||
|
maxLeft
|
||||||
|
);
|
||||||
|
const top = Math.min(Math.max(shipMenuAnchor.top + verticalPadding, verticalPadding), maxTop);
|
||||||
|
return { top, left, width: menuWidth };
|
||||||
|
}, [
|
||||||
|
shipMenuAnchor.left,
|
||||||
|
shipMenuAnchor.top,
|
||||||
|
shipMenuHeight,
|
||||||
|
shipMenuVisible,
|
||||||
|
theme.spacing,
|
||||||
|
windowHeight,
|
||||||
|
windowWidth,
|
||||||
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
<View style={styles.header} testID="changes-header">
|
<View style={styles.header} testID="changes-header">
|
||||||
@@ -788,72 +907,101 @@ export function GitDiffPane({ serverId, agentId }: GitDiffPaneProps) {
|
|||||||
<Text style={styles.branchLabel} testID="changes-branch" numberOfLines={1}>
|
<Text style={styles.branchLabel} testID="changes-branch" numberOfLines={1}>
|
||||||
{branchLabel}
|
{branchLabel}
|
||||||
</Text>
|
</Text>
|
||||||
{isStatusFetching && (
|
{isStatusFetching && (
|
||||||
<ActivityIndicator size="small" color={theme.colors.foregroundMuted} />
|
<ActivityIndicator size="small" color={theme.colors.foregroundMuted} />
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.modeToggle}>
|
{isGit ? (
|
||||||
<Pressable
|
<Pressable
|
||||||
testID="changes-mode-uncommitted"
|
testID="changes-view-selector"
|
||||||
onPress={() => setDiffMode("uncommitted")}
|
onPress={handleOpenViewMenu}
|
||||||
style={[
|
style={styles.viewSelector}
|
||||||
styles.modeToggleButton,
|
accessibilityRole="button"
|
||||||
diffMode === "uncommitted" && styles.modeToggleButtonActive,
|
accessibilityLabel="Change diff view"
|
||||||
]}
|
|
||||||
>
|
>
|
||||||
<Text
|
<Text style={styles.viewSelectorText}>
|
||||||
style={[
|
{diffMode === "uncommitted" ? "Working" : "Base"}
|
||||||
styles.modeToggleText,
|
|
||||||
diffMode === "uncommitted" && styles.modeToggleTextActive,
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
Uncommitted
|
|
||||||
</Text>
|
</Text>
|
||||||
|
<ChevronDown size={14} color={theme.colors.foregroundMuted} />
|
||||||
</Pressable>
|
</Pressable>
|
||||||
<Pressable
|
) : null}
|
||||||
testID="changes-mode-base"
|
|
||||||
onPress={() => setDiffMode("base")}
|
|
||||||
style={[
|
|
||||||
styles.modeToggleButton,
|
|
||||||
diffMode === "base" && styles.modeToggleButtonActive,
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Text
|
|
||||||
style={[
|
|
||||||
styles.modeToggleText,
|
|
||||||
diffMode === "base" && styles.modeToggleTextActive,
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
Base
|
|
||||||
</Text>
|
|
||||||
</Pressable>
|
|
||||||
</View>
|
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{isGit ? (
|
{isGit ? (
|
||||||
<View style={styles.toolbarRow} testID="changes-toolbar">
|
<View style={styles.toolbarRow} testID="changes-toolbar">
|
||||||
<View style={styles.toolbarPrimary}>
|
<View style={styles.toolbarLeft}>
|
||||||
{primaryAction ? (
|
{canShowCommit ? (
|
||||||
<Pressable
|
<Pressable
|
||||||
testID={primaryAction.testID}
|
testID="changes-action-commit"
|
||||||
style={[
|
style={[
|
||||||
styles.primaryActionButton,
|
styles.secondaryActionButton,
|
||||||
primaryAction.disabled && styles.primaryActionButtonDisabled,
|
commitDisabled && styles.secondaryActionButtonDisabled,
|
||||||
]}
|
]}
|
||||||
onPress={primaryAction.onPress}
|
onPress={() => commitMutation.mutate()}
|
||||||
disabled={primaryAction.disabled}
|
disabled={commitDisabled}
|
||||||
>
|
>
|
||||||
{primaryAction.isPending ? (
|
{commitMutation.isPending ? (
|
||||||
<ActivityIndicator size="small" color={theme.colors.foreground} />
|
<ActivityIndicator size="small" color={theme.colors.foreground} />
|
||||||
) : (
|
) : (
|
||||||
<Text style={styles.primaryActionText}>{primaryAction.label}</Text>
|
<Text style={styles.secondaryActionText}>Commit…</Text>
|
||||||
)}
|
)}
|
||||||
</Pressable>
|
</Pressable>
|
||||||
) : (
|
) : (
|
||||||
<View style={styles.toolbarPrimarySpacer} />
|
<View style={styles.toolbarLeftSpacer} />
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.toolbarRight}>
|
<View style={styles.toolbarRight}>
|
||||||
|
{canShowShip ? (
|
||||||
|
<View style={styles.shipSplitButton}>
|
||||||
|
<Pressable
|
||||||
|
testID="changes-ship-primary"
|
||||||
|
style={[
|
||||||
|
styles.shipPrimaryButton,
|
||||||
|
shipPrimaryDisabled && styles.shipPrimaryButtonDisabled,
|
||||||
|
]}
|
||||||
|
onPress={async () => {
|
||||||
|
if (!resolvedShipPrimary || shipPrimaryDisabled) return;
|
||||||
|
await persistShipDefault(resolvedShipPrimary);
|
||||||
|
if (resolvedShipPrimary === "merge") {
|
||||||
|
if (mergeDisabled) return;
|
||||||
|
mergeMutation.mutate();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (hasPullRequest && prStatus?.url) {
|
||||||
|
void Linking.openURL(prStatus.url);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (prDisabled) return;
|
||||||
|
prMutation.mutate();
|
||||||
|
}}
|
||||||
|
disabled={shipPrimaryDisabled}
|
||||||
|
accessibilityRole="button"
|
||||||
|
accessibilityLabel="Ship changes"
|
||||||
|
>
|
||||||
|
{shipPrimaryPending ? (
|
||||||
|
<ActivityIndicator size="small" color={theme.colors.foreground} />
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Text style={styles.shipPrimaryText}>
|
||||||
|
{resolvedShipPrimary === "merge" ? "Merge" : prActionLabel}
|
||||||
|
</Text>
|
||||||
|
<Text style={styles.shipSecondaryText} numberOfLines={1}>
|
||||||
|
into {baseRefLabel}
|
||||||
|
</Text>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Pressable>
|
||||||
|
<Pressable
|
||||||
|
testID="changes-ship-caret"
|
||||||
|
onPress={handleOpenShipMenu}
|
||||||
|
style={styles.shipCaretButton}
|
||||||
|
accessibilityRole="button"
|
||||||
|
accessibilityLabel="More ship options"
|
||||||
|
>
|
||||||
|
<ChevronDown size={16} color={theme.colors.foregroundMuted} />
|
||||||
|
</Pressable>
|
||||||
|
</View>
|
||||||
|
) : null}
|
||||||
<Pressable
|
<Pressable
|
||||||
testID="changes-overflow-menu"
|
testID="changes-overflow-menu"
|
||||||
onPress={handleOpenOverflowMenu}
|
onPress={handleOpenOverflowMenu}
|
||||||
@@ -871,12 +1019,21 @@ export function GitDiffPane({ serverId, agentId }: GitDiffPaneProps) {
|
|||||||
{actionStatus ? <Text style={styles.actionStatusText}>{actionStatus}</Text> : null}
|
{actionStatus ? <Text style={styles.actionStatusText}>{actionStatus}</Text> : null}
|
||||||
{actionError ? <Text style={styles.actionErrorText}>{actionError}</Text> : null}
|
{actionError ? <Text style={styles.actionErrorText}>{actionError}</Text> : null}
|
||||||
{prStatus ? (
|
{prStatus ? (
|
||||||
<View style={styles.prStatusRow} testID="changes-pr-status">
|
<Pressable
|
||||||
|
style={styles.prStatusRow}
|
||||||
|
testID="changes-pr-status"
|
||||||
|
accessibilityRole="button"
|
||||||
|
accessibilityLabel="Open pull request"
|
||||||
|
onPress={() => {
|
||||||
|
if (!prStatus.url) return;
|
||||||
|
void Linking.openURL(prStatus.url);
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Text style={styles.prStatusLabel}>PR</Text>
|
<Text style={styles.prStatusLabel}>PR</Text>
|
||||||
<Text style={styles.prStatusValue}>
|
<Text style={styles.prStatusValue}>
|
||||||
{prStatus.state} {prStatus.url ? `· ${prStatus.url}` : ""}
|
{prStatus.state} {prStatus.url ? `· ${prStatus.url}` : ""}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</Pressable>
|
||||||
) : null}
|
) : null}
|
||||||
{prErrorMessage ? (
|
{prErrorMessage ? (
|
||||||
<Text style={styles.actionErrorText}>{prErrorMessage}</Text>
|
<Text style={styles.actionErrorText}>{prErrorMessage}</Text>
|
||||||
@@ -925,56 +1082,6 @@ export function GitDiffPane({ serverId, agentId }: GitDiffPaneProps) {
|
|||||||
|
|
||||||
<View style={styles.menuDivider} />
|
<View style={styles.menuDivider} />
|
||||||
|
|
||||||
{showCommitAction && primaryAction?.key !== "commit" ? (
|
|
||||||
<Pressable
|
|
||||||
testID="changes-menu-commit"
|
|
||||||
style={[styles.menuItem, commitDisabled && styles.menuItemDisabled]}
|
|
||||||
onPress={() => {
|
|
||||||
if (commitDisabled) return;
|
|
||||||
handleCloseOverflowMenu();
|
|
||||||
commitMutation.mutate();
|
|
||||||
}}
|
|
||||||
disabled={commitDisabled}
|
|
||||||
accessibilityRole="button"
|
|
||||||
>
|
|
||||||
<Text style={styles.menuItemText}>Commit</Text>
|
|
||||||
</Pressable>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
{showCreatePrAction && primaryAction?.key !== "create_pr" ? (
|
|
||||||
<Pressable
|
|
||||||
testID="changes-menu-create-pr"
|
|
||||||
style={[styles.menuItem, prDisabled && styles.menuItemDisabled]}
|
|
||||||
onPress={() => {
|
|
||||||
if (prDisabled) return;
|
|
||||||
handleCloseOverflowMenu();
|
|
||||||
prMutation.mutate();
|
|
||||||
}}
|
|
||||||
disabled={prDisabled}
|
|
||||||
accessibilityRole="button"
|
|
||||||
>
|
|
||||||
<Text style={styles.menuItemText}>Create PR</Text>
|
|
||||||
</Pressable>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
{showMergeAction && primaryAction?.key !== "merge" ? (
|
|
||||||
<Pressable
|
|
||||||
testID="changes-menu-merge"
|
|
||||||
style={[styles.menuItem, mergeDisabled && styles.menuItemDisabled]}
|
|
||||||
onPress={() => {
|
|
||||||
if (mergeDisabled) return;
|
|
||||||
handleCloseOverflowMenu();
|
|
||||||
mergeMutation.mutate();
|
|
||||||
}}
|
|
||||||
disabled={mergeDisabled}
|
|
||||||
accessibilityRole="button"
|
|
||||||
>
|
|
||||||
<Text style={styles.menuItemText}>Merge to base</Text>
|
|
||||||
</Pressable>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
<View style={styles.menuDivider} />
|
|
||||||
|
|
||||||
<Pressable
|
<Pressable
|
||||||
testID="changes-menu-archive"
|
testID="changes-menu-archive"
|
||||||
style={[
|
style={[
|
||||||
@@ -998,6 +1105,130 @@ export function GitDiffPane({ serverId, agentId }: GitDiffPaneProps) {
|
|||||||
) : null}
|
) : null}
|
||||||
</View>
|
</View>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
|
<Modal
|
||||||
|
visible={viewMenuVisible}
|
||||||
|
animationType="fade"
|
||||||
|
transparent={true}
|
||||||
|
onRequestClose={handleCloseViewMenu}
|
||||||
|
>
|
||||||
|
<View style={styles.menuOverlay}>
|
||||||
|
<Pressable
|
||||||
|
accessibilityRole="button"
|
||||||
|
accessibilityLabel="View menu backdrop"
|
||||||
|
style={styles.menuBackdrop}
|
||||||
|
onPress={handleCloseViewMenu}
|
||||||
|
/>
|
||||||
|
{viewMenuPosition ? (
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
styles.dropdownMenu,
|
||||||
|
{
|
||||||
|
position: "absolute",
|
||||||
|
top: viewMenuPosition.top,
|
||||||
|
left: viewMenuPosition.left,
|
||||||
|
width: viewMenuPosition.width,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
onLayout={handleViewMenuLayout}
|
||||||
|
>
|
||||||
|
<Pressable
|
||||||
|
testID="changes-mode-uncommitted"
|
||||||
|
style={[styles.menuItem, diffMode === "uncommitted" && styles.menuItemSelected]}
|
||||||
|
onPress={() => {
|
||||||
|
handleCloseViewMenu();
|
||||||
|
setDiffMode("uncommitted");
|
||||||
|
}}
|
||||||
|
accessibilityRole="button"
|
||||||
|
>
|
||||||
|
<Text style={styles.menuItemText}>Working</Text>
|
||||||
|
</Pressable>
|
||||||
|
<Pressable
|
||||||
|
testID="changes-mode-base"
|
||||||
|
style={[styles.menuItem, diffMode === "base" && styles.menuItemSelected]}
|
||||||
|
onPress={() => {
|
||||||
|
handleCloseViewMenu();
|
||||||
|
setDiffMode("base");
|
||||||
|
}}
|
||||||
|
accessibilityRole="button"
|
||||||
|
>
|
||||||
|
<Text style={styles.menuItemText}>Base</Text>
|
||||||
|
</Pressable>
|
||||||
|
</View>
|
||||||
|
) : null}
|
||||||
|
</View>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
<Modal
|
||||||
|
visible={shipMenuVisible}
|
||||||
|
animationType="fade"
|
||||||
|
transparent={true}
|
||||||
|
onRequestClose={handleCloseShipMenu}
|
||||||
|
>
|
||||||
|
<View style={styles.menuOverlay}>
|
||||||
|
<Pressable
|
||||||
|
accessibilityRole="button"
|
||||||
|
accessibilityLabel="Ship menu backdrop"
|
||||||
|
style={styles.menuBackdrop}
|
||||||
|
onPress={handleCloseShipMenu}
|
||||||
|
/>
|
||||||
|
{shipMenuPosition ? (
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
styles.dropdownMenu,
|
||||||
|
{
|
||||||
|
position: "absolute",
|
||||||
|
top: shipMenuPosition.top,
|
||||||
|
left: shipMenuPosition.left,
|
||||||
|
width: shipMenuPosition.width,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
onLayout={handleShipMenuLayout}
|
||||||
|
>
|
||||||
|
<Pressable
|
||||||
|
testID="changes-ship-merge"
|
||||||
|
style={[styles.menuItem, mergeDisabled && styles.menuItemDisabled]}
|
||||||
|
onPress={() => {
|
||||||
|
if (mergeDisabled) return;
|
||||||
|
handleCloseShipMenu();
|
||||||
|
void persistShipDefault("merge");
|
||||||
|
mergeMutation.mutate();
|
||||||
|
}}
|
||||||
|
disabled={mergeDisabled}
|
||||||
|
accessibilityRole="button"
|
||||||
|
>
|
||||||
|
<Text style={styles.menuItemText}>Merge into {baseRefLabel}</Text>
|
||||||
|
</Pressable>
|
||||||
|
{mergeDisabled && hasUncommittedChanges ? (
|
||||||
|
<Text style={styles.menuHintText}>Requires a clean working tree.</Text>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
<View style={styles.menuDivider} />
|
||||||
|
|
||||||
|
<Pressable
|
||||||
|
testID={hasPullRequest ? "changes-ship-open-pr" : "changes-ship-create-pr"}
|
||||||
|
style={[styles.menuItem, prDisabled && styles.menuItemDisabled]}
|
||||||
|
onPress={() => {
|
||||||
|
if (prDisabled) return;
|
||||||
|
handleCloseShipMenu();
|
||||||
|
void persistShipDefault("pr");
|
||||||
|
if (hasPullRequest && prStatus?.url) {
|
||||||
|
void Linking.openURL(prStatus.url);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
prMutation.mutate();
|
||||||
|
}}
|
||||||
|
disabled={prDisabled}
|
||||||
|
accessibilityRole="button"
|
||||||
|
>
|
||||||
|
<Text style={styles.menuItemText}>
|
||||||
|
{hasPullRequest ? "Open PR" : `Create PR into ${baseRefLabel}`}
|
||||||
|
</Text>
|
||||||
|
</Pressable>
|
||||||
|
</View>
|
||||||
|
) : null}
|
||||||
|
</View>
|
||||||
|
</Modal>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1030,29 +1261,21 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
fontWeight: theme.fontWeight.medium,
|
fontWeight: theme.fontWeight.medium,
|
||||||
flexShrink: 1,
|
flexShrink: 1,
|
||||||
},
|
},
|
||||||
modeToggle: {
|
viewSelector: {
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
backgroundColor: theme.colors.surface2,
|
|
||||||
borderRadius: theme.borderRadius.lg,
|
|
||||||
padding: theme.spacing[1],
|
|
||||||
gap: theme.spacing[1],
|
gap: theme.spacing[1],
|
||||||
},
|
|
||||||
modeToggleButton: {
|
|
||||||
paddingHorizontal: theme.spacing[2],
|
paddingHorizontal: theme.spacing[2],
|
||||||
paddingVertical: theme.spacing[1],
|
paddingVertical: theme.spacing[1],
|
||||||
borderRadius: theme.borderRadius.md,
|
borderRadius: theme.borderRadius.lg,
|
||||||
|
backgroundColor: theme.colors.surface2,
|
||||||
|
borderWidth: theme.borderWidth[1],
|
||||||
|
borderColor: theme.colors.border,
|
||||||
},
|
},
|
||||||
modeToggleButtonActive: {
|
viewSelectorText: {
|
||||||
backgroundColor: theme.colors.surface0,
|
|
||||||
},
|
|
||||||
modeToggleText: {
|
|
||||||
fontSize: theme.fontSize.xs,
|
fontSize: theme.fontSize.xs,
|
||||||
color: theme.colors.foregroundMuted,
|
|
||||||
fontWeight: theme.fontWeight.medium,
|
|
||||||
},
|
|
||||||
modeToggleTextActive: {
|
|
||||||
color: theme.colors.foreground,
|
color: theme.colors.foreground,
|
||||||
|
fontWeight: theme.fontWeight.medium,
|
||||||
},
|
},
|
||||||
toolbarRow: {
|
toolbarRow: {
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
@@ -1062,12 +1285,12 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
paddingTop: theme.spacing[2],
|
paddingTop: theme.spacing[2],
|
||||||
paddingBottom: theme.spacing[1],
|
paddingBottom: theme.spacing[1],
|
||||||
},
|
},
|
||||||
toolbarPrimary: {
|
toolbarLeft: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
minWidth: 0,
|
minWidth: 0,
|
||||||
},
|
},
|
||||||
toolbarPrimarySpacer: {
|
toolbarLeftSpacer: {
|
||||||
height: 32,
|
height: 36,
|
||||||
},
|
},
|
||||||
toolbarRight: {
|
toolbarRight: {
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
@@ -1075,7 +1298,7 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
gap: theme.spacing[1],
|
gap: theme.spacing[1],
|
||||||
flexShrink: 0,
|
flexShrink: 0,
|
||||||
},
|
},
|
||||||
primaryActionButton: {
|
secondaryActionButton: {
|
||||||
paddingHorizontal: theme.spacing[3],
|
paddingHorizontal: theme.spacing[3],
|
||||||
paddingVertical: theme.spacing[2],
|
paddingVertical: theme.spacing[2],
|
||||||
borderRadius: theme.borderRadius.md,
|
borderRadius: theme.borderRadius.md,
|
||||||
@@ -1084,14 +1307,48 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
borderColor: theme.colors.borderAccent,
|
borderColor: theme.colors.borderAccent,
|
||||||
alignSelf: "flex-start",
|
alignSelf: "flex-start",
|
||||||
},
|
},
|
||||||
primaryActionButtonDisabled: {
|
secondaryActionButtonDisabled: {
|
||||||
opacity: 0.5,
|
opacity: 0.5,
|
||||||
},
|
},
|
||||||
primaryActionText: {
|
secondaryActionText: {
|
||||||
fontSize: theme.fontSize.xs,
|
fontSize: theme.fontSize.xs,
|
||||||
color: theme.colors.foreground,
|
color: theme.colors.foreground,
|
||||||
fontWeight: theme.fontWeight.medium,
|
fontWeight: theme.fontWeight.medium,
|
||||||
},
|
},
|
||||||
|
shipSplitButton: {
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "stretch",
|
||||||
|
borderRadius: theme.borderRadius.md,
|
||||||
|
backgroundColor: theme.colors.surface2,
|
||||||
|
borderWidth: theme.borderWidth[1],
|
||||||
|
borderColor: theme.colors.borderAccent,
|
||||||
|
overflow: "hidden",
|
||||||
|
},
|
||||||
|
shipPrimaryButton: {
|
||||||
|
paddingHorizontal: theme.spacing[3],
|
||||||
|
paddingVertical: theme.spacing[2],
|
||||||
|
minWidth: 140,
|
||||||
|
},
|
||||||
|
shipPrimaryButtonDisabled: {
|
||||||
|
opacity: 0.6,
|
||||||
|
},
|
||||||
|
shipPrimaryText: {
|
||||||
|
fontSize: theme.fontSize.xs,
|
||||||
|
color: theme.colors.foreground,
|
||||||
|
fontWeight: theme.fontWeight.medium,
|
||||||
|
},
|
||||||
|
shipSecondaryText: {
|
||||||
|
marginTop: 2,
|
||||||
|
fontSize: theme.fontSize.xs,
|
||||||
|
color: theme.colors.foregroundMuted,
|
||||||
|
},
|
||||||
|
shipCaretButton: {
|
||||||
|
width: 36,
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
borderLeftWidth: theme.borderWidth[1],
|
||||||
|
borderLeftColor: theme.colors.borderAccent,
|
||||||
|
},
|
||||||
iconButton: {
|
iconButton: {
|
||||||
width: 32,
|
width: 32,
|
||||||
height: 32,
|
height: 32,
|
||||||
@@ -1128,6 +1385,9 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
paddingHorizontal: theme.spacing[3],
|
paddingHorizontal: theme.spacing[3],
|
||||||
paddingVertical: theme.spacing[2],
|
paddingVertical: theme.spacing[2],
|
||||||
},
|
},
|
||||||
|
menuItemSelected: {
|
||||||
|
backgroundColor: theme.colors.surface2,
|
||||||
|
},
|
||||||
menuItemDisabled: {
|
menuItemDisabled: {
|
||||||
opacity: 0.5,
|
opacity: 0.5,
|
||||||
},
|
},
|
||||||
@@ -1136,6 +1396,12 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
color: theme.colors.foreground,
|
color: theme.colors.foreground,
|
||||||
fontWeight: theme.fontWeight.medium,
|
fontWeight: theme.fontWeight.medium,
|
||||||
},
|
},
|
||||||
|
menuHintText: {
|
||||||
|
paddingHorizontal: theme.spacing[3],
|
||||||
|
paddingBottom: theme.spacing[2],
|
||||||
|
fontSize: theme.fontSize.xs,
|
||||||
|
color: theme.colors.foregroundMuted,
|
||||||
|
},
|
||||||
menuItemDestructive: {
|
menuItemDestructive: {
|
||||||
backgroundColor: "rgba(248, 81, 73, 0.08)",
|
backgroundColor: "rgba(248, 81, 73, 0.08)",
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user