import React from 'react'; import { Button } from '@/components/ui/button'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; import { Icon } from "@/components/icon/Icon"; import type { IconName } from "@/components/icon/icons"; import { BranchSelector } from './BranchSelector'; import { WorktreeBranchDisplay } from './WorktreeBranchDisplay'; import { SyncActions } from './SyncActions'; import type { GitStatus, GitIdentityProfile, GitRemote, GitRemoteComparison, GitHubPullRequest, GitHubChecksSummary, } from '@/lib/api/types'; import { useI18n } from '@/lib/i18n'; type SyncAction = 'fetch' | 'pull' | 'push' | 'sync' | null; interface GitHeaderProps { status: GitStatus | null; localBranches: string[]; remoteBranches: string[]; branchInfo: Record | undefined; syncAction: SyncAction; remotes: GitRemote[]; onFetch: (remote: GitRemote) => void; onSync: (remote: GitRemote) => void; onRemoveRemote: (remote: GitRemote) => void; removingRemoteName: string | null; onCheckoutBranch: (branch: string) => void; onCreateBranch: (name: string, remote?: GitRemote) => Promise; onRenameBranch?: (oldName: string, newName: string) => Promise; activeIdentityProfile: GitIdentityProfile | null; availableIdentities: GitIdentityProfile[]; onSelectIdentity: (profile: GitIdentityProfile) => void; isApplyingIdentity: boolean; isWorktreeMode: boolean; onOpenHistory?: () => void; onOpenGraph?: () => void; onOpenStashes?: () => void; onOpenUpdateBranch?: () => void; onOpenReintegrateCommits?: () => void; pullRequest?: GitHubPullRequest | null; prChecks?: GitHubChecksSummary | null; onOpenPullRequest?: () => void; } const IDENTITY_ICON_MAP: Record = { branch: 'git-branch', briefcase: 'briefcase', house: 'home', graduation: 'graduation-cap', code: 'code', heart: 'heart', user: 'user-3', fingerprint: 'fingerprint', }; const IDENTITY_COLOR_MAP: Record = { keyword: 'var(--syntax-keyword)', error: 'var(--status-error)', string: 'var(--syntax-string)', function: 'var(--syntax-function)', type: 'var(--syntax-type)', success: 'var(--status-success)', info: 'var(--status-info)', warning: 'var(--status-warning)', }; function getIdentityColor(token?: string | null) { if (!token) { return 'var(--primary)'; } return IDENTITY_COLOR_MAP[token] || 'var(--primary)'; } interface IdentityIconProps { icon?: string | null; className?: string; colorToken?: string | null; } const IdentityIcon: React.FC = ({ icon, className, colorToken }) => { const iconName = IDENTITY_ICON_MAP[icon ?? 'branch'] ?? 'user-3'; return ( ); }; interface IdentityDropdownProps { activeProfile: GitIdentityProfile | null; identities: GitIdentityProfile[]; onSelect: (profile: GitIdentityProfile) => void; isApplying: boolean; iconOnly?: boolean; } export const IdentityDropdown: React.FC = ({ activeProfile, identities, onSelect, isApplying, iconOnly = false, }) => { const { t } = useI18n(); const isDisabled = isApplying || identities.length === 0; return ( {t('gitView.header.identityTooltip')} {identities.length === 0 ? (

{t('gitView.header.noProfiles')}

) : ( identities.map((profile) => { const isSelected = activeProfile?.id === profile.id; return ( onSelect(profile)}> {profile.name} {profile.userEmail} {isSelected ? ( ) : null} ); }) )}
); }; interface UpstreamStatusPillProps { comparison: GitRemoteComparison; trackingBranch: string | null; tooltipDelayMs?: number; } const UpstreamStatusPill: React.FC = ({ comparison, trackingBranch, tooltipDelayMs = 1000, }) => { const { t } = useI18n(); const target = `${comparison.remote}/${comparison.branch}`; const isSynced = comparison.ahead === 0 && comparison.behind === 0; const tooltipText = trackingBranch ? t('gitView.header.upstreamTooltipTracking', { target, tracking: trackingBranch }) : t('gitView.header.upstreamTooltip', { target }); return (
{target} {isSynced ? ( {t('gitView.header.upstreamSynced')} ) : ( {comparison.ahead > 0 ? ( ↑{comparison.ahead} ) : null} {comparison.behind > 0 ? ( ↓{comparison.behind} ) : null} )}
{tooltipText}
); }; export const GitHeader: React.FC = ({ status, localBranches, remoteBranches, branchInfo, syncAction, remotes, onFetch, onSync, onRemoveRemote, removingRemoteName, onCheckoutBranch, onCreateBranch, onRenameBranch, activeIdentityProfile, availableIdentities, onSelectIdentity, isApplyingIdentity, isWorktreeMode, onOpenHistory, onOpenGraph, onOpenStashes, onOpenUpdateBranch, onOpenReintegrateCommits, pullRequest, prChecks, onOpenPullRequest, }) => { const { t } = useI18n(); if (!status) { return null; } const managementButtons = (
{onOpenHistory || onOpenGraph || onOpenStashes || onOpenUpdateBranch ? ( {t('gitView.header.repositoryViews')} {onOpenHistory ? ( {t('gitView.history.title')} ) : null} {onOpenGraph ? ( {t('gitView.graph.title')} ) : null} {onOpenStashes ? ( {t('gitView.stashes.title')} ) : null} {onOpenUpdateBranch ? ( {t('gitView.header.updateBranch')} ) : null} {onOpenReintegrateCommits ? ( {t('gitView.integrate.title')} ) : null} ) : null}
); const prChecksColor = prChecks ? prChecks.state === 'success' ? 'var(--status-success)' : prChecks.state === 'failure' ? 'var(--status-error)' : 'var(--status-warning)' : null; const prVisualState = pullRequest ? pullRequest.state === 'merged' ? 'merged' : pullRequest.state === 'closed' ? 'closed' : pullRequest.draft ? 'draft' : prChecks?.state === 'failure' || pullRequest.mergeable === false || pullRequest.mergeableState === 'blocked' || pullRequest.mergeableState === 'dirty' ? 'blocked' : 'open' : null; const prChip = pullRequest && onOpenPullRequest ? ( {t('gitView.header.openPullRequest')} ) : null; const syncButtons = ( 0} /> ); const upstreamStatusPill = status.upstreamComparison ? ( ) : null; const identityControl = ( ); return (
{isWorktreeMode ? ( ) : ( )}
{identityControl}
{prChip ?
{prChip}
: null}
{upstreamStatusPill ? (
{upstreamStatusPill}
) : null} {managementButtons}
{syncButtons}
); };