feat: create FallbackSteps UI component (Task 17)

Collapsible component supporting edit and execute modes. Edit mode
provides title/description inputs with add/remove controls. Execute
mode shows "This worked" / "Didn't help" action buttons with emerald/
rose styling. Amber accent styling throughout.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
chihlasm
2026-03-16 01:13:41 -04:00
parent a0ba253428
commit 7359ec8222

View File

@@ -0,0 +1,153 @@
import { useState } from 'react'
import { AlertCircle, ChevronDown, ChevronRight, Plus, Trash2, Check, X } from 'lucide-react'
import type { ProceduralStep } from '@/types'
import { cn } from '@/lib/utils'
interface FallbackStepsProps {
fallbackSteps: ProceduralStep[]
mode: 'edit' | 'execute'
// Edit mode
onAdd?: () => void
onRemove?: (index: number) => void
onUpdate?: (index: number, updates: Partial<ProceduralStep>) => void
// Execute mode
onComplete?: (stepId: string, notes: string | null, outcome: 'resolved' | 'not_resolved' | 'skipped') => void
completedIds?: Set<string>
}
export function FallbackSteps({
fallbackSteps,
mode,
onAdd,
onRemove,
onUpdate,
onComplete,
completedIds,
}: FallbackStepsProps) {
const [expanded, setExpanded] = useState(false)
// In execute mode, hide if no fallback steps
if (mode === 'execute' && fallbackSteps.length === 0) {
return null
}
const toggleLabel =
mode === 'execute'
? "Didn't work?"
: `Fallback branches (${fallbackSteps.length})`
return (
<div className="mt-4">
{/* Toggle button */}
<button
type="button"
onClick={() => setExpanded((v) => !v)}
className="flex items-center gap-2 text-sm text-muted-foreground hover:text-amber-400/80 transition-colors"
>
<AlertCircle className="h-4 w-4 text-amber-400/80 shrink-0" />
<span>{toggleLabel}</span>
{expanded ? (
<ChevronDown className="h-4 w-4" />
) : (
<ChevronRight className="h-4 w-4" />
)}
</button>
{expanded && (
<div className="mt-3 border-l-2 border-amber-400/20 pl-4">
<div className="space-y-2">
{fallbackSteps.map((fbStep, index) => {
const isCompleted = completedIds?.has(fbStep.id)
return (
<div
key={fbStep.id}
className={cn(
'rounded-lg border p-3 transition-colors',
'bg-white/[0.02] border-border/50',
isCompleted && 'border-emerald-500/30 bg-emerald-500/5'
)}
>
{mode === 'edit' ? (
<div className="space-y-2">
<div className="flex items-center gap-2">
<input
type="text"
value={fbStep.title}
onChange={(e) => onUpdate?.(index, { title: e.target.value })}
placeholder="Fallback step title"
className="flex-1 rounded border border-border bg-card px-2.5 py-1.5 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
/>
<button
type="button"
onClick={() => onRemove?.(index)}
className="shrink-0 rounded p-1.5 text-muted-foreground hover:bg-rose-500/10 hover:text-rose-400 transition-colors"
title="Remove fallback step"
>
<Trash2 className="h-3.5 w-3.5" />
</button>
</div>
<textarea
value={fbStep.description || ''}
onChange={(e) =>
onUpdate?.(index, { description: e.target.value || undefined })
}
placeholder="Describe this alternative approach..."
rows={2}
className="w-full rounded border border-border bg-card px-2.5 py-1.5 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
/>
</div>
) : (
// Execute mode
<div>
<p className={cn('text-sm font-medium', isCompleted ? 'text-emerald-400' : 'text-foreground')}>
{fbStep.title}
</p>
{fbStep.description && (
<p className="mt-1 text-xs text-muted-foreground">{fbStep.description}</p>
)}
{!isCompleted && (
<div className="mt-3 flex gap-2">
<button
type="button"
onClick={() => onComplete?.(fbStep.id, null, 'resolved')}
className="flex items-center gap-1.5 rounded-lg border border-emerald-500/30 bg-emerald-500/10 px-3 py-1.5 text-xs font-medium text-emerald-400 hover:bg-emerald-500/20 transition-colors"
>
<Check className="h-3.5 w-3.5" />
This worked
</button>
<button
type="button"
onClick={() => onComplete?.(fbStep.id, null, 'not_resolved')}
className="flex items-center gap-1.5 rounded-lg border border-rose-500/30 bg-rose-500/10 px-3 py-1.5 text-xs font-medium text-rose-400 hover:bg-rose-500/20 transition-colors"
>
<X className="h-3.5 w-3.5" />
Didn&apos;t help
</button>
</div>
)}
{isCompleted && (
<p className="mt-2 text-xs text-emerald-400/70">Resolved via this fallback</p>
)}
</div>
)}
</div>
)
})}
{mode === 'edit' && (
<button
type="button"
onClick={onAdd}
className="flex w-full items-center justify-center gap-1.5 rounded-lg border border-dashed border-amber-400/20 px-3 py-2 text-xs text-amber-400/60 hover:border-amber-400/40 hover:text-amber-400/80 transition-colors"
>
<Plus className="h-3.5 w-3.5" />
Add fallback step
</button>
)}
</div>
</div>
)}
</div>
)
}