3,200+ hardcoded color values replaced with CSS variable-backed Tailwind classes (bg-card, text-foreground, border-border, etc.). Enables light mode via CSS variable swap. Only syntax highlighting colors and intentional one-offs remain hardcoded (~15 values). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
76 lines
3.1 KiB
TypeScript
76 lines
3.1 KiB
TypeScript
import { CheckCircle2, Circle, ArrowRight } from 'lucide-react'
|
|
import type { RuntimeStep } from '@/types'
|
|
import { cn } from '@/lib/utils'
|
|
|
|
interface StepChecklistProps {
|
|
steps: RuntimeStep[]
|
|
currentStepIndex: number
|
|
completedStepIds: Set<string>
|
|
onStepClick: (index: number) => void
|
|
}
|
|
|
|
export function StepChecklist({ steps, currentStepIndex, completedStepIds, onStepClick }: StepChecklistProps) {
|
|
const procedureSteps = steps.filter((s) => s.type === 'procedure_step')
|
|
|
|
// Pre-compute which steps should show a section header
|
|
const sectionVisibility = new Set<number>()
|
|
let prevSection: string | undefined
|
|
for (let i = 0; i < procedureSteps.length; i++) {
|
|
const s = procedureSteps[i]
|
|
const header = 'section_header' in s ? s.section_header : undefined
|
|
if (header && header !== prevSection) sectionVisibility.add(i)
|
|
if (header) prevSection = header
|
|
}
|
|
|
|
return (
|
|
<nav className="space-y-0.5">
|
|
{procedureSteps.map((step, index) => {
|
|
const isCompleted = completedStepIds.has(step.id)
|
|
const isCurrent = index === currentStepIndex
|
|
const showSection = sectionVisibility.has(index)
|
|
|
|
return (
|
|
<div key={step.id}>
|
|
{showSection && (
|
|
<div className="mb-1 mt-3 border-b border-border pb-1 text-[10px] font-semibold uppercase tracking-wider text-muted-foreground first:mt-0">
|
|
{'section_header' in step ? step.section_header : undefined}
|
|
</div>
|
|
)}
|
|
<button
|
|
onClick={() => onStepClick(index)}
|
|
className={cn(
|
|
'flex w-full items-center gap-2 rounded-lg px-2 py-1.5 text-left text-sm transition-colors',
|
|
isCurrent && 'bg-accent text-foreground',
|
|
!isCurrent && isCompleted && 'text-muted-foreground',
|
|
!isCurrent && !isCompleted && 'text-muted-foreground hover:bg-accent/50'
|
|
)}
|
|
>
|
|
{isCompleted ? (
|
|
<CheckCircle2 className="h-4 w-4 shrink-0 text-emerald-400" />
|
|
) : isCurrent ? (
|
|
<ArrowRight className="h-4 w-4 shrink-0 text-foreground" />
|
|
) : (
|
|
<Circle className="h-4 w-4 shrink-0 text-muted-foreground" />
|
|
)}
|
|
<span className="flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-accent text-[10px] font-medium">
|
|
{index + 1}
|
|
</span>
|
|
<span className="min-w-0 flex-1 flex items-center gap-1.5 overflow-hidden">
|
|
<span className="truncate">{step.title || 'Untitled step'}</span>
|
|
{'isCustom' in step && step.isCustom && (
|
|
<span className="shrink-0 rounded-full bg-amber-400/15 px-1.5 py-0.5 text-[9px] font-semibold uppercase tracking-wide text-amber-400">
|
|
Custom
|
|
</span>
|
|
)}
|
|
</span>
|
|
{'estimated_minutes' in step && step.estimated_minutes && (
|
|
<span className="shrink-0 text-[10px] text-muted-foreground">~{step.estimated_minutes}m</span>
|
|
)}
|
|
</button>
|
|
</div>
|
|
)
|
|
})}
|
|
</nav>
|
|
)
|
|
}
|