"use client"; import { useTranslation } from "react-i18next"; import type { ComponentProps } from "react"; import { IconChevronDown, IconLoader2 } from "@tabler/icons-react"; import { Button } from "@kandev/ui/button"; import { DropdownMenu, DropdownMenuTrigger } from "@kandev/ui/dropdown-menu"; import { Tooltip, TooltipContent, TooltipTrigger } from "@kandev/ui/tooltip"; import { cn } from "@kandev/ui/lib/utils"; import { MultiRepoVcsButton, type PerRepoCallbacks, type PerRepoStatus, } from "./vcs-multi-repo-menu"; import { VcsDropdownItems } from "./vcs-split-button-dropdown"; import type { PrimaryButtonConfig } from "./vcs-split-button"; import { type RemoteContributionResolutionTarget, type useRemoteContributionResolution, } from "@/components/task/use-remote-contribution-resolution"; import { RemoteContributionResolutionDialog } from "@/components/task/remote-contribution-resolution-dialog"; import { AHEAD_MARK, BEHIND_MARK, DEFAULT_BASE_BRANCH } from "./vcs-constants"; type DivergenceTone = "ahead" | "behind"; const divergenceToneClass: Record = { ahead: "border-emerald-500/40 bg-emerald-500/10 text-emerald-500", behind: "border-yellow-500/40 bg-yellow-500/10 text-yellow-500", }; function DivergencePill({ tone, value, ariaLabel, }: { tone: DivergenceTone; value: number; ariaLabel: string; }) { if (value <= 0) return null; return ( {tone === "ahead" ? AHEAD_MARK : BEHIND_MARK} {value} ); } export function GitDivergencePills({ ahead, behind }: { ahead: number; behind: number }) { const { t } = useTranslation(); if (ahead <= 0 && behind <= 0) return null; // `{{value}}`, not `{{count}}`: the shipped English is count-invariant // ("1 commits ahead"), and switching to a plural here would change the // accessible name that E2E specs query by. return ( ); } type SingleRepoVcsButtonProps = { primaryButtonConfig: PrimaryButtonConfig; primaryAction: "commit" | "push" | "pr" | "rebase"; isDisabled: boolean; isGitLoading: boolean; baseBranch?: string; hasUpstream: boolean; behindCount: number; aheadCount: number; pushDisabled: boolean; pullDisabled: boolean; pushDisabledReason?: string; pullDisabledReason?: string; showContributionResolution: boolean; replaceDisabled: boolean; useDisabled: boolean; onPR: () => void; onPull: () => void; onPush: (force: boolean) => void; onReplaceContribution: () => void; onUseContribution: () => void; onViewPRVersion: () => void; onCompareContribution: () => void; prNumber?: number; onRebase: () => void; onMerge: () => void; className?: string; buttonSize?: ComponentProps["size"]; showDivergencePills?: boolean; }; function SingleRepoVcsButton({ primaryButtonConfig, primaryAction, isDisabled, isGitLoading, baseBranch, hasUpstream, behindCount, aheadCount, pushDisabled, pullDisabled, pushDisabledReason, pullDisabledReason, showContributionResolution, replaceDisabled, useDisabled, onPR, onPull, onPush, onReplaceContribution, onUseContribution, onViewPRVersion, onCompareContribution, prNumber, onRebase, onMerge, className, buttonSize = "sm", showDivergencePills = false, }: SingleRepoVcsButtonProps) { const { t } = useTranslation(); return (
{primaryButtonConfig.tooltip}
); } export type VcsSplitButtonCallbacks = { onCommit: (repo?: string) => void; onPR: (repo?: string) => void; onPull: (repo?: string) => void; onPush: (force: boolean, repo?: string) => void; onRebase: (repo?: string) => void; onMerge: (repo?: string) => void; onReplaceContribution: (repo?: string) => void; onUseContribution: (repo?: string) => void; onViewContribution: (repo?: string) => void; onCompareContribution: (repo?: string) => void; }; export function buildSingleRepoContributionCallbacks( callbacks: Pick< VcsSplitButtonCallbacks, "onReplaceContribution" | "onUseContribution" | "onViewContribution" | "onCompareContribution" >, blockedRepositoryName?: string, ) { const repositoryScope = blockedRepositoryName ?? ""; return { onReplaceContribution: () => callbacks.onReplaceContribution(repositoryScope), onUseContribution: () => callbacks.onUseContribution(repositoryScope), onViewContribution: () => callbacks.onViewContribution(repositoryScope), onCompareContribution: () => callbacks.onCompareContribution(repositoryScope), }; } type VcsSplitButtonContentProps = { isMultiRepo: boolean; primaryButtonConfig: PrimaryButtonConfig; primaryAction: "commit" | "push" | "pr" | "rebase"; isDisabled: boolean; isGitLoading: boolean; baseBranch?: string; hasUpstream: boolean; behindCount: number; aheadCount: number; pushDisabled: boolean; pullDisabled: boolean; pushDisabledReason?: string; pullDisabledReason?: string; showContributionResolution: boolean; replaceDisabled: boolean; useDisabled: boolean; repoNames: string[]; perRepoStatus: PerRepoStatus[]; blockedRepositoryName?: string; repoDisplayName: (repositoryName: string) => string | undefined; buttonSize?: ComponentProps["size"]; className?: string; showDivergencePills: boolean; callbacks: VcsSplitButtonCallbacks; resolution: ReturnType; resolutionTarget: RemoteContributionResolutionTarget | null; confirmResolution: () => Promise; prNumber?: number; }; type SingleRepoVcsContentProps = Omit< VcsSplitButtonContentProps, "isMultiRepo" | "repoNames" | "perRepoStatus" | "repoDisplayName" >; function ContributionResolutionDialog({ resolution, resolutionTarget, confirmResolution, }: Pick) { if (!resolution.pending || !resolutionTarget) return null; return ( { if (!open) resolution.cancel(); }} onConfirm={() => { void confirmResolution(); }} /> ); } function MultiRepoVcsContent({ primaryButtonConfig, primaryAction, isDisabled, isGitLoading, baseBranch, repoNames, perRepoStatus, pushDisabled, pullDisabled, pushDisabledReason, pullDisabledReason, showContributionResolution, replaceDisabled, useDisabled, blockedRepositoryName, repoDisplayName, callbacks, resolution, resolutionTarget, confirmResolution, prNumber, }: Pick< VcsSplitButtonContentProps, | "primaryButtonConfig" | "primaryAction" | "isDisabled" | "isGitLoading" | "baseBranch" | "repoNames" | "perRepoStatus" | "pushDisabled" | "pullDisabled" | "pushDisabledReason" | "pullDisabledReason" | "showContributionResolution" | "replaceDisabled" | "useDisabled" | "blockedRepositoryName" | "repoDisplayName" | "callbacks" | "resolution" | "resolutionTarget" | "confirmResolution" | "prNumber" >) { const multiRepoCallbacks: PerRepoCallbacks = { onCommit: (repo) => callbacks.onCommit(repo), onPR: (repo) => callbacks.onPR(repo), onPull: (repo) => callbacks.onPull(repo), onPush: (force, repo) => callbacks.onPush(force, repo), onRebase: (repo) => callbacks.onRebase(repo), onMerge: (repo) => callbacks.onMerge(repo), onReplaceContribution: (repo) => callbacks.onReplaceContribution(repo), onUseContribution: (repo) => callbacks.onUseContribution(repo), onViewContribution: (repo) => callbacks.onViewContribution(repo), onCompareContribution: (repo) => callbacks.onCompareContribution(repo), }; return ( <> ); } function SingleRepoVcsContent({ primaryButtonConfig, primaryAction, isDisabled, isGitLoading, baseBranch, hasUpstream, behindCount, aheadCount, pushDisabled, pullDisabled, pushDisabledReason, pullDisabledReason, showContributionResolution, replaceDisabled, useDisabled, blockedRepositoryName, buttonSize, className, showDivergencePills, callbacks, resolution, resolutionTarget, confirmResolution, prNumber, }: SingleRepoVcsContentProps) { const singleRepoContributionCallbacks = buildSingleRepoContributionCallbacks( callbacks, blockedRepositoryName, ); return ( <> callbacks.onPR()} onPull={() => callbacks.onPull()} onPush={(force) => callbacks.onPush(force)} onReplaceContribution={singleRepoContributionCallbacks.onReplaceContribution} onUseContribution={singleRepoContributionCallbacks.onUseContribution} onViewPRVersion={singleRepoContributionCallbacks.onViewContribution} onCompareContribution={singleRepoContributionCallbacks.onCompareContribution} prNumber={prNumber} onRebase={() => callbacks.onRebase()} onMerge={() => callbacks.onMerge()} /> ); } export function VcsSplitButtonContent(props: VcsSplitButtonContentProps) { if (props.isMultiRepo) { return ; } return ; }