11import { useDroppable } from '@dnd-kit/core' ;
22import { SortableContext , horizontalListSortingStrategy } from '@dnd-kit/sortable' ;
3- import { ChevronRight } from 'lucide-react' ;
3+ import { ChevronRight , Plus , Circle } from 'lucide-react' ;
44import { cn } from '@baros/lib/utils' ;
55import type { Step , Phase , StepKey } from '../types' ;
66import { StepCard } from './StepCard' ;
@@ -13,14 +13,45 @@ interface PhaseRowProps {
1313 readonly selectedStepKey : StepKey | null ;
1414 readonly onStepSelect : ( key : StepKey ) => void ;
1515 readonly onStepRemove : ( phase : Phase , stepIndex : number ) => void ;
16+ readonly onInsertStep ?: ( phase : Phase , atIndex : number ) => void ;
1617 readonly dropState ?: 'compatible' | 'incompatible' | null ;
1718}
1819
1920function Arrow ( ) {
2021 return < ChevronRight className = "h-4 w-4 shrink-0 text-muted-foreground/50" /> ;
2122}
2223
23- export function PhaseRow ( { label, phase, steps, flowIndex, selectedStepKey, onStepSelect, onStepRemove, dropState } : PhaseRowProps ) {
24+ function EndpointBadge ( { label, variant } : { label : string ; variant : 'start' | 'end' } ) {
25+ return (
26+ < div className = { cn (
27+ 'flex shrink-0 items-center gap-1.5 rounded-full border px-2.5 py-1 text-xs font-medium' ,
28+ variant === 'start'
29+ ? 'border-blue-200 bg-blue-50 text-blue-700 dark:border-blue-800 dark:bg-blue-950 dark:text-blue-300'
30+ : 'border-emerald-200 bg-emerald-50 text-emerald-700 dark:border-emerald-800 dark:bg-emerald-950 dark:text-emerald-300' ,
31+ ) } >
32+ < Circle className = "h-2 w-2 fill-current" />
33+ { label }
34+ </ div >
35+ ) ;
36+ }
37+
38+ function InsertButton ( { onClick } : { onClick : ( ) => void } ) {
39+ return (
40+ < button
41+ type = "button"
42+ onClick = { onClick }
43+ className = "group/insert flex shrink-0 items-center"
44+ aria-label = "Insert policy here"
45+ >
46+ < Arrow />
47+ < span className = "flex h-5 w-5 items-center justify-center rounded-full border border-dashed border-muted-foreground/30 opacity-0 transition-opacity group-hover/insert:opacity-100 hover:border-primary hover:text-primary" >
48+ < Plus className = "h-3 w-3" />
49+ </ span >
50+ </ button >
51+ ) ;
52+ }
53+
54+ export function PhaseRow ( { label, phase, steps, flowIndex, selectedStepKey, onStepSelect, onStepRemove, onInsertStep, dropState } : PhaseRowProps ) {
2455 const droppableId = `phase-${ flowIndex } -${ phase } ` ;
2556 const { setNodeRef, isOver } = useDroppable ( {
2657 id : droppableId ,
@@ -32,6 +63,9 @@ export function PhaseRow({ label, phase, steps, flowIndex, selectedStepKey, onSt
3263 const isSelected = ( index : number ) =>
3364 selectedStepKey ?. phase === phase && selectedStepKey . index === index ;
3465
66+ const startLabel = phase === 'request' ? 'Entrypoint' : 'Endpoint' ;
67+ const endLabel = phase === 'request' ? 'Endpoint' : 'Entrypoint' ;
68+
3569 return (
3670 < div className = "flex flex-col gap-2" >
3771 < h3 className = { cn (
@@ -50,14 +84,24 @@ export function PhaseRow({ label, phase, steps, flowIndex, selectedStepKey, onSt
5084 ! isOver && 'border-border/50 bg-muted/30' ,
5185 ) }
5286 >
87+ < EndpointBadge label = { startLabel } variant = "start" />
88+
5389 { steps . length === 0 && (
54- < div className = "px-4 py-2 text-xs text-muted-foreground" >
55- Drop a policy here
56- </ div >
90+ < >
91+ < Arrow />
92+ < div className = "px-4 py-2 text-xs text-muted-foreground" >
93+ Drop a policy here
94+ </ div >
95+ </ >
5796 ) }
97+
5898 { steps . map ( ( step , index ) => (
5999 < div key = { step . id } className = "flex items-center gap-1" >
60- { index > 0 && < Arrow /> }
100+ { onInsertStep ? (
101+ < InsertButton onClick = { ( ) => onInsertStep ( phase , index ) } />
102+ ) : (
103+ < Arrow />
104+ ) }
61105 < StepCard
62106 step = { step }
63107 phase = { phase }
@@ -69,6 +113,10 @@ export function PhaseRow({ label, phase, steps, flowIndex, selectedStepKey, onSt
69113 />
70114 </ div >
71115 ) ) }
116+
117+ < Arrow />
118+ < EndpointBadge label = { endLabel } variant = "end" />
119+
72120 { isOver && dropState === 'incompatible' && (
73121 < div className = "ml-2 text-xs text-destructive" >
74122 Incompatible phase
0 commit comments