Skip to content

Commit 1d8c845

Browse files
just-be-devclaude
andauthored
Add tag descriptions with hover tooltips (#229)
* Add tag descriptions with hover tooltips Introduce a centralized tag registry (src/content/tags.yaml) with descriptions for all 13 content tags. Add a new tags content collection with custom YAML loader. Enhance the Tag component to display descriptions in a tooltip when hovering over tags. Update blog pages to load and pass tag descriptions to the Tag component. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com> * Simplify tag tooltip to use title attribute Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>
1 parent 1c058dd commit 1d8c845

6 files changed

Lines changed: 94 additions & 3 deletions

File tree

src/components/Tag.astro

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,17 @@ interface Props {
55
count?: number;
66
url?: string;
77
class?: string;
8+
description?: string;
89
}
910
10-
const { id, tag, count, url = "/blog", class: className } = Astro.props;
11+
const { id, tag, count, url = "/blog", class: className, description } = Astro.props;
1112
const tagUrl = `${url}?tag=${encodeURIComponent(tag)}`;
1213
---
1314

1415
<a
1516
id={id}
1617
href={tagUrl}
18+
title={description}
1719
class:list={[
1820
"bg-2 text-accent hocus:bg-0 hocus:invert group flex items-center outline-none font-medium border-2 border-fg-0",
1921
className,

src/content.config.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import {
1313
urlsSchema,
1414
redirectsSchema,
1515
redirectsFileSchema,
16+
tagEntrySchema,
17+
tagsFileSchema,
1618
} from "./content/schemas";
1719

1820
const blog = defineCollection({
@@ -103,6 +105,30 @@ const redirects = defineCollection({
103105
schema: redirectsSchema,
104106
});
105107

108+
const tags = defineCollection({
109+
loader: {
110+
name: "tags-loader",
111+
load: ({ store, logger }) => {
112+
logger.info("Loading tags");
113+
114+
const tagsPath = join(process.cwd(), "src/content/tags.yaml");
115+
const tagsContent = readFileSync(tagsPath, "utf-8");
116+
const rawData = loadYAML(tagsContent);
117+
const data = tagsFileSchema.parse(rawData);
118+
119+
for (const tag of data.tags) {
120+
store.set({
121+
id: tag.name,
122+
data: { description: tag.description },
123+
});
124+
}
125+
126+
logger.info(`Loaded ${data.tags.length} tags`);
127+
},
128+
},
129+
schema: tagEntrySchema,
130+
});
131+
106132
export const collections = {
107133
blog,
108134
projects,
@@ -112,4 +138,5 @@ export const collections = {
112138
talks,
113139
urls,
114140
redirects,
141+
tags,
115142
};

src/content/schemas.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,3 +90,17 @@ export const redirectsFileSchema = z.object({
9090
}),
9191
),
9292
});
93+
94+
export const tagEntrySchema = z.object({
95+
description: z.string(),
96+
});
97+
98+
// Schema for the tags YAML file structure
99+
export const tagsFileSchema = z.object({
100+
tags: z.array(
101+
z.object({
102+
name: z.string(),
103+
description: z.string(),
104+
}),
105+
),
106+
});

src/content/tags.yaml

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
tags:
2+
- name: ai
3+
description: Artificial intelligence, large language models, and machine learning
4+
5+
- name: api
6+
description: API design, standards, and integration patterns
7+
8+
- name: claude
9+
description: Anthropic's Claude AI assistant and related tooling
10+
11+
- name: data
12+
description: Data management, storage, and processing
13+
14+
- name: infrastructure
15+
description: Backend systems, deployment, and platform engineering
16+
17+
- name: interoperability
18+
description: Standards and protocols for system-to-system communication
19+
20+
- name: mcp
21+
description: Model Context Protocol, Anthropic's open protocol for AI tool integration
22+
23+
- name: meta
24+
description: Posts about this site, writing, or process
25+
26+
- name: privacy
27+
description: Data privacy, self-sovereignty, and personal data control
28+
29+
- name: recurse
30+
description: Recurse Center experiences and community
31+
32+
- name: schema
33+
description: Data schemas, type systems, and validation
34+
35+
- name: self-hosting
36+
description: Running your own services and infrastructure
37+
38+
- name: web
39+
description: Web development, browsers, and the open web

src/pages/blog/[slug].astro

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ import MDContent from "@/components/MDContent.astro";
99
import Layout from "@/layouts/Layout.astro";
1010
import { Code } from "@/utils/code";
1111
12+
const tagEntries = await getCollection("tags");
13+
const tagDescriptions = new Map(tagEntries.map((t) => [t.id, t.data.description]));
14+
1215
export async function getStaticPaths() {
1316
const posts = await getCollection("blog");
1417
return posts.map((post) => ({
@@ -52,7 +55,7 @@ const code = post.data.code ? post.data.code.toUpperCase() : Code.fromId(post.id
5255
<div class="tag-list hidden absolute right-0 top-full pt-1 z-10">
5356
<div class="flex flex-wrap gap-2 bg-1 px-2 py-1">
5457
{post.data.tags.map((tag) => (
55-
<Tag tag={tag} url="/blog" />
58+
<Tag tag={tag} url="/blog" description={tagDescriptions.get(tag)} />
5659
))}
5760
</div>
5861
</div>

src/pages/blog/index.astro

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ import type { LinkListItem } from "@/components/LinkList.astro";
77
import Layout from "@/layouts/Layout.astro";
88
import { Code } from "@/utils/code";
99
10+
const tagEntries = await getCollection("tags");
11+
const tagDescriptions = new Map(tagEntries.map((t) => [t.id, t.data.description]));
12+
1013
export const prerender = false;
1114
1215
// Get selected tag from query params
@@ -64,11 +67,14 @@ const linkListItems: LinkListItem[] = posts.map((post) => {
6467
url="/blog"
6568
class="invert"
6669
id="selected-tag"
70+
description={tagDescriptions.get(selectedTag)}
6771
/>
6872
<Button shortcut="c" label="clear" href="/blog" />
6973
</>
7074
) : (
71-
sortedTags.map(({ tag, count }) => <Tag tag={tag} count={count} url="/blog" />)
75+
sortedTags.map(({ tag, count }) => (
76+
<Tag tag={tag} count={count} url="/blog" description={tagDescriptions.get(tag)} />
77+
))
7278
)
7379
}
7480
</div>

0 commit comments

Comments
 (0)