Skip to content

Commit e27ff62

Browse files
authored
Merge pull request #5 from btrustteam/fix/contribution-table-flex-layout
fix: replace table layout with flex divs in ContributionTable
2 parents 4b84af2 + 0be6a8e commit e27ff62

2 files changed

Lines changed: 99 additions & 78 deletions

File tree

src/components/ContributionTable.tsx

Lines changed: 73 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -21,87 +21,82 @@ export function ContributionTable({ items }: ContributionTableProps) {
2121
}
2222

2323
return (
24-
<div className="hidden md:block overflow-x-auto">
25-
<table className="w-full text-sm">
26-
<thead>
27-
<tr className="border-b text-left text-zinc-500 dark:text-zinc-400">
28-
<th className="w-8 pb-2" />
29-
<th className="pb-2 font-medium">Title</th>
30-
<th className="pb-2 font-medium">Repo</th>
31-
<th className="pb-2 font-medium">Status</th>
32-
<th className="pb-2 font-medium">Date</th>
33-
<th className="w-8 pb-2" />
34-
</tr>
35-
</thead>
36-
<tbody>
37-
{items.map((item) => {
38-
const isExpanded = expandedId === item.id;
39-
const canExpand = item.type === "pr";
40-
const [owner, repo] = item.repoNameWithOwner.split("/");
24+
<div className="hidden md:block text-sm">
25+
{/* Header row */}
26+
<div className="flex items-center gap-3 border-b px-2 pb-2 font-medium text-zinc-500 dark:text-zinc-400">
27+
<div className="w-4 flex-shrink-0" />
28+
<div className="min-w-0 flex-1">Title</div>
29+
<div className="w-40 flex-shrink-0">Repo</div>
30+
<div className="w-16 flex-shrink-0">Status</div>
31+
<div className="w-28 flex-shrink-0">Date</div>
32+
<div className="w-4 flex-shrink-0" />
33+
</div>
4134

42-
return (
43-
<tr key={item.id} className="group">
44-
<td colSpan={6} className="p-0">
45-
<div
35+
{/* Data rows */}
36+
{items.map((item) => {
37+
const isExpanded = expandedId === item.id;
38+
const canExpand = item.type === "pr";
39+
const [owner, repo] = item.repoNameWithOwner.split("/");
40+
41+
return (
42+
<div key={item.id}>
43+
<div
44+
className={cn(
45+
"flex items-center gap-3 border-b px-2 py-3 transition-colors",
46+
canExpand && "cursor-pointer hover:bg-zinc-50 dark:hover:bg-zinc-900",
47+
)}
48+
onClick={() => {
49+
if (canExpand) {
50+
setExpandedId(isExpanded ? null : item.id);
51+
}
52+
}}
53+
role={canExpand ? "button" : undefined}
54+
aria-expanded={canExpand ? isExpanded : undefined}
55+
>
56+
<div className="w-4 flex-shrink-0">
57+
{canExpand && (
58+
<ChevronRight
4659
className={cn(
47-
"flex items-center gap-3 border-b px-2 py-3 transition-colors",
48-
canExpand && "cursor-pointer hover:bg-zinc-50 dark:hover:bg-zinc-900",
60+
"h-4 w-4 text-zinc-400 transition-transform",
61+
isExpanded && "rotate-90"
4962
)}
50-
onClick={() => {
51-
if (canExpand) {
52-
setExpandedId(isExpanded ? null : item.id);
53-
}
54-
}}
55-
role={canExpand ? "button" : undefined}
56-
aria-expanded={canExpand ? isExpanded : undefined}
57-
>
58-
<div className="w-4 flex-shrink-0">
59-
{canExpand && (
60-
<ChevronRight
61-
className={cn(
62-
"h-4 w-4 text-zinc-400 transition-transform",
63-
isExpanded && "rotate-90"
64-
)}
65-
/>
66-
)}
67-
</div>
68-
<div className="min-w-0 flex-1">
69-
<span className="truncate">{item.title}</span>
70-
</div>
71-
<div className="hidden sm:block w-40 flex-shrink-0 truncate text-zinc-500 dark:text-zinc-400">
72-
{item.repoNameWithOwner}
73-
</div>
74-
<div className="flex-shrink-0">
75-
<Badge
76-
className={stateColors[item.state]}
77-
variant="outline"
78-
>
79-
{item.state}
80-
</Badge>
81-
</div>
82-
<div className="hidden sm:block w-28 flex-shrink-0 text-zinc-500 dark:text-zinc-400">
83-
{formatDate(item.createdAt)}
84-
</div>
85-
<a
86-
href={item.url}
87-
target="_blank"
88-
rel="noopener noreferrer"
89-
onClick={(e) => e.stopPropagation()}
90-
className="flex-shrink-0 text-zinc-400 hover:text-zinc-700 dark:hover:text-zinc-200"
91-
aria-label={`Open #${item.number} on GitHub`}
92-
>
93-
<ExternalLink className="h-4 w-4" />
94-
</a>
95-
</div>
96-
{isExpanded && canExpand && (
97-
<ExpandedPRDetail owner={owner} repo={repo} number={item.number} />
98-
)}
99-
</td>
100-
</tr>
101-
);
102-
})}
103-
</tbody>
104-
</table>
63+
/>
64+
)}
65+
</div>
66+
<div className="min-w-0 flex-1">
67+
<span className="block truncate">{item.title}</span>
68+
</div>
69+
<div className="w-40 flex-shrink-0 truncate text-zinc-500 dark:text-zinc-400">
70+
{item.repoNameWithOwner}
71+
</div>
72+
<div className="w-16 flex-shrink-0">
73+
<Badge
74+
className={stateColors[item.state]}
75+
variant="outline"
76+
>
77+
{item.state}
78+
</Badge>
79+
</div>
80+
<div className="w-28 flex-shrink-0 text-zinc-500 dark:text-zinc-400">
81+
{formatDate(item.createdAt)}
82+
</div>
83+
<a
84+
href={item.url}
85+
target="_blank"
86+
rel="noopener noreferrer"
87+
onClick={(e) => e.stopPropagation()}
88+
className="flex-shrink-0 text-zinc-400 hover:text-zinc-700 dark:hover:text-zinc-200"
89+
aria-label={`Open #${item.number} on GitHub`}
90+
>
91+
<ExternalLink className="h-4 w-4" />
92+
</a>
93+
</div>
94+
{isExpanded && canExpand && (
95+
<ExpandedPRDetail owner={owner} repo={repo} number={item.number} />
96+
)}
97+
</div>
98+
);
99+
})}
105100
</div>
106101
);
107102
}

src/components/__tests__/ContributionTable.test.tsx

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,32 @@ describe("ContributionTable", () => {
7575
expect(issueRow).not.toHaveAttribute("role", "button");
7676
});
7777

78+
it("renders column headers", () => {
79+
render(<ContributionTable items={items} />);
80+
81+
expect(screen.getByText("Title")).toBeInTheDocument();
82+
expect(screen.getByText("Repo")).toBeInTheDocument();
83+
expect(screen.getByText("Status")).toBeInTheDocument();
84+
expect(screen.getByText("Date")).toBeInTheDocument();
85+
});
86+
87+
it("renders repo name and date for each row", () => {
88+
render(<ContributionTable items={items} />);
89+
90+
expect(screen.getAllByText("bitcoin/bitcoin")).toHaveLength(2);
91+
});
92+
93+
it("collapses expanded PR when clicked again", () => {
94+
render(<ContributionTable items={items} />);
95+
96+
const row = screen.getByText("Fix consensus bug").closest("[role='button']");
97+
fireEvent.click(row!);
98+
expect(screen.getByTestId("pr-detail")).toBeInTheDocument();
99+
100+
fireEvent.click(row!);
101+
expect(screen.queryByTestId("pr-detail")).not.toBeInTheDocument();
102+
});
103+
78104
it("has external links to GitHub", () => {
79105
render(<ContributionTable items={items} />);
80106

0 commit comments

Comments
 (0)