Skip to content

Commit eec8e21

Browse files
authored
PR.25 feat/devicon-tags
feat: add devicon icons to tag badges
2 parents fcf4a3b + 5de552f commit eec8e21

37 files changed

Lines changed: 18101 additions & 1753 deletions

.claude/settings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@
7171
"WebFetch(domain:vercel.com)",
7272
"WebFetch(domain:vitest.dev)"
7373
],
74-
"deny": ["Read(**/.env)", "Read(**/.envrc)"],
74+
"deny": ["Read(**/.env)", "Read(**/.envrc)", "Read(x_docs/OLD/**)"],
7575
"ask": []
7676
},
7777
"enabledPlugins": {

app/(root)/page.tsx

Lines changed: 5 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,9 @@
1+
import { TagLink } from "@/components/tag-link";
2+
13
const HomePage = () => (
24
<>
3-
<h1 className="text-heading-2xl">Hello Root page with heading H1</h1>
4-
5-
{/* Demo: top overflow test */}
6-
<div className="mt-8 bg-primary/30 p-4">{"words ".repeat(50)}</div>
7-
8-
{/* Flexbox demo: grow vs flex-none */}
9-
<div className="my-8 flex gap-4 border-2 border-dashed border-primary p-4">
10-
{/* Fixed width - won't grow */}
11-
<div className="flex h-24 w-24 flex-none items-center justify-center rounded-lg border-2 border-dashed border-primary bg-sky-500/20 text-sm font-medium">
12-
flex-none
13-
</div>
14-
15-
{/* Grows to fill available space */}
16-
<div className="flex h-24 grow items-center justify-center rounded-lg border-2 border-dashed border-primary bg-indigo-500/30 text-sm font-medium">
17-
grow
18-
</div>
19-
20-
{/* Fixed width - won't grow */}
21-
<div className="flex h-24 w-24 flex-none items-center justify-center rounded-lg border-2 border-dashed border-primary bg-sky-500/20 text-sm font-medium">
22-
flex-none
23-
</div>
24-
</div>
25-
26-
{/* Demo: bottom overflow test */}
27-
<div className="bg-primary/30 p-4">{"words ".repeat(50)}</div>
5+
<h1 className="text-heading-2xl">All Questions</h1>
286

29-
{/* Question boxes for testing sticky sidebar scroll */}
307
<div className="mt-8 space-y-6">
318
{[1, 2, 3, 4].map((questionIndex) => (
329
<article
@@ -56,13 +33,8 @@ const HomePage = () => (
5633

5734
{/* Tags */}
5835
<div className="mb-4 flex flex-wrap gap-2">
59-
{["next.js", "tailwindcss", "react", "css"].map((tag) => (
60-
<span
61-
key={tag}
62-
className="rounded-md bg-secondary px-2.5 py-1 text-xs font-medium text-secondary-foreground"
63-
>
64-
{tag}
65-
</span>
36+
{["tailwind", "nextjs"].map((tag) => (
37+
<TagLink key={tag} name={tag} />
6638
))}
6739
</div>
6840

app/layout.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@ export default function RootLayout({
2727
className={`${inter.variable} ${spaceGrotesk.variable} ${jetbrainsMono.variable} h-full`}
2828
suppressHydrationWarning
2929
>
30+
<head>
31+
<link
32+
rel="stylesheet"
33+
href="https://cdn.jsdelivr.net/gh/devicons/devicon@latest/devicon.min.css"
34+
/>
35+
</head>
3036
<body className="flex min-h-full flex-col antialiased">
3137
<ThemeProvider
3238
attribute="class"

components/right-sidebar/right-sidebar.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { QuestionLink } from "@/components/right-sidebar/question-link";
2-
import { TagLink } from "@/components/right-sidebar/tag-link";
2+
import { TagLink } from "@/components/tag-link";
33
import {
44
Sidebar,
55
SidebarContent,
@@ -59,6 +59,8 @@ export async function RightSidebar() {
5959
key={tag.name}
6060
name={tag.name}
6161
questionCount={tag.questions}
62+
showIcon
63+
colored
6264
/>
6365
))}
6466
</div>

components/right-sidebar/tag-link.tsx

Lines changed: 0 additions & 23 deletions
This file was deleted.

components/tag-link.tsx

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { Tag } from "lucide-react";
2+
import Link from "next/link";
3+
import { Badge } from "@/components/ui/badge";
4+
import { getDeviconClassName } from "@/lib/devicon";
5+
6+
type TagLinkProps = {
7+
name: string;
8+
questionCount?: number;
9+
colored?: boolean;
10+
showIcon?: boolean;
11+
};
12+
13+
export function TagLink({
14+
name,
15+
questionCount,
16+
colored = false,
17+
showIcon = false,
18+
}: TagLinkProps) {
19+
const iconClass = getDeviconClassName(name, colored);
20+
21+
return (
22+
<Link
23+
href={`/tags/${name}`}
24+
className="group flex items-center justify-between gap-2 rounded-md p-1 transition-transform duration-150 hover:-translate-y-0.5 hover:scale-[1.005]"
25+
>
26+
<Badge className="flex items-center gap-1.5 rounded-md border-transparent bg-(--tag-bg) px-4 py-1.5 uppercase text-(--tag-text)">
27+
{showIcon &&
28+
(iconClass ? (
29+
<i className={iconClass} aria-hidden="true" />
30+
) : (
31+
<Tag className="size-3.5" aria-hidden="true" />
32+
))}
33+
{name}
34+
</Badge>
35+
{questionCount !== undefined && (
36+
<span className="text-xs text-muted-foreground">{questionCount}</span>
37+
)}
38+
</Link>
39+
);
40+
}

lib/data/tags.test.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,11 @@ describe("getPopularTags", () => {
1616
expect(tags[0]).toHaveProperty("name");
1717
expect(tags[0]).toHaveProperty("questions");
1818
});
19+
20+
it("returns tags sorted by question count (descending)", async () => {
21+
const tags = await getPopularTags(5);
22+
for (let i = 0; i < tags.length - 1; i++) {
23+
expect(tags[i].questions).toBeGreaterThanOrEqual(tags[i + 1].questions);
24+
}
25+
});
1926
});

lib/data/tags.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,14 @@ export type Tag = {
66
const MOCK_TAGS: Tag[] = [
77
{ name: "nextjs", questions: 320 },
88
{ name: "reactjs", questions: 244 },
9+
{ name: "typescript", questions: 203 },
10+
{ name: "css", questions: 156 },
11+
{ name: "tailwind", questions: 94 },
12+
{ name: "postgres", questions: 89 },
913
{ name: "javascript", questions: 83 },
14+
{ name: "nodejs", questions: 67 },
15+
{ name: "html", questions: 45 },
1016
{ name: "react", questions: 30 },
11-
{ name: "postgres", questions: 89 },
1217
];
1318

1419
/**

0 commit comments

Comments
 (0)