import { useCallback, useMemo, type ReactElement } from "react";
import {
View,
Text,
ActivityIndicator,
Pressable,
type PressableStateCallbackType,
} from "react-native";
import { StyleSheet, useUnistyles } from "react-native-unistyles";
import { ChevronDown, Info, MoreVertical } from "lucide-react-native";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Shortcut } from "@/components/ui/shortcut";
import { useShortcutKeys } from "@/hooks/use-shortcut-keys";
import { useToast } from "@/contexts/toast-context";
import type { GitAction, GitActions } from "@/components/git-actions-policy";
interface GitActionsSplitButtonProps {
gitActions: GitActions;
hideLabels?: boolean;
}
interface GitActionMenuItemProps {
action: GitAction;
onSelect: (action: GitAction) => void;
trailing?: ReactElement | null;
needsSeparator?: boolean;
showSeparator?: boolean;
closeOnSelect?: boolean;
}
function GitActionMenuItem({
action,
onSelect,
trailing,
needsSeparator,
showSeparator,
closeOnSelect,
}: GitActionMenuItemProps) {
const handleSelect = useCallback(() => onSelect(action), [onSelect, action]);
return (
{needsSeparator && showSeparator ? : null}
{action.label}
);
}
export function GitActionsSplitButton({ gitActions, hideLabels }: GitActionsSplitButtonProps) {
const { theme } = useUnistyles();
const toast = useToast();
const archiveShortcutKeys = useShortcutKeys("archive-worktree");
const getActionDisplayLabel = useCallback((action: GitAction): string => {
if (action.status === "pending") return action.pendingLabel;
if (action.status === "success") return action.successLabel;
return action.label;
}, []);
const handleActionSelect = useCallback(
(action: GitAction) => {
if (action.unavailableMessage) {
toast.show(action.unavailableMessage, {
durationMs: 3200,
icon: ,
});
return;
}
action.handler();
},
[theme.colors.foreground, toast],
);
const overflowMenuButtonStyle = useMemo(() => [styles.iconButton, styles.overflowMenuButton], []);
const primaryDisabled = gitActions.primary?.disabled;
const primaryPressableStyle = useCallback(
({ hovered, pressed }: PressableStateCallbackType & { hovered?: boolean }) => [
styles.splitButtonPrimary,
(Boolean(hovered) || pressed) && styles.splitButtonPrimaryHovered,
primaryDisabled && styles.splitButtonPrimaryDisabled,
],
[primaryDisabled],
);
const caretTriggerStyle = useCallback(
({ hovered, pressed, open }: { hovered: boolean; pressed: boolean; open: boolean }) => [
styles.splitButtonCaret,
(hovered || pressed || open) && styles.splitButtonCaretHovered,
],
[],
);
return (
{gitActions.primary ? (
{gitActions.primary.status === "pending" ? (
) : (
{gitActions.primary.icon}
{!hideLabels && (
{getActionDisplayLabel(gitActions.primary)}
)}
)}
{gitActions.secondary.length > 0 ? (
{gitActions.secondary.map((action, index) => (
) : undefined
}
needsSeparator={
action.id === "merge-from-base" || action.id === "archive-worktree"
}
showSeparator={index > 0}
closeOnSelect={
action.status === "idle" && action.id === "pr" && action.label === "View PR"
}
/>
))}
) : null}
) : null}
{gitActions.menu.length > 0 ? (
{gitActions.menu.map((action) => (
))}
) : null}
);
}
const styles = StyleSheet.create((theme) => ({
row: {
flexDirection: "row",
alignItems: "center",
gap: theme.spacing[1],
flexShrink: 0,
},
splitButton: {
flexDirection: "row",
alignItems: "stretch",
borderRadius: theme.borderRadius.md,
borderWidth: theme.borderWidth[1],
borderColor: theme.colors.borderAccent,
overflow: "hidden",
},
splitButtonPrimary: {
paddingHorizontal: theme.spacing[3],
paddingVertical: theme.spacing[1],
justifyContent: "center",
position: "relative",
},
splitButtonPrimaryHovered: {
backgroundColor: theme.colors.surface2,
},
splitButtonPrimaryDisabled: {
opacity: 0.6,
},
splitButtonText: {
fontSize: theme.fontSize.sm,
lineHeight: theme.fontSize.sm * 1.5,
color: theme.colors.foreground,
fontWeight: theme.fontWeight.normal,
},
splitButtonContent: {
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
gap: theme.spacing[2],
},
splitButtonSpinnerOnly: {
transform: [{ scale: 0.8 }],
},
splitButtonCaret: {
width: 28,
alignItems: "center",
justifyContent: "center",
borderLeftWidth: theme.borderWidth[1],
borderLeftColor: theme.colors.borderAccent,
},
splitButtonCaretHovered: {
backgroundColor: theme.colors.surface2,
},
iconButton: {
width: 32,
height: 32,
alignItems: "center",
justifyContent: "center",
borderRadius: theme.borderRadius.md,
},
overflowMenuButton: {
marginRight: -theme.spacing[2],
},
}));