11import { memo } from "react" ;
22import { CheckIcon , LoaderIcon } from "lucide-react" ;
33import { cn } from "~/lib/utils" ;
4+ import { Badge } from "./ui/badge" ;
45
56// ---------------------------------------------------------------------------
67// Types
@@ -11,6 +12,12 @@ export interface PlanChecklistItemData {
1112 text : string ;
1213 /** Execution status. */
1314 status : "pending" | "inProgress" | "completed" ;
15+ /** Optional supporting note shown below the step. */
16+ note ?: string ;
17+ /** Optional status label override shown beside the step. */
18+ statusText ?: string ;
19+ /** Optional badge tone for the status label override. */
20+ statusTone ?: "success" | "info" | "warning" ;
1421}
1522
1623interface PlanChecklistProps {
@@ -50,14 +57,22 @@ const PlanChecklist = memo(function PlanChecklist({
5057 { items . length === 1 ? "To-do" : "To-dos" }
5158 < span className = "mx-1.5 text-muted-foreground/30" > ·</ span >
5259 < span > { completionMode } </ span >
60+ { completedCount > 0 ? (
61+ < >
62+ < span className = "mx-1.5 text-muted-foreground/30" > ·</ span >
63+ < span className = "font-medium text-emerald-700/80 dark:text-emerald-300/85" >
64+ { completedCount === items . length ? "All done" : `${ completedCount } done` }
65+ </ span >
66+ </ >
67+ ) : null }
5368 </ p >
5469 </ div >
5570
5671 { /* Items */ }
5772 < div className = "rounded-xl border border-border/50 bg-background/40" >
5873 { items . map ( ( item , index ) => (
5974 < PlanChecklistRow
60- key = { `${ item . status } :${ item . text } ` }
75+ key = { `${ item . text } :${ item . note ?? "" } : ${ item . statusText ?? item . status } ` }
6176 item = { item }
6277 index = { index }
6378 isLast = { index === items . length - 1 }
@@ -67,7 +82,7 @@ const PlanChecklist = memo(function PlanChecklist({
6782 </ div >
6883
6984 { /* Progress summary */ }
70- { completedCount > 0 && completedCount < items . length ? (
85+ { completedCount > 0 ? (
7186 < div className = "flex items-center gap-2.5 px-1" >
7287 < div className = "h-1 min-w-0 flex-1 overflow-hidden rounded-full bg-muted/50" >
7388 < div
@@ -77,8 +92,8 @@ const PlanChecklist = memo(function PlanChecklist({
7792 } }
7893 />
7994 </ div >
80- < span className = "shrink-0 text-[10px] tabular-nums text-muted-foreground/50 " >
81- { completedCount } / { items . length }
95+ < span className = "shrink-0 text-[10px] tabular-nums text-emerald-700/80 dark:text-emerald-300/80 " >
96+ { completedCount === items . length ? "Done" : ` ${ completedCount } / $ {items . length } done` }
8297 </ span >
8398 </ div >
8499 ) : null }
@@ -101,34 +116,58 @@ const PlanChecklistRow = memo(function PlanChecklistRow({
101116 isLast : boolean ;
102117 live : boolean ;
103118} ) {
119+ const statusBadge = resolveChecklistStatusBadge ( item ) ;
120+
104121 return (
105122 < div
106123 data-slot = "plan-checklist-item"
107124 data-status = { item . status }
108125 className = { cn (
109126 "flex items-start gap-3 px-3.5 py-2.5 transition-colors duration-150" ,
110127 ! isLast && "border-b border-border/30" ,
111- item . status === "inProgress" && "bg-blue-500/[0.03]" ,
128+ item . status === "completed" && "bg-emerald-500/[0.04]" ,
129+ item . status === "inProgress" &&
130+ ( item . statusTone === "warning" ? "bg-amber-500/[0.06]" : "bg-blue-500/[0.03]" ) ,
112131 ) }
113132 >
114133 { /* Status indicator */ }
115134 < div className = "mt-0.5 flex size-5 shrink-0 items-center justify-center" >
116- < ChecklistStatusIndicator status = { item . status } live = { live } />
135+ < ChecklistStatusIndicator status = { item . status } statusTone = { item . statusTone } live = { live } />
117136 </ div >
118137
119138 { /* Text */ }
120- < p
121- className = { cn (
122- "min-w-0 flex-1 text-[13px] leading-snug" ,
123- item . status === "completed"
124- ? "text-muted-foreground/45 line-through decoration-muted-foreground/20"
125- : item . status === "inProgress"
126- ? "text-foreground/90 font-medium"
127- : "text-foreground/70" ,
128- ) }
129- >
130- { item . text }
131- </ p >
139+ < div className = "min-w-0 flex-1" >
140+ < p
141+ className = { cn (
142+ "text-[13px] leading-snug" ,
143+ item . status === "completed"
144+ ? "text-emerald-800 dark:text-emerald-200/95"
145+ : item . status === "inProgress"
146+ ? "font-medium text-foreground/90"
147+ : "text-foreground/70" ,
148+ ) }
149+ >
150+ { item . text }
151+ </ p >
152+ { item . note ? (
153+ < p
154+ className = { cn (
155+ "mt-1 text-[11px] leading-relaxed" ,
156+ item . statusTone === "warning"
157+ ? "text-amber-800/80 dark:text-amber-200/80"
158+ : "text-muted-foreground/65" ,
159+ ) }
160+ >
161+ { item . note }
162+ </ p >
163+ ) : null }
164+ </ div >
165+
166+ { statusBadge ? (
167+ < Badge size = "sm" variant = { statusBadge . variant } className = "mt-0.5 shrink-0" >
168+ { statusBadge . label }
169+ </ Badge >
170+ ) : null }
132171
133172 { /* Item number */ }
134173 < span className = "mt-0.5 shrink-0 text-[10px] tabular-nums text-muted-foreground/25" >
@@ -144,9 +183,11 @@ const PlanChecklistRow = memo(function PlanChecklistRow({
144183
145184function ChecklistStatusIndicator ( {
146185 status,
186+ statusTone,
147187 live,
148188} : {
149189 status : PlanChecklistItemData [ "status" ] ;
190+ statusTone ?: PlanChecklistItemData [ "statusTone" ] ;
150191 live : boolean ;
151192} ) {
152193 if ( status === "completed" ) {
@@ -158,6 +199,13 @@ function ChecklistStatusIndicator({
158199 }
159200
160201 if ( status === "inProgress" ) {
202+ if ( statusTone === "warning" ) {
203+ return (
204+ < span className = "flex size-[18px] items-center justify-center rounded-full bg-amber-500/10 text-amber-700 ring-1 ring-amber-400/30 dark:text-amber-300" >
205+ < span className = "text-[11px] font-semibold leading-none" > ?</ span >
206+ </ span >
207+ ) ;
208+ }
161209 return (
162210 < span className = "flex size-[18px] items-center justify-center rounded-full bg-blue-500/10 text-blue-400 ring-1 ring-blue-400/30" >
163211 { live ? (
@@ -177,5 +225,26 @@ function ChecklistStatusIndicator({
177225 ) ;
178226}
179227
228+ function resolveChecklistStatusBadge (
229+ item : PlanChecklistItemData ,
230+ ) : { label : string ; variant : "success" | "info" | "warning" } | null {
231+ if ( item . statusText && item . statusTone ) {
232+ return {
233+ label : item . statusText ,
234+ variant : item . statusTone ,
235+ } ;
236+ }
237+
238+ if ( item . status === "completed" ) {
239+ return { label : "Done" , variant : "success" } ;
240+ }
241+
242+ if ( item . status === "inProgress" ) {
243+ return { label : "Working" , variant : "info" } ;
244+ }
245+
246+ return null ;
247+ }
248+
180249export default PlanChecklist ;
181250export type { PlanChecklistProps } ;
0 commit comments