From 8a61c94b98cffe78be7b3304d419cbe7b5fe69da Mon Sep 17 00:00:00 2001 From: Jiayuan Date: Tue, 31 Mar 2026 03:26:43 +0800 Subject: [PATCH] feat(ui): restyle issue status and priority with colored badges - Status labels use colored pill badges (solid bg for active, muted for inactive) - Board columns have tinted backgrounds matching their status color - Priority badges use orange (--priority) design token for clear distinction from status - Issue cards restructured: identifier, title, then assignee/priority/date row - Agent avatar default color changed from blue to gray - New Issue button in header changed to solid/primary style - Reduced hover shadow on board cards - Added inheritColor prop to StatusIcon and PriorityIcon for badge use --- apps/web/app/globals.css | 3 + apps/web/components/common/actor-avatar.tsx | 2 +- .../components/batch-action-toolbar.tsx | 6 +- .../features/issues/components/board-card.tsx | 154 +++++++++--------- .../issues/components/board-column.tsx | 12 +- .../issues/components/issue-detail.tsx | 12 +- .../issues/components/issues-header.tsx | 1 - .../features/issues/components/list-view.tsx | 8 +- .../components/pickers/priority-picker.tsx | 6 +- .../issues/components/priority-icon.tsx | 6 +- .../issues/components/status-icon.tsx | 4 +- apps/web/features/issues/config/priority.ts | 12 +- apps/web/features/issues/config/status.ts | 24 ++- apps/web/features/modals/create-issue.tsx | 6 +- 14 files changed, 140 insertions(+), 116 deletions(-) diff --git a/apps/web/app/globals.css b/apps/web/app/globals.css index e0b3950f..66118975 100644 --- a/apps/web/app/globals.css +++ b/apps/web/app/globals.css @@ -31,6 +31,7 @@ --color-info: var(--info); --color-brand: var(--brand); --color-brand-foreground: var(--brand-foreground); + --color-priority: var(--priority); --color-canvas: var(--canvas); --color-accent-foreground: var(--accent-foreground); --color-accent: var(--accent); @@ -94,6 +95,7 @@ --success: oklch(0.55 0.16 145); --warning: oklch(0.75 0.16 85); --info: oklch(0.55 0.18 250); + --priority: oklch(0.65 0.18 50); --scrollbar-thumb: oklch(0.82 0.003 286); --scrollbar-thumb-hover: oklch(0.705 0.015 286.067); --scrollbar-track: transparent; @@ -137,6 +139,7 @@ --success: oklch(0.65 0.15 145); --warning: oklch(0.70 0.16 85); --info: oklch(0.65 0.18 250); + --priority: oklch(0.70 0.18 50); --scrollbar-thumb: oklch(1 0 0 / 15%); --scrollbar-thumb-hover: oklch(1 0 0 / 30%); --scrollbar-track: transparent; diff --git a/apps/web/components/common/actor-avatar.tsx b/apps/web/components/common/actor-avatar.tsx index b6bc740a..ad9d8d48 100644 --- a/apps/web/components/common/actor-avatar.tsx +++ b/apps/web/components/common/actor-avatar.tsx @@ -33,7 +33,7 @@ function ActorAvatar({
- - {cfg.label} + + + {cfg.label} + ); })} diff --git a/apps/web/features/issues/components/board-card.tsx b/apps/web/features/issues/components/board-card.tsx index a6eed5e0..88555263 100644 --- a/apps/web/features/issues/components/board-card.tsx +++ b/apps/web/features/issues/components/board-card.tsx @@ -62,37 +62,12 @@ export function BoardCardContent({ const showBottom = showAssignee || showDueDate; return ( -
- {/* Priority */} - {showPriority && - (editable ? ( - - - - - {priorityCfg.label} - - - } - /> - - ) : ( -
- - - {priorityCfg.label} - -
- ))} +
+ {/* Row 1: Identifier */} +

{issue.identifier}

- {/* Title */} -

+ {/* Row 2: Title */} +

{issue.title}

@@ -103,66 +78,87 @@ export function BoardCardContent({

)} - {/* Bottom: assignee + due date */} - {showBottom && ( -
-
- {showAssignee && - (editable ? ( - - - } - /> - - ) : ( - - ))} -
- {showDueDate && + {/* Row 3: Assignee, priority badge, due date */} + {(showAssignee || showPriority || showDueDate) && ( +
+ {showAssignee && (editable ? ( - - - {formatDate(issue.due_date!)} + + } + /> + + ) : ( + + ))} + {showPriority && + (editable ? ( + + + + {priorityCfg.label} } /> ) : ( - - - {formatDate(issue.due_date!)} + + + {priorityCfg.label} ))} + {showDueDate && ( +
+ {editable ? ( + + + + {formatDate(issue.due_date!)} + + } + /> + + ) : ( + + + {formatDate(issue.due_date!)} + + )} +
+ )}
)}
diff --git a/apps/web/features/issues/components/board-column.tsx b/apps/web/features/issues/components/board-column.tsx index ee0b2901..bdb89929 100644 --- a/apps/web/features/issues/components/board-column.tsx +++ b/apps/web/features/issues/components/board-column.tsx @@ -43,13 +43,15 @@ export function BoardColumn({ ); return ( -
+
- {/* Left: icon + label + count */} + {/* Left: status badge + count */}
- - {cfg.label} - + + + {cfg.label} + + {issues.length}
diff --git a/apps/web/features/issues/components/issue-detail.tsx b/apps/web/features/issues/components/issue-detail.tsx index 48a72778..7e300263 100644 --- a/apps/web/features/issues/components/issue-detail.tsx +++ b/apps/web/features/issues/components/issue-detail.tsx @@ -744,8 +744,10 @@ export function IssueDetail({ issueId, onDelete, defaultSidebarOpen = true, layo key={p} onClick={() => handleUpdateField({ priority: p })} > - - {PRIORITY_CONFIG[p].label} + + + {PRIORITY_CONFIG[p].label} + {issue.priority === p && } ))} @@ -1213,8 +1215,10 @@ export function IssueDetail({ issueId, onDelete, defaultSidebarOpen = true, layo {PRIORITY_ORDER.map((p) => ( handleUpdateField({ priority: p })}> - - {PRIORITY_CONFIG[p].label} + + + {PRIORITY_CONFIG[p].label} + {p === issue.priority && } ))} diff --git a/apps/web/features/issues/components/issues-header.tsx b/apps/web/features/issues/components/issues-header.tsx index a6030de1..558730a4 100644 --- a/apps/web/features/issues/components/issues-header.tsx +++ b/apps/web/features/issues/components/issues-header.tsx @@ -310,7 +310,6 @@ export function IssuesHeader() { {/* New issue */}
- - {cfg.label} - + + + {cfg.label} + + {statusIssues.length} diff --git a/apps/web/features/issues/components/pickers/priority-picker.tsx b/apps/web/features/issues/components/pickers/priority-picker.tsx index 08803222..09cd840c 100644 --- a/apps/web/features/issues/components/pickers/priority-picker.tsx +++ b/apps/web/features/issues/components/pickers/priority-picker.tsx @@ -43,8 +43,10 @@ export function PriorityPicker({ setOpen(false); }} > - - {c.label} + + + {c.label} + ); })} diff --git a/apps/web/features/issues/components/priority-icon.tsx b/apps/web/features/issues/components/priority-icon.tsx index 4b3ef7c8..963efb4e 100644 --- a/apps/web/features/issues/components/priority-icon.tsx +++ b/apps/web/features/issues/components/priority-icon.tsx @@ -4,9 +4,11 @@ import { PRIORITY_CONFIG } from "@/features/issues/config"; export function PriorityIcon({ priority, className = "", + inheritColor = false, }: { priority: IssuePriority; className?: string; + inheritColor?: boolean; }) { const cfg = PRIORITY_CONFIG[priority]; @@ -15,7 +17,7 @@ export function PriorityIcon({ return ( diff --git a/apps/web/features/issues/components/status-icon.tsx b/apps/web/features/issues/components/status-icon.tsx index 573e0c54..d2368110 100644 --- a/apps/web/features/issues/components/status-icon.tsx +++ b/apps/web/features/issues/components/status-icon.tsx @@ -160,9 +160,11 @@ const STATUS_RENDERERS: Record React.ReactNode> = { export function StatusIcon({ status, className = "h-4 w-4", + inheritColor = false, }: { status: IssueStatus; className?: string; + inheritColor?: boolean; }) { const cfg = STATUS_CONFIG[status]; const Renderer = STATUS_RENDERERS[status]; @@ -171,7 +173,7 @@ export function StatusIcon({ diff --git a/apps/web/features/issues/config/priority.ts b/apps/web/features/issues/config/priority.ts index 6c810094..b0c22d2d 100644 --- a/apps/web/features/issues/config/priority.ts +++ b/apps/web/features/issues/config/priority.ts @@ -10,11 +10,11 @@ export const PRIORITY_ORDER: IssuePriority[] = [ export const PRIORITY_CONFIG: Record< IssuePriority, - { label: string; bars: number; color: string } + { label: string; bars: number; color: string; badgeBg: string; badgeText: string } > = { - urgent: { label: "Urgent", bars: 4, color: "text-destructive" }, - high: { label: "High", bars: 3, color: "text-warning" }, - medium: { label: "Medium", bars: 2, color: "text-warning" }, - low: { label: "Low", bars: 1, color: "text-info" }, - none: { label: "No priority", bars: 0, color: "text-muted-foreground" }, + urgent: { label: "Urgent", bars: 4, color: "text-destructive", badgeBg: "bg-priority", badgeText: "text-white" }, + high: { label: "High", bars: 3, color: "text-warning", badgeBg: "bg-priority/80", badgeText: "text-white" }, + medium: { label: "Medium", bars: 2, color: "text-warning", badgeBg: "bg-priority/15", badgeText: "text-priority" }, + low: { label: "Low", bars: 1, color: "text-info", badgeBg: "bg-priority/10", badgeText: "text-priority" }, + none: { label: "No priority", bars: 0, color: "text-muted-foreground", badgeBg: "bg-muted", badgeText: "text-muted-foreground" }, }; diff --git a/apps/web/features/issues/config/status.ts b/apps/web/features/issues/config/status.ts index fe4f00ef..88ca389a 100644 --- a/apps/web/features/issues/config/status.ts +++ b/apps/web/features/issues/config/status.ts @@ -22,13 +22,21 @@ export const ALL_STATUSES: IssueStatus[] = [ export const STATUS_CONFIG: Record< IssueStatus, - { label: string; iconColor: string; hoverBg: string } + { + label: string; + iconColor: string; + hoverBg: string; + dividerColor: string; + badgeBg: string; + badgeText: string; + columnBg: string; + } > = { - backlog: { label: "Backlog", iconColor: "text-muted-foreground", hoverBg: "hover:bg-accent" }, - todo: { label: "Todo", iconColor: "text-muted-foreground", hoverBg: "hover:bg-accent" }, - in_progress: { label: "In Progress", iconColor: "text-warning", hoverBg: "hover:bg-warning/10" }, - in_review: { label: "In Review", iconColor: "text-success", hoverBg: "hover:bg-success/10" }, - done: { label: "Done", iconColor: "text-info", hoverBg: "hover:bg-info/10" }, - blocked: { label: "Blocked", iconColor: "text-destructive", hoverBg: "hover:bg-destructive/10" }, - cancelled: { label: "Cancelled", iconColor: "text-muted-foreground", hoverBg: "hover:bg-accent" }, + backlog: { label: "Backlog", iconColor: "text-muted-foreground", hoverBg: "hover:bg-accent", dividerColor: "bg-muted-foreground/40", badgeBg: "bg-muted", badgeText: "text-muted-foreground", columnBg: "bg-muted/40" }, + todo: { label: "Todo", iconColor: "text-muted-foreground", hoverBg: "hover:bg-accent", dividerColor: "bg-muted-foreground/40", badgeBg: "bg-muted", badgeText: "text-muted-foreground", columnBg: "bg-muted/40" }, + in_progress: { label: "In Progress", iconColor: "text-warning", hoverBg: "hover:bg-warning/10", dividerColor: "bg-warning", badgeBg: "bg-warning", badgeText: "text-white", columnBg: "bg-warning/5" }, + in_review: { label: "In Review", iconColor: "text-success", hoverBg: "hover:bg-success/10", dividerColor: "bg-success", badgeBg: "bg-success", badgeText: "text-white", columnBg: "bg-success/5" }, + done: { label: "Done", iconColor: "text-info", hoverBg: "hover:bg-info/10", dividerColor: "bg-info", badgeBg: "bg-info", badgeText: "text-white", columnBg: "bg-info/5" }, + blocked: { label: "Blocked", iconColor: "text-destructive", hoverBg: "hover:bg-destructive/10", dividerColor: "bg-destructive", badgeBg: "bg-destructive", badgeText: "text-white", columnBg: "bg-destructive/5" }, + cancelled: { label: "Cancelled", iconColor: "text-muted-foreground", hoverBg: "hover:bg-accent", dividerColor: "bg-muted-foreground/40", badgeBg: "bg-muted", badgeText: "text-muted-foreground", columnBg: "bg-muted/40" }, }; diff --git a/apps/web/features/modals/create-issue.tsx b/apps/web/features/modals/create-issue.tsx index 9ce70cb6..abb0d0c6 100644 --- a/apps/web/features/modals/create-issue.tsx +++ b/apps/web/features/modals/create-issue.tsx @@ -248,8 +248,10 @@ export function CreateIssueModal({ onClose, data }: { onClose: () => void; data? {PRIORITY_ORDER.map((p) => ( updatePriority(p)}> - - {PRIORITY_CONFIG[p].label} + + + {PRIORITY_CONFIG[p].label} + ))}