Skip to content

Commit e99e37f

Browse files
authored
Merge pull request #1726 from Adez017/blog-alginment-issue
fix the cards on blog carousal
2 parents 5111961 + eb23674 commit e99e37f

2 files changed

Lines changed: 143 additions & 109 deletions

File tree

Lines changed: 136 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"use client";
22
import React from "react";
33
import Link from "@docusaurus/Link";
4-
import { getAuthorProfiles, getAuthorTooltip } from "../../utils/authors";
4+
import { getAuthorProfiles } from "../../utils/authors";
55

66
interface BlogCardProps {
77
type: string;
@@ -11,140 +11,172 @@ interface BlogCardProps {
1111
imageUrl: string;
1212
id: string;
1313
authors?: string[];
14+
tags?: string[];
15+
category?: string;
16+
}
17+
18+
const TAG_COLORS = [
19+
{ dot: "#f59e0b", border: "#fde68a", bg: "#fffbeb", text: "#92400e" },
20+
{ dot: "#6366f1", border: "#c7d2fe", bg: "#eef2ff", text: "#3730a3" },
21+
{ dot: "#ec4899", border: "#fbcfe8", bg: "#fdf2f8", text: "#9d174d" },
22+
{ dot: "#10b981", border: "#a7f3d0", bg: "#ecfdf5", text: "#065f46" },
23+
{ dot: "#3b82f6", border: "#bfdbfe", bg: "#eff6ff", text: "#1e40af" },
24+
{ dot: "#8b5cf6", border: "#ddd6fe", bg: "#f5f3ff", text: "#5b21b6" },
25+
{ dot: "#f97316", border: "#fed7aa", bg: "#fff7ed", text: "#9a3412" },
26+
{ dot: "#14b8a6", border: "#99f6e4", bg: "#f0fdfa", text: "#134e4a" },
27+
];
28+
29+
function tagColor(label: string) {
30+
let hash = 0;
31+
for (let i = 0; i < label.length; i++)
32+
hash = label.charCodeAt(i) + ((hash << 5) - hash);
33+
return TAG_COLORS[Math.abs(hash) % TAG_COLORS.length];
34+
}
35+
36+
function formatDate(dateStr?: string) {
37+
if (!dateStr) return "";
38+
const d = new Date(dateStr);
39+
if (isNaN(d.getTime())) return dateStr;
40+
return d.toLocaleDateString("en-US", {
41+
month: "short",
42+
day: "2-digit",
43+
year: "numeric",
44+
});
1445
}
1546

1647
const BlogCard = ({
1748
type,
49+
date,
1850
title,
19-
content,
2051
imageUrl,
2152
id,
2253
authors,
54+
tags,
55+
category,
2356
}: BlogCardProps) => {
2457
const authorProfiles = getAuthorProfiles(authors || []);
2558

2659
if (!id || !type) {
2760
return <div>data not fetched properly, imageId and entryId not found</div>;
2861
}
2962

30-
// Get category from title for demo purposes
31-
const getCategory = (title) => {
32-
if (
33-
title.toLowerCase().includes("design") ||
34-
title.toLowerCase().includes("ux")
35-
)
36-
return "Design";
37-
if (
38-
title.toLowerCase().includes("ai") ||
39-
title.toLowerCase().includes("deepmind")
40-
)
41-
return "AI & Tech";
42-
if (
43-
title.toLowerCase().includes("github") ||
44-
title.toLowerCase().includes("git")
45-
)
46-
return "Development";
47-
return "Resources";
48-
};
63+
// Tags: use tags array first, then category, skip generic "blog"
64+
const rawTags = Array.isArray(tags) && tags.length > 0
65+
? tags
66+
: category
67+
? [category]
68+
: [];
4969

50-
const category = getCategory(title);
70+
const tagList = rawTags.filter(
71+
(t) => t && t.toLowerCase() !== "blog" && t.toLowerCase() !== "post"
72+
);
5173

5274
return (
53-
<div className="relative h-full overflow-hidden transition-all duration-300">
54-
<div className="article-card h-full">
55-
{/* Category Badge */}
56-
<div className="card-category">{category}</div>
57-
58-
{/* Card Image */}
59-
<div className="card-image">
60-
<img src={imageUrl} alt={title} />
61-
</div>
75+
<div className="article-card">
76+
{/* Image */}
77+
<div className="card-image">
78+
<img src={imageUrl} alt={title} loading="lazy" />
79+
</div>
6280

63-
{/* Card Content */}
64-
<div className="card-content">
65-
<h3 className="card-title">
66-
<Link to={`/blog/${id}`} className="card-title-link">
67-
{title}
68-
</Link>
69-
</h3>
70-
<p className="card-description">{content}</p>
81+
{/* Content */}
82+
<div className="card-content">
83+
{/* Title */}
84+
<h3 className="card-title">
85+
<Link to={`/blog/${id}`} className="card-title-link">
86+
{title}
87+
</Link>
88+
</h3>
7189

72-
{/* Card Meta */}
73-
<div className="card-meta">
74-
<div className="card-author">
75-
{/* Stacked Author Avatars */}
76-
{authorProfiles.length > 0 &&
77-
(() => {
78-
const max = 3;
79-
const visible = authorProfiles.slice(0, max);
80-
const extra = Math.max(0, authorProfiles.length - max);
81-
return (
82-
<div className="author-stack" aria-hidden>
83-
{visible.map((a, i) => (
84-
<div
85-
key={a.id}
86-
className="author-stack-item"
87-
style={{ zIndex: max - i }}
88-
>
89-
{a.imageUrl ? (
90-
<img
91-
src={a.imageUrl}
92-
alt={a.name}
93-
className="author-stack-avatar"
94-
onError={(e) => {
95-
const target = e.currentTarget;
96-
target.style.display = "none";
97-
const fallback =
98-
target.nextElementSibling as HTMLElement | null;
99-
if (fallback) fallback.style.display = "flex";
100-
}}
101-
/>
102-
) : (
103-
<span className="author-stack-fallback">
104-
{a.name.charAt(0).toUpperCase()}
105-
</span>
106-
)}
107-
</div>
108-
))}
109-
{extra > 0 && (
110-
<div className="author-stack-more">+{extra}</div>
111-
)}
112-
</div>
113-
);
114-
})()}
90+
{/* Tag pills */}
91+
{tagList.length > 0 && (
92+
<div className="card-tags">
93+
{tagList.slice(0, 5).map((tag) => {
94+
const c = tagColor(tag);
95+
return (
96+
<span
97+
key={tag}
98+
className="card-tag"
99+
style={{
100+
"--tag-dot": c.dot,
101+
"--tag-border": c.border,
102+
"--tag-bg": c.bg,
103+
"--tag-text": c.text,
104+
} as React.CSSProperties}
105+
>
106+
<span className="card-tag-dot" />
107+
{tag}
108+
</span>
109+
);
110+
})}
111+
</div>
112+
)}
115113

116-
{/* Author Names */}
117-
<div className="author-name-group">
118-
{authorProfiles.map((author, authorIndex) => (
119-
<span key={author.id} className="author-item">
120-
{authorIndex > 0 && (
121-
<span className="author-separator">,</span>
122-
)}
123-
<Link
124-
href={author.githubUrl}
125-
className="author-name author-link"
126-
target="_blank"
127-
rel="noopener noreferrer"
128-
data-author-tooltip={getAuthorTooltip(author.id)}
129-
aria-label={`Open ${author.name} on GitHub`}
114+
{/* Footer: avatars + author names/date + Read → */}
115+
<div className="card-footer">
116+
<div className="card-author-row">
117+
{/* Avatar stack — shows all authors overlapped */}
118+
{authorProfiles.length > 0 && (
119+
<div className="card-avatar-stack">
120+
{authorProfiles.map((author, i) => (
121+
<div
122+
key={author.id || i}
123+
className="card-avatar"
124+
style={{ zIndex: authorProfiles.length - i }}
125+
>
126+
{author.imageUrl ? (
127+
<img
128+
src={author.imageUrl}
129+
alt={author.name}
130+
className="card-avatar-img"
131+
onError={(e) => {
132+
const t = e.currentTarget;
133+
t.style.display = "none";
134+
const fb = t.nextElementSibling as HTMLElement | null;
135+
if (fb) fb.style.display = "flex";
136+
}}
137+
/>
138+
) : null}
139+
<span
140+
className="card-avatar-fallback"
141+
style={{ display: author.imageUrl ? "none" : "flex" }}
130142
>
131-
{author.name}
132-
</Link>
133-
</span>
143+
{author.name.charAt(0).toUpperCase()}
144+
</span>
145+
</div>
134146
))}
135147
</div>
148+
)}
149+
150+
<div className="card-author-info">
151+
{/* All author names inline, separated by commas */}
152+
{authorProfiles.length > 0 && (
153+
<div className="card-author-names">
154+
{authorProfiles.map((author, i) => (
155+
<React.Fragment key={author.id || i}>
156+
{i > 0 && <span className="card-author-sep">, </span>}
157+
<Link
158+
href={author.githubUrl}
159+
className="card-author-handle"
160+
target="_blank"
161+
rel="noopener noreferrer"
162+
>
163+
@{author.id || author.name.toLowerCase().replace(/\s+/g, "")}
164+
</Link>
165+
</React.Fragment>
166+
))}
167+
</div>
168+
)}
169+
{date && <span className="card-date">{formatDate(date)}</span>}
136170
</div>
137-
<span className="card-read-time">5 min read</span>
138171
</div>
139172

140-
{/* Read More Button */}
141-
<Link to={`/blog/${id}`} className="card-read-more">
142-
Read Article →
173+
<Link to={`/blog/${id}`} className="card-read-link">
174+
Read →
143175
</Link>
144176
</div>
145177
</div>
146178
</div>
147179
);
148180
};
149181

150-
export default BlogCard;
182+
export default BlogCard;

src/components/blogCarousel/blogCarousel.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export function BlogCarousel() {
4040
<h2 className="text-2xl font-semibold text-gray-800 dark:text-gray-100">
4141
From the Blog
4242
</h2>
43-
<p className="text-sm text-gray-500 dark:text-gray-400 mt-1">
43+
<p className="text-sm text-black-500 dark:text-gray-400 mt-1">
4444
Latest articles from our contributors
4545
</p>
4646
</div>
@@ -74,13 +74,15 @@ export function BlogCarousel() {
7474
className="basis-full sm:basis-1/2 lg:basis-1/3 xl:basis-1/4"
7575
>
7676
<BlogCard
77-
type="blog"
78-
date="2024-01-01"
77+
type={blog.category}
78+
date={(blog as any).date}
7979
title={blog.title}
8080
content={blog.description}
8181
imageUrl={blog.image}
8282
id={blog.slug}
8383
authors={blog.authors}
84+
tags={blog.tags}
85+
category={blog.category}
8486
/>
8587
</CarouselItem>
8688
))}
@@ -98,8 +100,8 @@ export function BlogCarousel() {
98100
onClick={() => api?.scrollTo(index)}
99101
aria-label={`Go to slide ${index + 1}`}
100102
className={`h-2 rounded-full transition-all duration-300 ${current === index + 1
101-
? "w-5 bg-indigo-500"
102-
: "w-2 bg-gray-300 hover:bg-gray-400 dark:bg-gray-600 dark:hover:bg-gray-500"
103+
? "w-5 bg-indigo-500"
104+
: "w-2 bg-gray-300 hover:bg-gray-400 dark:bg-gray-600 dark:hover:bg-gray-500"
103105
}`}
104106
/>
105107
))}

0 commit comments

Comments
 (0)