diff options
| author | soryu <soryu@soryu.co> | 2026-01-11 05:52:14 +0000 |
|---|---|---|
| committer | soryu <soryu@soryu.co> | 2026-01-15 00:21:16 +0000 |
| commit | 87044a747b47bd83249d61a45842c7f7b2eae56d (patch) | |
| tree | ef2000ce79ffcc2723ef841acef5aa1deb1d5378 /makima/frontend/src/components/contracts/PhaseProgressBar.tsx | |
| parent | 077820c4167c168072d217a1b01df840463a12a8 (diff) | |
| download | soryu-87044a747b47bd83249d61a45842c7f7b2eae56d.tar.gz soryu-87044a747b47bd83249d61a45842c7f7b2eae56d.zip | |
Contract system
Diffstat (limited to 'makima/frontend/src/components/contracts/PhaseProgressBar.tsx')
| -rw-r--r-- | makima/frontend/src/components/contracts/PhaseProgressBar.tsx | 142 |
1 files changed, 142 insertions, 0 deletions
diff --git a/makima/frontend/src/components/contracts/PhaseProgressBar.tsx b/makima/frontend/src/components/contracts/PhaseProgressBar.tsx new file mode 100644 index 0000000..5ee7999 --- /dev/null +++ b/makima/frontend/src/components/contracts/PhaseProgressBar.tsx @@ -0,0 +1,142 @@ +import type { ContractPhase } from "../../lib/api"; + +interface PhaseProgressBarProps { + currentPhase: ContractPhase; + onPhaseClick?: (phase: ContractPhase) => void; + readonly?: boolean; +} + +const phases: ContractPhase[] = ["research", "specify", "plan", "execute", "review"]; + +const phaseLabels: Record<ContractPhase, string> = { + research: "Research", + specify: "Specify", + plan: "Plan", + execute: "Execute", + review: "Review", +}; + +const phaseColors: Record<ContractPhase, { active: string; inactive: string; completed: string }> = { + research: { + active: "bg-purple-400 border-purple-400", + inactive: "bg-transparent border-purple-400/30", + completed: "bg-purple-400/50 border-purple-400/50", + }, + specify: { + active: "bg-blue-400 border-blue-400", + inactive: "bg-transparent border-blue-400/30", + completed: "bg-blue-400/50 border-blue-400/50", + }, + plan: { + active: "bg-cyan-400 border-cyan-400", + inactive: "bg-transparent border-cyan-400/30", + completed: "bg-cyan-400/50 border-cyan-400/50", + }, + execute: { + active: "bg-yellow-400 border-yellow-400", + inactive: "bg-transparent border-yellow-400/30", + completed: "bg-yellow-400/50 border-yellow-400/50", + }, + review: { + active: "bg-green-400 border-green-400", + inactive: "bg-transparent border-green-400/30", + completed: "bg-green-400/50 border-green-400/50", + }, +}; + +export function PhaseProgressBar({ + currentPhase, + onPhaseClick, + readonly = false, +}: PhaseProgressBarProps) { + const currentIndex = phases.indexOf(currentPhase); + + return ( + <div className="flex items-center gap-1"> + {phases.map((phase, index) => { + const isActive = phase === currentPhase; + const isCompleted = index < currentIndex; + const colors = phaseColors[phase]; + const colorClass = isActive + ? colors.active + : isCompleted + ? colors.completed + : colors.inactive; + + const canClick = !readonly && onPhaseClick; + + return ( + <div key={phase} className="flex items-center"> + {/* Phase node */} + <button + onClick={() => canClick && onPhaseClick(phase)} + disabled={readonly} + className={` + relative group flex flex-col items-center + ${canClick ? "cursor-pointer" : "cursor-default"} + `} + > + {/* Circle */} + <div + className={` + w-3 h-3 rounded-full border-2 transition-all + ${colorClass} + ${canClick && !isActive ? "hover:scale-110" : ""} + `} + /> + {/* Label */} + <span + className={` + absolute top-4 font-mono text-[9px] uppercase tracking-wide whitespace-nowrap + ${isActive ? "text-[#dbe7ff]" : "text-[#555]"} + ${canClick && !isActive ? "group-hover:text-[#75aafc]" : ""} + `} + > + {phaseLabels[phase]} + </span> + </button> + + {/* Connector line */} + {index < phases.length - 1 && ( + <div + className={` + w-8 h-0.5 mx-1 + ${index < currentIndex ? "bg-[#3f6fb3]" : "bg-[rgba(117,170,252,0.15)]"} + `} + /> + )} + </div> + ); + })} + </div> + ); +} + +export function PhaseProgressBarCompact({ + currentPhase, +}: { + currentPhase: ContractPhase; +}) { + const currentIndex = phases.indexOf(currentPhase); + + return ( + <div className="flex items-center gap-0.5"> + {phases.map((phase, index) => { + const isActive = phase === currentPhase; + const isCompleted = index < currentIndex; + const colors = phaseColors[phase]; + + return ( + <div + key={phase} + className={` + w-2 h-2 rounded-full border + ${isActive ? colors.active : isCompleted ? colors.completed : colors.inactive} + `} + title={phaseLabels[phase]} + /> + ); + })} + </div> + ); +} |
