Skip to content

Commit d033a47

Browse files
committed
[Docs Site] Create TutorialsGrid component
1 parent 585ff20 commit d033a47

File tree

4 files changed

+199
-2
lines changed

4 files changed

+199
-2
lines changed

src/components/TutorialsGrid.astro

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
---
2+
import { z } from "astro:schema";
3+
import {
4+
getCollection,
5+
getEntry,
6+
reference,
7+
type CollectionEntry,
8+
} from "astro:content";
9+
import type { ComponentProps } from "astro/types";
10+
import { Badge } from "@astrojs/starlight/components";
11+
12+
type Props = z.infer<typeof props>;
13+
type Badge = ComponentProps<typeof Badge>;
14+
15+
const props = z.object({
16+
product: reference("products").optional(),
17+
frontmatterBadges: z.string().array().optional(),
18+
});
19+
20+
let { product, frontmatterBadges } = props.parse(Astro.props);
21+
22+
type DocsEntry = CollectionEntry<"docs">;
23+
type VideoEntry = CollectionEntry<"videos">;
24+
25+
const tutorials: Array<DocsEntry | VideoEntry> = [];
26+
27+
if (!product) {
28+
product = {
29+
id: Astro.url.pathname.split("/").filter(Boolean)[0],
30+
collection: "products",
31+
};
32+
}
33+
34+
const productEntry = await getEntry(product);
35+
36+
if (!productEntry) {
37+
throw new Error(
38+
`[ListTutorials] Unable to find product YAML for ${product.id}.`,
39+
);
40+
}
41+
42+
const productTitle = productEntry.data.product.title;
43+
44+
const docs = await getCollection("docs", (entry) => {
45+
return (
46+
// pcx_content_type: tutorial
47+
entry.data.pcx_content_type === "tutorial" &&
48+
// /src/content/r2/**/*.mdx
49+
(entry.id.startsWith(`${productEntry.id}/`) ||
50+
// products: [R2]
51+
entry.data.products?.some(
52+
(v: string) => v.toUpperCase() === productTitle.toUpperCase(),
53+
))
54+
);
55+
});
56+
57+
tutorials.push(...docs);
58+
59+
const videos = await getCollection("videos", (entry) => {
60+
return entry.data.products.some(
61+
(v: string) => v.toUpperCase() === productTitle.toUpperCase(),
62+
);
63+
});
64+
65+
tutorials.push(...videos);
66+
67+
tutorials.sort((a, b) => Number(b.data.updated) - Number(a.data.updated));
68+
69+
const difficulties = {
70+
Beginner: "success",
71+
Intermediate: "caution",
72+
Advanced: "danger",
73+
} as Record<string, ComponentProps<typeof Badge>["variant"]>;
74+
---
75+
76+
<div class="grid grid-cols-2 gap-4">
77+
{
78+
tutorials.map((tutorial) => {
79+
const title =
80+
tutorial.collection === "docs" ? tutorial.data.title : tutorial.id;
81+
82+
const href =
83+
tutorial.collection === "docs"
84+
? `/${tutorial.id}/`
85+
: tutorial.data.link;
86+
87+
const difficulty = tutorial.data.difficulty;
88+
89+
if (!difficulty) {
90+
throw new Error(`${tutorial.id} is missing difficulty property`);
91+
}
92+
93+
const video = tutorial.collection === "videos";
94+
95+
const spotlight =
96+
tutorial.collection === "docs" && tutorial.data.spotlight;
97+
98+
const badges: Badge[] = [
99+
{
100+
text: difficulty,
101+
variant: difficulties[difficulty],
102+
},
103+
];
104+
105+
if (video) {
106+
badges.push({
107+
text: "Video",
108+
variant: "note",
109+
});
110+
}
111+
112+
if (spotlight) {
113+
badges.push({
114+
text: "Spotlight",
115+
variant: "tip",
116+
});
117+
}
118+
119+
if (frontmatterBadges) {
120+
for (const frontmatterBadge of frontmatterBadges) {
121+
const values =
122+
tutorial.data[frontmatterBadge as keyof typeof tutorial.data];
123+
124+
if (values) {
125+
if (Array.isArray(values)) {
126+
for (const value of values) {
127+
badges.push({ text: value.toString() });
128+
}
129+
} else {
130+
badges.push({ text: values.toString() });
131+
}
132+
}
133+
}
134+
}
135+
136+
return (
137+
<a
138+
href={href}
139+
class="flex flex-col rounded border border-cl1-gray-8 p-6 !text-black no-underline hover:bg-cl1-gray-9 dark:border-cl1-gray-2 dark:bg-cl1-gray-0 dark:hover:bg-cl1-gray-1"
140+
>
141+
<strong>{title}</strong>
142+
<p class="!mt-auto">
143+
{badges.map((badge) => (
144+
<Badge {...badge} />
145+
))}
146+
</p>
147+
</a>
148+
);
149+
})
150+
}
151+
</div>

src/components/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ export { default as SpotlightAuthorDetails } from "./SpotlightAuthorDetails.astr
5252
export { default as Stream } from "./Stream.astro";
5353
export { default as TroubleshootingList } from "./TroubleshootingList.astro";
5454
export { default as TunnelCalculator } from "./TunnelCalculator.astro";
55+
export { default as TutorialsGrid } from "./TutorialsGrid.astro";
5556
export { default as Type } from "./Type.astro";
5657
export { default as TypeScriptExample } from "./TypeScriptExample.astro";
5758
export { default as WranglerConfig } from "./WranglerConfig.astro";
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
---
2+
title: Tutorials grid
3+
---
4+
5+
This component fetches all pages with `pcx_content_type: tutorial` under a given product.
6+
7+
## Import
8+
9+
```mdx
10+
import { TutorialsGrid } from "~/components";
11+
```
12+
13+
## Usage
14+
15+
```mdx live
16+
import { TutorialsGrid } from "~/components";
17+
18+
<TutorialsGrid product="workers" frontmatterBadges={["languages"]} />
19+
```
20+
21+
## `<TutorialsGrid>` Props
22+
23+
### `product`
24+
25+
**type:** `string`
26+
27+
The product to fetch tutorials for, based on the file location and the `products` frontmatter property.
28+
29+
This property is optional, and will be inferred from the page it is used on if omitted.
30+
31+
### `frontmatterBadges`
32+
33+
**type:** `string[]`
34+
35+
Frontmatter values to add badges from.
36+
37+
For example, given the below frontmatter, the badges `foo` and `bar` would be added when `frontmatterBadges={["stuff"]}` is provided.
38+
39+
```mdx
40+
---
41+
title: Example Title
42+
stuff:
43+
- foo
44+
- bar
45+
```

src/content/docs/workers/tutorials/index.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@ sidebar:
77
order: 4
88
---
99

10-
import { GlossaryTooltip, ListTutorials } from "~/components";
10+
import { GlossaryTooltip, TutorialsGrid } from "~/components";
1111

1212
:::note
1313
[Explore our community-written tutorials contributed through the Developer Spotlight program.](/developer-spotlight/)
1414
:::
1515

1616
View <GlossaryTooltip term="tutorial">tutorials</GlossaryTooltip> to help you get started with Workers.
1717

18-
<ListTutorials />
18+
<TutorialsGrid frontmatterBadges={["languages"]} />

0 commit comments

Comments
 (0)