refactor: restructure git actions into data-driven design pattern
This commit is contained in:
@@ -1261,7 +1261,7 @@ const permissionStyles = StyleSheet.create((theme) => ({
|
|||||||
},
|
},
|
||||||
optionsContainerDesktop: {
|
optionsContainerDesktop: {
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
justifyContent: "flex-end",
|
justifyContent: "flex-start",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
width: "100%",
|
width: "100%",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -54,6 +54,37 @@ type ActionState = {
|
|||||||
trigger: () => void;
|
trigger: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Git Actions Data Structure
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
type GitActionId =
|
||||||
|
| "commit"
|
||||||
|
| "push"
|
||||||
|
| "view-pr"
|
||||||
|
| "create-pr"
|
||||||
|
| "merge-branch"
|
||||||
|
| "merge-from-base"
|
||||||
|
| "archive-worktree";
|
||||||
|
|
||||||
|
interface GitAction {
|
||||||
|
id: GitActionId;
|
||||||
|
label: string;
|
||||||
|
pendingLabel: string;
|
||||||
|
successLabel: string;
|
||||||
|
disabled: boolean;
|
||||||
|
status: ActionStatus;
|
||||||
|
description?: string;
|
||||||
|
destructive?: boolean;
|
||||||
|
handler: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GitActions {
|
||||||
|
primary: GitAction | null;
|
||||||
|
secondary: GitAction[];
|
||||||
|
menu: GitAction[];
|
||||||
|
}
|
||||||
|
|
||||||
function useActionStatus<TData, TError, TVariables, TContext>(
|
function useActionStatus<TData, TError, TVariables, TContext>(
|
||||||
mutation: UseMutationResult<TData, TError, TVariables, TContext>,
|
mutation: UseMutationResult<TData, TError, TVariables, TContext>,
|
||||||
onTrigger?: () => void
|
onTrigger?: () => void
|
||||||
@@ -843,151 +874,177 @@ export function GitDiffPane({ serverId, agentId, cwd }: GitDiffPaneProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const hasPullRequest = Boolean(prStatus?.url);
|
const hasPullRequest = Boolean(prStatus?.url);
|
||||||
|
const hasRemote = gitStatus?.hasRemote ?? false;
|
||||||
|
const isPaseoOwnedWorktree = gitStatus?.isPaseoOwnedWorktree ?? false;
|
||||||
|
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
// Primary CTA Logic
|
// Git Actions (Data-Oriented)
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
// Rules (in priority order):
|
// All possible actions are computed as data, then partitioned into:
|
||||||
// 1. Uncommitted changes → "Commit" is primary
|
// - primary: The main CTA button
|
||||||
// 2. Ahead of origin (unpushed commits) → "Push" is primary
|
// - secondary: Dropdown next to primary button
|
||||||
// 3. Has PR → "View PR" is primary
|
// - menu: Kebab overflow menu
|
||||||
// 4. Ahead of base → "Merge branch" or "Create PR" based on shipDefault preference
|
|
||||||
// 5. Nothing to do → no primary CTA
|
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
type PrimaryCTA =
|
const gitActions: GitActions = useMemo(() => {
|
||||||
| { type: "commit" }
|
if (!isGit) {
|
||||||
| { type: "push" }
|
return { primary: null, secondary: [], menu: [] };
|
||||||
| { type: "view-pr"; url: string }
|
}
|
||||||
| { type: "ship"; action: "merge" | "create-pr" }
|
|
||||||
| null;
|
|
||||||
|
|
||||||
const primaryCTA: PrimaryCTA = useMemo(() => {
|
// Build all possible actions
|
||||||
if (!isGit) return null;
|
const allActions = new Map<GitActionId, GitAction>();
|
||||||
|
|
||||||
|
// Commit - always available
|
||||||
|
allActions.set("commit", {
|
||||||
|
id: "commit",
|
||||||
|
label: "Commit",
|
||||||
|
pendingLabel: "Committing...",
|
||||||
|
successLabel: "Committed",
|
||||||
|
disabled: commitDisabled,
|
||||||
|
status: commitAction.status,
|
||||||
|
handler: commitAction.trigger,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Push - when has remote
|
||||||
|
if (hasRemote) {
|
||||||
|
allActions.set("push", {
|
||||||
|
id: "push",
|
||||||
|
label: "Push",
|
||||||
|
pendingLabel: "Pushing...",
|
||||||
|
successLabel: "Pushed",
|
||||||
|
disabled: pushDisabled,
|
||||||
|
status: pushAction.status,
|
||||||
|
description: !hasRemote ? "No remote configured" : undefined,
|
||||||
|
handler: pushAction.trigger,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// View PR - when PR exists
|
||||||
|
if (hasPullRequest && prStatus?.url) {
|
||||||
|
const prUrl = prStatus.url;
|
||||||
|
allActions.set("view-pr", {
|
||||||
|
id: "view-pr",
|
||||||
|
label: "View PR",
|
||||||
|
pendingLabel: "View PR",
|
||||||
|
successLabel: "View PR",
|
||||||
|
disabled: false,
|
||||||
|
status: "idle",
|
||||||
|
handler: () => openURLInNewTab(prUrl),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create PR - when ahead of base and no PR
|
||||||
|
if (aheadCount > 0 && !hasPullRequest) {
|
||||||
|
allActions.set("create-pr", {
|
||||||
|
id: "create-pr",
|
||||||
|
label: "Create PR",
|
||||||
|
pendingLabel: "Creating PR...",
|
||||||
|
successLabel: "PR Created",
|
||||||
|
disabled: prDisabled,
|
||||||
|
status: prCreateAction.status,
|
||||||
|
handler: prCreateAction.trigger,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge branch - when ahead of base
|
||||||
|
if (aheadCount > 0) {
|
||||||
|
allActions.set("merge-branch", {
|
||||||
|
id: "merge-branch",
|
||||||
|
label: "Merge branch",
|
||||||
|
pendingLabel: "Merging...",
|
||||||
|
successLabel: "Merged",
|
||||||
|
disabled: mergeDisabled,
|
||||||
|
status: mergeAction.status,
|
||||||
|
description: hasUncommittedChanges ? "Requires clean working tree" : undefined,
|
||||||
|
handler: mergeAction.trigger,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge from base - always available
|
||||||
|
allActions.set("merge-from-base", {
|
||||||
|
id: "merge-from-base",
|
||||||
|
label: `Merge from ${baseRefLabel}`,
|
||||||
|
pendingLabel: "Merging...",
|
||||||
|
successLabel: "Merged",
|
||||||
|
disabled: mergeFromBaseDisabled,
|
||||||
|
status: mergeFromBaseAction.status,
|
||||||
|
description: hasUncommittedChanges ? "Requires clean working tree" : undefined,
|
||||||
|
handler: mergeFromBaseAction.trigger,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Archive worktree - only for Paseo worktrees
|
||||||
|
if (isPaseoOwnedWorktree) {
|
||||||
|
allActions.set("archive-worktree", {
|
||||||
|
id: "archive-worktree",
|
||||||
|
label: "Archive worktree",
|
||||||
|
pendingLabel: "Archiving...",
|
||||||
|
successLabel: "Archived",
|
||||||
|
disabled: archiveDisabled,
|
||||||
|
status: archiveAction.status,
|
||||||
|
destructive: true,
|
||||||
|
handler: archiveAction.trigger,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select primary action (priority rules)
|
||||||
|
let primaryActionId: GitActionId | null = null;
|
||||||
|
|
||||||
// Rule 1: Uncommitted changes → Commit
|
// Rule 1: Uncommitted changes → Commit
|
||||||
if (hasUncommittedChanges) {
|
if (hasUncommittedChanges) {
|
||||||
return { type: "commit" };
|
primaryActionId = "commit";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rule 2: Ahead of origin → Push
|
// Rule 2: Ahead of origin → Push
|
||||||
if (aheadOfOrigin > 0 && !pushDisabled) {
|
else if (aheadOfOrigin > 0 && allActions.has("push") && !pushDisabled) {
|
||||||
return { type: "push" };
|
primaryActionId = "push";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rule 3: Has PR → View PR
|
// Rule 3: Has PR → View PR
|
||||||
if (hasPullRequest && prStatus?.url) {
|
else if (hasPullRequest) {
|
||||||
return { type: "view-pr", url: prStatus.url };
|
primaryActionId = "view-pr";
|
||||||
}
|
}
|
||||||
|
// Rule 4: Ahead of base → Ship action based on preference
|
||||||
|
else if (aheadCount > 0) {
|
||||||
|
const preferred: GitActionId = shipDefault === "merge" ? "merge-branch" : "create-pr";
|
||||||
|
const fallback: GitActionId = shipDefault === "merge" ? "create-pr" : "merge-branch";
|
||||||
|
|
||||||
// Rule 4: Ahead of base → Ship (merge or create PR based on preference)
|
const preferredAction = allActions.get(preferred);
|
||||||
if (aheadCount > 0) {
|
const fallbackAction = allActions.get(fallback);
|
||||||
const preferredAction = shipDefault === "merge" ? "merge" : "create-pr";
|
|
||||||
// If preferred action is disabled, fall back to the other
|
if (preferredAction && !preferredAction.disabled) {
|
||||||
if (preferredAction === "merge" && mergeDisabled && !prDisabled) {
|
primaryActionId = preferred;
|
||||||
return { type: "ship", action: "create-pr" };
|
} else if (fallbackAction && !fallbackAction.disabled) {
|
||||||
|
primaryActionId = fallback;
|
||||||
|
} else if (preferredAction) {
|
||||||
|
primaryActionId = preferred;
|
||||||
}
|
}
|
||||||
if (preferredAction === "create-pr" && prDisabled && !mergeDisabled) {
|
|
||||||
return { type: "ship", action: "merge" };
|
|
||||||
}
|
|
||||||
return { type: "ship", action: preferredAction };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rule 5: Nothing to do
|
const primary = primaryActionId ? allActions.get(primaryActionId) ?? null : null;
|
||||||
return null;
|
|
||||||
}, [isGit, hasUncommittedChanges, aheadOfOrigin, pushDisabled, hasPullRequest, prStatus?.url, aheadCount, shipDefault, mergeDisabled, prDisabled]);
|
|
||||||
|
|
||||||
const primaryCTALabel = useMemo(() => {
|
// Secondary actions: ship-related + merge from base + push (excluding primary)
|
||||||
if (!primaryCTA) return "";
|
const secondaryIds: GitActionId[] = ["merge-branch", "create-pr", "view-pr", "merge-from-base", "push"];
|
||||||
switch (primaryCTA.type) {
|
const secondary = secondaryIds
|
||||||
case "commit":
|
.filter(id => id !== primaryActionId && allActions.has(id))
|
||||||
return "Commit";
|
.map(id => allActions.get(id)!);
|
||||||
case "push":
|
|
||||||
return "Push";
|
|
||||||
case "view-pr":
|
|
||||||
return "View PR";
|
|
||||||
case "ship":
|
|
||||||
return primaryCTA.action === "merge" ? "Merge branch" : "Create PR";
|
|
||||||
}
|
|
||||||
}, [primaryCTA]);
|
|
||||||
|
|
||||||
const primaryCTADisabled = useMemo(() => {
|
// Menu actions: archive worktree only
|
||||||
if (!primaryCTA || actionsDisabled) return true;
|
const menu = allActions.has("archive-worktree")
|
||||||
switch (primaryCTA.type) {
|
? [allActions.get("archive-worktree")!]
|
||||||
case "commit":
|
: [];
|
||||||
return commitDisabled;
|
|
||||||
case "push":
|
|
||||||
return pushDisabled;
|
|
||||||
case "view-pr":
|
|
||||||
return false; // View PR is never disabled
|
|
||||||
case "ship":
|
|
||||||
return primaryCTA.action === "merge" ? mergeDisabled : prDisabled;
|
|
||||||
}
|
|
||||||
}, [primaryCTA, actionsDisabled, commitDisabled, pushDisabled, mergeDisabled, prDisabled]);
|
|
||||||
|
|
||||||
const primaryCTAStatus: ActionStatus = useMemo(() => {
|
return { primary, secondary, menu };
|
||||||
if (!primaryCTA) return "idle";
|
}, [
|
||||||
switch (primaryCTA.type) {
|
isGit, hasRemote, hasPullRequest, prStatus?.url, aheadCount, isPaseoOwnedWorktree,
|
||||||
case "commit":
|
hasUncommittedChanges, aheadOfOrigin, shipDefault, baseRefLabel,
|
||||||
return commitAction.status;
|
commitDisabled, pushDisabled, prDisabled, mergeDisabled, mergeFromBaseDisabled, archiveDisabled,
|
||||||
case "push":
|
commitAction, pushAction, prCreateAction, mergeAction, mergeFromBaseAction, archiveAction,
|
||||||
return pushAction.status;
|
]);
|
||||||
case "view-pr":
|
|
||||||
return "idle"; // View PR is instant, no status
|
|
||||||
case "ship":
|
|
||||||
return primaryCTA.action === "merge" ? mergeAction.status : prCreateAction.status;
|
|
||||||
}
|
|
||||||
}, [primaryCTA, commitAction.status, pushAction.status, mergeAction.status, prCreateAction.status]);
|
|
||||||
|
|
||||||
const primaryCTADisplayLabel = useMemo(() => {
|
// Helper to get display label based on status
|
||||||
if (!primaryCTA) return "";
|
const getActionDisplayLabel = useCallback((action: GitAction): string => {
|
||||||
const status = primaryCTAStatus;
|
if (action.status === "pending") return action.pendingLabel;
|
||||||
|
if (action.status === "success") return action.successLabel;
|
||||||
switch (primaryCTA.type) {
|
return action.label;
|
||||||
case "commit":
|
}, []);
|
||||||
if (status === "pending") return "Committing...";
|
|
||||||
if (status === "success") return "Committed";
|
|
||||||
return "Commit";
|
|
||||||
case "push":
|
|
||||||
if (status === "pending") return "Pushing...";
|
|
||||||
if (status === "success") return "Pushed";
|
|
||||||
return "Push";
|
|
||||||
case "view-pr":
|
|
||||||
return "View PR";
|
|
||||||
case "ship":
|
|
||||||
if (primaryCTA.action === "merge") {
|
|
||||||
if (status === "pending") return "Merging...";
|
|
||||||
if (status === "success") return "Merged";
|
|
||||||
return "Merge branch";
|
|
||||||
} else {
|
|
||||||
if (status === "pending") return "Creating PR...";
|
|
||||||
if (status === "success") return "PR Created";
|
|
||||||
return "Create PR";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [primaryCTA, primaryCTAStatus]);
|
|
||||||
|
|
||||||
const handlePrimaryCTAPress = useCallback(() => {
|
|
||||||
if (!primaryCTA || primaryCTADisabled || primaryCTAStatus !== "idle") return;
|
|
||||||
switch (primaryCTA.type) {
|
|
||||||
case "commit":
|
|
||||||
commitAction.trigger();
|
|
||||||
break;
|
|
||||||
case "push":
|
|
||||||
pushAction.trigger();
|
|
||||||
break;
|
|
||||||
case "view-pr":
|
|
||||||
openURLInNewTab(primaryCTA.url);
|
|
||||||
break;
|
|
||||||
case "ship":
|
|
||||||
if (primaryCTA.action === "merge") {
|
|
||||||
mergeAction.trigger();
|
|
||||||
} else {
|
|
||||||
prCreateAction.trigger();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}, [primaryCTA, primaryCTADisabled, primaryCTAStatus, commitAction, pushAction, mergeAction, prCreateAction]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
@@ -1003,126 +1060,91 @@ export function GitDiffPane({ serverId, agentId, cwd }: GitDiffPaneProps) {
|
|||||||
</View>
|
</View>
|
||||||
{isGit ? (
|
{isGit ? (
|
||||||
<View style={styles.headerRight}>
|
<View style={styles.headerRight}>
|
||||||
{primaryCTA ? (
|
{gitActions.primary ? (
|
||||||
<View style={styles.splitButton}>
|
<View style={styles.splitButton}>
|
||||||
<Pressable
|
<Pressable
|
||||||
testID="changes-primary-cta"
|
testID="changes-primary-cta"
|
||||||
style={[
|
style={[
|
||||||
styles.splitButtonPrimary,
|
styles.splitButtonPrimary,
|
||||||
primaryCTADisabled && styles.splitButtonPrimaryDisabled,
|
gitActions.primary.disabled && styles.splitButtonPrimaryDisabled,
|
||||||
]}
|
]}
|
||||||
onPress={handlePrimaryCTAPress}
|
onPress={gitActions.primary.handler}
|
||||||
disabled={primaryCTADisabled}
|
disabled={gitActions.primary.disabled}
|
||||||
accessibilityRole="button"
|
accessibilityRole="button"
|
||||||
accessibilityLabel={primaryCTALabel}
|
accessibilityLabel={gitActions.primary.label}
|
||||||
>
|
>
|
||||||
{primaryCTAStatus === "pending" ? (
|
{gitActions.primary.status === "pending" ? (
|
||||||
<ActivityIndicator size="small" color={theme.colors.foreground} style={styles.splitButtonSpinner} />
|
<ActivityIndicator size="small" color={theme.colors.foreground} style={styles.splitButtonSpinner} />
|
||||||
) : (
|
) : (
|
||||||
<Text style={styles.splitButtonText}>{primaryCTADisplayLabel}</Text>
|
<Text style={styles.splitButtonText}>{getActionDisplayLabel(gitActions.primary)}</Text>
|
||||||
)}
|
)}
|
||||||
</Pressable>
|
</Pressable>
|
||||||
<DropdownMenu>
|
{gitActions.secondary.length > 0 ? (
|
||||||
<DropdownMenuTrigger
|
<DropdownMenu>
|
||||||
testID="changes-primary-cta-caret"
|
<DropdownMenuTrigger
|
||||||
style={styles.splitButtonCaret}
|
testID="changes-primary-cta-caret"
|
||||||
accessibilityRole="button"
|
style={styles.splitButtonCaret}
|
||||||
accessibilityLabel="More options"
|
accessibilityRole="button"
|
||||||
>
|
accessibilityLabel="More options"
|
||||||
<ChevronDown size={16} color={theme.colors.foregroundMuted} />
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent align="end" width={260} testID="changes-primary-cta-menu">
|
|
||||||
{/* Ship actions - only show if ahead of base */}
|
|
||||||
{aheadCount > 0 ? (
|
|
||||||
<>
|
|
||||||
<DropdownMenuItem
|
|
||||||
testID="changes-menu-merge"
|
|
||||||
disabled={mergeDisabled}
|
|
||||||
status={mergeAction.status}
|
|
||||||
pendingLabel="Merging..."
|
|
||||||
successLabel="Merged"
|
|
||||||
closeOnSelect={false}
|
|
||||||
description={hasUncommittedChanges ? "Requires clean working tree" : undefined}
|
|
||||||
onSelect={mergeAction.trigger}
|
|
||||||
>
|
|
||||||
Merge branch
|
|
||||||
</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem
|
|
||||||
testID={hasPullRequest ? "changes-menu-view-pr" : "changes-menu-create-pr"}
|
|
||||||
disabled={!hasPullRequest && prDisabled}
|
|
||||||
status={!hasPullRequest ? prCreateAction.status : undefined}
|
|
||||||
pendingLabel="Creating PR..."
|
|
||||||
successLabel="PR Created"
|
|
||||||
closeOnSelect={!hasPullRequest ? false : true}
|
|
||||||
onSelect={() => {
|
|
||||||
if (hasPullRequest && prStatus?.url) {
|
|
||||||
openURLInNewTab(prStatus.url);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
prCreateAction.trigger();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{hasPullRequest ? "View PR" : "Create PR"}
|
|
||||||
</DropdownMenuItem>
|
|
||||||
<DropdownMenuSeparator />
|
|
||||||
</>
|
|
||||||
) : null}
|
|
||||||
<DropdownMenuItem
|
|
||||||
testID="changes-menu-merge-from-base"
|
|
||||||
disabled={mergeFromBaseDisabled}
|
|
||||||
status={mergeFromBaseAction.status}
|
|
||||||
pendingLabel="Merging..."
|
|
||||||
successLabel="Merged"
|
|
||||||
closeOnSelect={false}
|
|
||||||
description={hasUncommittedChanges ? "Requires clean working tree" : undefined}
|
|
||||||
onSelect={mergeFromBaseAction.trigger}
|
|
||||||
>
|
>
|
||||||
Merge from {baseRefLabel}
|
<ChevronDown size={16} color={theme.colors.foregroundMuted} />
|
||||||
</DropdownMenuItem>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuContent align="end" width={260} testID="changes-primary-cta-menu">
|
||||||
<DropdownMenuItem
|
{gitActions.secondary.map((action, index) => {
|
||||||
testID="changes-menu-push"
|
const needsSeparator = action.id === "merge-from-base" || action.id === "push";
|
||||||
disabled={pushDisabled}
|
return (
|
||||||
status={pushAction.status}
|
<View key={action.id}>
|
||||||
pendingLabel="Pushing..."
|
{needsSeparator && index > 0 ? <DropdownMenuSeparator /> : null}
|
||||||
successLabel="Pushed"
|
<DropdownMenuItem
|
||||||
closeOnSelect={false}
|
testID={`changes-menu-${action.id}`}
|
||||||
description={!(gitStatus?.hasRemote ?? false) ? "No remote configured" : undefined}
|
disabled={action.disabled}
|
||||||
onSelect={pushAction.trigger}
|
status={action.status}
|
||||||
>
|
pendingLabel={action.pendingLabel}
|
||||||
Push to remote
|
successLabel={action.successLabel}
|
||||||
</DropdownMenuItem>
|
closeOnSelect={action.status === "idle" && action.id === "view-pr"}
|
||||||
</DropdownMenuContent>
|
description={action.description}
|
||||||
</DropdownMenu>
|
onSelect={action.handler}
|
||||||
|
>
|
||||||
|
{action.label}
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
) : null}
|
||||||
</View>
|
</View>
|
||||||
) : null}
|
) : null}
|
||||||
<DropdownMenu>
|
{gitActions.menu.length > 0 ? (
|
||||||
<DropdownMenuTrigger
|
<DropdownMenu>
|
||||||
testID="changes-overflow-menu"
|
<DropdownMenuTrigger
|
||||||
hitSlop={8}
|
testID="changes-overflow-menu"
|
||||||
style={styles.iconButton}
|
hitSlop={8}
|
||||||
accessibilityRole="button"
|
style={styles.iconButton}
|
||||||
accessibilityLabel="More actions"
|
accessibilityRole="button"
|
||||||
>
|
accessibilityLabel="More actions"
|
||||||
<MoreVertical size={16} color={theme.colors.foregroundMuted} />
|
>
|
||||||
</DropdownMenuTrigger>
|
<MoreVertical size={16} color={theme.colors.foregroundMuted} />
|
||||||
<DropdownMenuContent align="end" width={220} testID="changes-overflow-content">
|
</DropdownMenuTrigger>
|
||||||
{gitStatus?.isPaseoOwnedWorktree ? (
|
<DropdownMenuContent align="end" width={220} testID="changes-overflow-content">
|
||||||
<DropdownMenuItem
|
{gitActions.menu.map((action) => (
|
||||||
testID="changes-menu-archive"
|
<DropdownMenuItem
|
||||||
destructive
|
key={action.id}
|
||||||
disabled={archiveDisabled}
|
testID={`changes-menu-${action.id}`}
|
||||||
status={archiveAction.status}
|
destructive={action.destructive}
|
||||||
pendingLabel="Archiving..."
|
disabled={action.disabled}
|
||||||
successLabel="Archived"
|
status={action.status}
|
||||||
closeOnSelect={false}
|
pendingLabel={action.pendingLabel}
|
||||||
onSelect={archiveAction.trigger}
|
successLabel={action.successLabel}
|
||||||
>
|
closeOnSelect={false}
|
||||||
Archive worktree
|
onSelect={action.handler}
|
||||||
</DropdownMenuItem>
|
>
|
||||||
) : null}
|
{action.label}
|
||||||
</DropdownMenuContent>
|
</DropdownMenuItem>
|
||||||
</DropdownMenu>
|
))}
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
) : null}
|
||||||
</View>
|
</View>
|
||||||
) : null}
|
) : null}
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
Reference in New Issue
Block a user