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
This commit is contained in:
Jiayuan 2026-03-31 03:26:43 +08:00
parent 32e19f847f
commit 8a61c94b98
14 changed files with 140 additions and 116 deletions

View file

@ -149,8 +149,10 @@ export function BatchActionToolbar() {
}}
className="flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm hover:bg-accent transition-colors"
>
<PriorityIcon priority={p} />
<span>{cfg.label}</span>
<span className={`inline-flex items-center gap-1 rounded px-1.5 py-0.5 text-xs font-medium ${cfg.badgeBg} ${cfg.badgeText}`}>
<PriorityIcon priority={p} className="h-3 w-3" inheritColor />
{cfg.label}
</span>
</button>
);
})}

View file

@ -62,37 +62,12 @@ export function BoardCardContent({
const showBottom = showAssignee || showDueDate;
return (
<div className="rounded-lg border bg-card p-3.5 shadow-[0_1px_2px_0_rgba(0,0,0,0.03)] transition-shadow group-hover:shadow-md">
{/* Priority */}
{showPriority &&
(editable ? (
<PickerWrapper>
<PriorityPicker
priority={issue.priority}
onUpdate={handleUpdate}
trigger={
<>
<PriorityIcon priority={issue.priority} />
<span className={`text-xs font-medium ${priorityCfg.color}`}>
{priorityCfg.label}
</span>
</>
}
/>
</PickerWrapper>
) : (
<div className="flex items-center gap-1.5">
<PriorityIcon priority={issue.priority} />
<span className={`text-xs font-medium ${priorityCfg.color}`}>
{priorityCfg.label}
</span>
</div>
))}
<div className="rounded-lg border bg-card p-3.5 shadow-[0_1px_2px_0_rgba(0,0,0,0.03)] transition-shadow group-hover:shadow-sm">
{/* Row 1: Identifier */}
<p className="text-xs text-muted-foreground">{issue.identifier}</p>
{/* Title */}
<p
className={`text-sm font-medium leading-snug line-clamp-2 ${showPriority ? "mt-2" : ""}`}
>
{/* Row 2: Title */}
<p className="mt-1 text-sm font-medium leading-snug line-clamp-2">
{issue.title}
</p>
@ -103,66 +78,87 @@ export function BoardCardContent({
</p>
)}
{/* Bottom: assignee + due date */}
{showBottom && (
<div className="mt-3 flex items-center justify-between">
<div className="flex items-center">
{showAssignee &&
(editable ? (
<PickerWrapper>
<AssigneePicker
assigneeType={issue.assignee_type}
assigneeId={issue.assignee_id}
onUpdate={handleUpdate}
trigger={
<ActorAvatar
actorType={issue.assignee_type!}
actorId={issue.assignee_id!}
size={22}
/>
}
/>
</PickerWrapper>
) : (
<ActorAvatar
actorType={issue.assignee_type!}
actorId={issue.assignee_id!}
size={22}
/>
))}
</div>
{showDueDate &&
{/* Row 3: Assignee, priority badge, due date */}
{(showAssignee || showPriority || showDueDate) && (
<div className="mt-3 flex items-center gap-2">
{showAssignee &&
(editable ? (
<PickerWrapper>
<DueDatePicker
dueDate={issue.due_date}
<AssigneePicker
assigneeType={issue.assignee_type}
assigneeId={issue.assignee_id}
onUpdate={handleUpdate}
trigger={
<span
className={`flex items-center gap-1 text-xs ${
new Date(issue.due_date!) < new Date()
? "text-destructive"
: "text-muted-foreground"
}`}
>
<CalendarDays className="size-3" />
{formatDate(issue.due_date!)}
<ActorAvatar
actorType={issue.assignee_type!}
actorId={issue.assignee_id!}
size={22}
/>
}
/>
</PickerWrapper>
) : (
<ActorAvatar
actorType={issue.assignee_type!}
actorId={issue.assignee_id!}
size={22}
/>
))}
{showPriority &&
(editable ? (
<PickerWrapper>
<PriorityPicker
priority={issue.priority}
onUpdate={handleUpdate}
trigger={
<span className={`inline-flex items-center gap-1 rounded px-1.5 py-0.5 text-xs font-medium ${priorityCfg.badgeBg} ${priorityCfg.badgeText}`}>
<PriorityIcon priority={issue.priority} className="h-3 w-3" inheritColor />
{priorityCfg.label}
</span>
}
/>
</PickerWrapper>
) : (
<span
className={`flex items-center gap-1 text-xs ${
new Date(issue.due_date!) < new Date()
? "text-destructive"
: "text-muted-foreground"
}`}
>
<CalendarDays className="size-3" />
{formatDate(issue.due_date!)}
<span className={`inline-flex items-center gap-1 rounded px-1.5 py-0.5 text-xs font-medium ${priorityCfg.badgeBg} ${priorityCfg.badgeText}`}>
<PriorityIcon priority={issue.priority} className="h-3 w-3" inheritColor />
{priorityCfg.label}
</span>
))}
{showDueDate && (
<div className="ml-auto">
{editable ? (
<PickerWrapper>
<DueDatePicker
dueDate={issue.due_date}
onUpdate={handleUpdate}
trigger={
<span
className={`flex items-center gap-1 text-xs ${
new Date(issue.due_date!) < new Date()
? "text-destructive"
: "text-muted-foreground"
}`}
>
<CalendarDays className="size-3" />
{formatDate(issue.due_date!)}
</span>
}
/>
</PickerWrapper>
) : (
<span
className={`flex items-center gap-1 text-xs ${
new Date(issue.due_date!) < new Date()
? "text-destructive"
: "text-muted-foreground"
}`}
>
<CalendarDays className="size-3" />
{formatDate(issue.due_date!)}
</span>
)}
</div>
)}
</div>
)}
</div>

View file

@ -43,13 +43,15 @@ export function BoardColumn({
);
return (
<div className="flex w-[280px] shrink-0 flex-col rounded-xl bg-muted/40 p-2">
<div className={`flex w-[280px] shrink-0 flex-col rounded-xl ${cfg.columnBg} p-2`}>
<div className="mb-2 flex items-center justify-between px-1.5">
{/* Left: icon + label + count */}
{/* Left: status badge + count */}
<div className="flex items-center gap-2">
<StatusIcon status={status} className="h-3.5 w-3.5" />
<span className="text-sm font-medium">{cfg.label}</span>
<span className="flex h-5 min-w-5 items-center justify-center rounded-full bg-muted px-1.5 text-xs text-muted-foreground">
<span className={`inline-flex items-center gap-1.5 rounded px-2 py-0.5 text-xs font-semibold ${cfg.badgeBg} ${cfg.badgeText}`}>
<StatusIcon status={status} className="h-3 w-3" inheritColor />
{cfg.label}
</span>
<span className="text-xs text-muted-foreground">
{issues.length}
</span>
</div>

View file

@ -744,8 +744,10 @@ export function IssueDetail({ issueId, onDelete, defaultSidebarOpen = true, layo
key={p}
onClick={() => handleUpdateField({ priority: p })}
>
<PriorityIcon priority={p} />
{PRIORITY_CONFIG[p].label}
<span className={`inline-flex items-center gap-1 rounded px-1.5 py-0.5 text-xs font-medium ${PRIORITY_CONFIG[p].badgeBg} ${PRIORITY_CONFIG[p].badgeText}`}>
<PriorityIcon priority={p} className="h-3 w-3" inheritColor />
{PRIORITY_CONFIG[p].label}
</span>
{issue.priority === p && <span className="ml-auto text-xs text-muted-foreground"></span>}
</DropdownMenuItem>
))}
@ -1213,8 +1215,10 @@ export function IssueDetail({ issueId, onDelete, defaultSidebarOpen = true, layo
<DropdownMenuContent align="start" className="w-44">
{PRIORITY_ORDER.map((p) => (
<DropdownMenuItem key={p} onClick={() => handleUpdateField({ priority: p })}>
<PriorityIcon priority={p} />
{PRIORITY_CONFIG[p].label}
<span className={`inline-flex items-center gap-1 rounded px-1.5 py-0.5 text-xs font-medium ${PRIORITY_CONFIG[p].badgeBg} ${PRIORITY_CONFIG[p].badgeText}`}>
<PriorityIcon priority={p} className="h-3 w-3" inheritColor />
{PRIORITY_CONFIG[p].label}
</span>
{p === issue.priority && <Check className="ml-auto h-3.5 w-3.5" />}
</DropdownMenuItem>
))}

View file

@ -310,7 +310,6 @@ export function IssuesHeader() {
</span>
{/* New issue */}
<Button
variant="outline"
size="sm"
onClick={() => useModalStore.getState().open("create-issue")}
>

View file

@ -96,9 +96,11 @@ export function ListView({
</div>
<Accordion.Trigger className="group/trigger flex flex-1 items-center gap-2 px-2 h-full text-left outline-none">
<ChevronRight className="size-3.5 shrink-0 text-muted-foreground transition-transform group-aria-expanded/trigger:rotate-90" />
<StatusIcon status={status} className="h-3.5 w-3.5" />
<span className="text-sm font-medium">{cfg.label}</span>
<span className="flex h-5 min-w-5 items-center justify-center rounded-full bg-muted px-1.5 text-xs text-muted-foreground">
<span className={`inline-flex items-center gap-1.5 rounded px-2 py-0.5 text-xs font-semibold ${cfg.badgeBg} ${cfg.badgeText}`}>
<StatusIcon status={status} className="h-3 w-3" inheritColor />
{cfg.label}
</span>
<span className="text-xs text-muted-foreground">
{statusIssues.length}
</span>
</Accordion.Trigger>

View file

@ -43,8 +43,10 @@ export function PriorityPicker({
setOpen(false);
}}
>
<PriorityIcon priority={p} />
<span>{c.label}</span>
<span className={`inline-flex items-center gap-1 rounded px-1.5 py-0.5 text-xs font-medium ${c.badgeBg} ${c.badgeText}`}>
<PriorityIcon priority={p} className="h-3 w-3" inheritColor />
{c.label}
</span>
</PickerItem>
);
})}

View file

@ -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 (
<svg
viewBox="0 0 16 16"
className={`h-3.5 w-3.5 text-muted-foreground shrink-0 ${className}`}
className={`h-3.5 w-3.5 ${inheritColor ? "" : "text-muted-foreground"} shrink-0 ${className}`}
fill="none"
stroke="currentColor"
strokeWidth="1.5"
@ -31,7 +33,7 @@ export function PriorityIcon({
return (
<svg
viewBox="0 0 16 16"
className={`h-3.5 w-3.5 ${cfg.color} shrink-0 ${className}`}
className={`h-3.5 w-3.5 ${inheritColor ? "" : cfg.color} shrink-0 ${className}`}
fill="currentColor"
style={isUrgent ? { animation: "priority-pulse 2s ease-in-out infinite" } : undefined}
>

View file

@ -160,9 +160,11 @@ const STATUS_RENDERERS: Record<IssueStatus, () => 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({
<svg
viewBox="0 0 14 14"
fill="none"
className={`${className} ${cfg.iconColor} shrink-0`}
className={`${className} ${inheritColor ? "" : cfg.iconColor} shrink-0`}
>
<Renderer />
</svg>