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:
parent
32e19f847f
commit
8a61c94b98
14 changed files with 140 additions and 116 deletions
|
|
@ -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>
|
||||
);
|
||||
})}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -310,7 +310,6 @@ export function IssuesHeader() {
|
|||
</span>
|
||||
{/* New issue */}
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => useModalStore.getState().open("create-issue")}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
})}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue