feat: add BranchMap sidebar with BranchNode tree visualization
BranchNode renders a branch button with depth-based indent, status icon, and status badge using the design system color palette. BranchMap builds a tree from the flat branch list via buildTree/flattenTree and renders all nodes with a GitBranch header. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
91
frontend/src/components/session/BranchNode.tsx
Normal file
91
frontend/src/components/session/BranchNode.tsx
Normal file
@@ -0,0 +1,91 @@
|
||||
import { CircleDot, CheckCircle2, XCircle, Circle, RotateCcw } from 'lucide-react'
|
||||
import { cn } from '@/lib/utils'
|
||||
import type { BranchResponse } from '@/types/branching'
|
||||
|
||||
type BranchStatus = BranchResponse['status']
|
||||
|
||||
interface StatusConfig {
|
||||
icon: React.ElementType
|
||||
textClass: string
|
||||
badgeClass: string
|
||||
label: string
|
||||
}
|
||||
|
||||
const STATUS_CONFIG: Record<BranchStatus, StatusConfig> = {
|
||||
active: {
|
||||
icon: CircleDot,
|
||||
textClass: 'text-accent',
|
||||
badgeClass: 'bg-accent-dim text-accent-text',
|
||||
label: 'Active',
|
||||
},
|
||||
solved: {
|
||||
icon: CheckCircle2,
|
||||
textClass: 'text-[#34d399]',
|
||||
badgeClass: 'bg-[rgba(52,211,153,0.10)] text-[#34d399]',
|
||||
label: 'Solved',
|
||||
},
|
||||
dead_end: {
|
||||
icon: XCircle,
|
||||
textClass: 'text-[#f87171]',
|
||||
badgeClass: 'bg-[rgba(248,113,113,0.10)] text-[#f87171]',
|
||||
label: 'Dead End',
|
||||
},
|
||||
untried: {
|
||||
icon: Circle,
|
||||
textClass: 'text-muted',
|
||||
badgeClass: 'bg-[rgba(79,86,102,0.20)] text-muted',
|
||||
label: 'Untried',
|
||||
},
|
||||
revived: {
|
||||
icon: RotateCcw,
|
||||
textClass: 'text-[#eab308]',
|
||||
badgeClass: 'bg-[rgba(234,179,8,0.10)] text-[#eab308]',
|
||||
label: 'Revived',
|
||||
},
|
||||
}
|
||||
|
||||
interface BranchNodeProps {
|
||||
branch: BranchResponse
|
||||
depth: number
|
||||
isActive: boolean
|
||||
onClick: (branchId: string) => void
|
||||
}
|
||||
|
||||
export function BranchNode({ branch, depth, isActive, onClick }: BranchNodeProps) {
|
||||
const config = STATUS_CONFIG[branch.status]
|
||||
const Icon = config.icon
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onClick(branch.id)}
|
||||
style={{ paddingLeft: `${8 + depth * 16}px` }}
|
||||
className={cn(
|
||||
'w-full flex items-center gap-2 py-2 pr-3 text-left rounded-md transition-colors',
|
||||
'hover:bg-elevated',
|
||||
isActive && 'bg-accent-dim border-l-2 border-accent'
|
||||
)}
|
||||
>
|
||||
<Icon
|
||||
size={14}
|
||||
className={cn('shrink-0', config.textClass)}
|
||||
/>
|
||||
<span
|
||||
className={cn(
|
||||
'flex-1 text-sm truncate',
|
||||
isActive ? 'text-heading font-medium' : 'text-primary'
|
||||
)}
|
||||
>
|
||||
{branch.label}
|
||||
</span>
|
||||
<span
|
||||
className={cn(
|
||||
'text-[10px] font-semibold uppercase tracking-wider px-1.5 py-0.5 rounded-full shrink-0',
|
||||
config.badgeClass
|
||||
)}
|
||||
>
|
||||
{config.label}
|
||||
</span>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user