From 124ebbf32c3fc509036e98b69ee5baa375cd0a31 Mon Sep 17 00:00:00 2001 From: Louis Knight-Webb Date: Mon, 17 Nov 2025 15:57:54 +0000 Subject: [PATCH] Kanban card user icon should be in title (vibe-kanban) (#1303) * Done! The kanban card layout is now restructured with **Title + Avatar + Actions** in the header row, and the description spans the full card width below. * Done! The avatar is now inline at the start of the title text with: - Sized down to `h-5 w-5` (from default `h-6 w-6`) to align better with the `text-sm` title - Inline-block with vertical alignment - Title text gets full remaining width without squishing - Actions stay on the right * Done! Changed to `inline-flex` so the avatar initials are properly centered while staying inline with the title text. * Done! Created [TaskCardHeader](file:///private/var/folders/m1/9q_ct1913z10v6wbnv54j25r0000gn/T/vibe-kanban/worktrees/665f-kanban-card-user/frontend/src/components/tasks/TaskCardHeader.tsx) and updated both cards: **Changes:** - **TaskCardHeader**: Shared component with inline avatar + optional actions slot - **TaskCard**: Now uses TaskCardHeader with status indicators passed via `right` prop - **SharedTaskCard**: Normalized to use TaskCardHeader with TaskCard styles (no more muted text, consistent spacing/sizing) Both cards now share the same header pattern with inline avatars. * invert header avatar colour --- .../src/components/tasks/SharedTaskCard.tsx | 33 ++++++----- frontend/src/components/tasks/TaskCard.tsx | 57 ++++++++----------- .../src/components/tasks/TaskCardHeader.tsx | 47 +++++++++++++++ frontend/src/components/tasks/UserAvatar.tsx | 2 +- 4 files changed, 89 insertions(+), 50 deletions(-) create mode 100644 frontend/src/components/tasks/TaskCardHeader.tsx diff --git a/frontend/src/components/tasks/SharedTaskCard.tsx b/frontend/src/components/tasks/SharedTaskCard.tsx index 21363302..4dd89d56 100644 --- a/frontend/src/components/tasks/SharedTaskCard.tsx +++ b/frontend/src/components/tasks/SharedTaskCard.tsx @@ -1,7 +1,7 @@ import { useCallback, useEffect, useRef } from 'react'; import { KanbanCard } from '@/components/ui/shadcn-io/kanban'; import type { SharedTaskRecord } from '@/hooks/useProjectTasks'; -import { UserAvatar } from './UserAvatar'; +import { TaskCardHeader } from './TaskCardHeader'; interface SharedTaskCardProps { task: SharedTaskRecord; @@ -48,23 +48,22 @@ export function SharedTaskCard({ dragDisabled className="relative overflow-hidden pl-5 before:absolute before:left-0 before:top-0 before:bottom-0 before:w-[3px] before:bg-muted-foreground before:content-['']" > -
- + -
-

- {task.title} -

- {task.description && ( -

- {task.description} -

- )} -
+ {task.description && ( +

+ {task.description.length > 130 + ? `${task.description.substring(0, 130)}...` + : task.description} +

+ )}
); diff --git a/frontend/src/components/tasks/TaskCard.tsx b/frontend/src/components/tasks/TaskCard.tsx index 0577c3a6..618f8b37 100644 --- a/frontend/src/components/tasks/TaskCard.tsx +++ b/frontend/src/components/tasks/TaskCard.tsx @@ -8,7 +8,7 @@ import { useNavigateWithSearch } from '@/hooks'; import { paths } from '@/lib/paths'; import { attemptsApi } from '@/lib/api'; import type { SharedTaskRecord } from '@/hooks/useProjectTasks'; -import { UserAvatar } from './UserAvatar'; +import { TaskCardHeader } from './TaskCardHeader'; import { useTranslation } from 'react-i18next'; type Task = TaskWithAttemptStatus; @@ -93,35 +93,29 @@ export function TaskCard({ : undefined } > -
- {sharedTask ? ( - - ) : null} -
-
-

- {task.title} -

-
- {/* In Progress Spinner */} +
+ {task.has_in_progress_attempt && ( )} - {/* Merged Indicator */} {task.has_merged_attempt && ( )} - {/* Failed Indicator */} {task.last_attempt_failed && !task.has_merged_attempt && ( )} - {/* Parent Task Indicator */} {task.parent_task_attempt && ( )} - {/* Actions Menu */} -
-
- {task.description && ( -

- {task.description.length > 130 - ? `${task.description.substring(0, 130)}...` - : task.description} -

- )} -
+ + } + /> + {task.description && ( +

+ {task.description.length > 130 + ? `${task.description.substring(0, 130)}...` + : task.description} +

+ )}
); diff --git a/frontend/src/components/tasks/TaskCardHeader.tsx b/frontend/src/components/tasks/TaskCardHeader.tsx new file mode 100644 index 00000000..d7b70435 --- /dev/null +++ b/frontend/src/components/tasks/TaskCardHeader.tsx @@ -0,0 +1,47 @@ +import type { ReactNode } from 'react'; +import { UserAvatar } from './UserAvatar'; + +interface HeaderAvatar { + firstName?: string; + lastName?: string; + username?: string; + imageUrl?: string; +} + +interface TaskCardHeaderProps { + title: ReactNode; + avatar?: HeaderAvatar; + right?: ReactNode; + className?: string; + titleClassName?: string; +} + +export function TaskCardHeader({ + title, + avatar, + right, + className, + titleClassName, +}: TaskCardHeaderProps) { + return ( +
+

+ {avatar ? ( + + ) : null} + {title} +

+ {right ? ( +
{right}
+ ) : null} +
+ ); +} diff --git a/frontend/src/components/tasks/UserAvatar.tsx b/frontend/src/components/tasks/UserAvatar.tsx index b6290fd7..98097a6e 100644 --- a/frontend/src/components/tasks/UserAvatar.tsx +++ b/frontend/src/components/tasks/UserAvatar.tsx @@ -99,7 +99,7 @@ export const UserAvatar = ({ return (