Skip to content

Commit 7da0e1a

Browse files
authored
docs: add documentation pages for ex_machina, bamboo, jsonapi, and scrivener_headers (#45)
* feat: add @tailwindcss/typography for prose styles Adds the typography plugin to support rendered markdown content in the upcoming docs pages. * feat: add docs content collection config and stub content Defines an Astro content collection with glob loader for library documentation. Includes stub markdown files with frontmatter for ex_machina, bamboo, jsonapi, and scrivener_headers. * feat: add docs layout, sidebar, and supporting components Adds DocsLayout with solid nav bar, sidebar navigation, and prose content wrapper. Includes HexDocsLink CTA component and documented projects config in constants. Updates types with DocumentedProject interface and adds "Docs" to nav links. * feat: add docs page routes and projects index Adds dynamic route at /projects/[project]/[...slug] that renders content collection entries. Includes /projects index page listing all documented libraries with cards linking to their docs. * feat: link project cards to internal docs for documented libraries ProjectCard now routes to /projects/{name} for libraries that have documentation on the site, while keeping external GitHub links for projects without docs. * docs: add bamboo overview page * docs: add bamboo getting started guide * docs: add bamboo adapters guide * docs: add bamboo testing guide * docs: add bamboo 2.0 upgrade guide * docs: add bamboo cheatsheet * docs: add ex_machina overview page * docs: add ex_machina getting started guide * docs: add ex_machina associations guide * docs: add ex_machina custom strategies guide * docs: add ex_machina cheatsheet * docs: add jsonapi overview page * docs: add jsonapi getting started guide * docs: add jsonapi serialization guide * docs: add jsonapi pagination guide * docs: add jsonapi cheatsheet * docs: add scrivener_headers overview page * docs: add scrivener_headers getting started guide * docs: add scrivener_headers cheatsheet * feat: add MDX support for richer documentation content Install @astrojs/mdx integration and update content collection glob pattern to support both .md and .mdx files. * feat: add breadcrumb navigation to docs pages Add DocsBreadcrumb.astro component showing Home > Docs > Project > Page trail. Integrates into DocsLayout with proper index page detection. * feat: add previous/next page navigation to docs Add DocsNavigation.astro component with prev/next links at the bottom of each docs page. Respects section ordering (overview > guides > resources) and the order field within each section. * feat: add "Edit this page on GitHub" links to docs Add edit link below docs content that points to the source markdown file on the main branch of the beam-community.org repository. * feat: add mobile-responsive collapsible sidebar for docs Add a slide-out sidebar panel for mobile viewports with toggle button, backdrop overlay, and close interactions. Desktop sidebar remains inline.
1 parent 43520eb commit 7da0e1a

35 files changed

+4376
-5
lines changed

astro.config.mjs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import { defineConfig } from "astro/config";
22
import tailwindcss from "@tailwindcss/vite";
33
import icon from "astro-icon";
4+
import mdx from "@astrojs/mdx";
45

56
export default defineConfig({
67
site: "https://beam-community.org",
7-
integrations: [icon()],
8+
integrations: [icon(), mdx()],
89
vite: {
910
plugins: [tailwindcss()],
1011
},

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
"lint:strict": "eslint --max-warnings=0 . --ext .js,.ts,.astro"
1313
},
1414
"dependencies": {
15+
"@astrojs/mdx": "^4.3.13",
1516
"@fontsource/inter": "^5.1.1",
1617
"astro": "^5.3.0",
1718
"astro-icon": "^1.1.5"
@@ -20,6 +21,7 @@
2021
"@eslint/js": "^9.22.0",
2122
"@iconify-json/lucide": "^1.2.34",
2223
"@iconify-json/simple-icons": "^1.2.30",
24+
"@tailwindcss/typography": "^0.5.19",
2325
"@tailwindcss/vite": "^4.0.0",
2426
"eslint": "^9.22.0",
2527
"eslint-plugin-astro": "^1.3.1",

pnpm-lock.yaml

Lines changed: 527 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
---
2+
import { Icon } from "astro-icon/components";
3+
4+
interface Props {
5+
projectName: string;
6+
projectSlug: string;
7+
currentTitle: string;
8+
isIndex?: boolean;
9+
}
10+
11+
const { projectName, projectSlug, currentTitle, isIndex = false } = Astro.props;
12+
---
13+
14+
<nav aria-label="Breadcrumb" class="mb-6">
15+
<ol class="flex flex-wrap items-center gap-1 text-sm text-slate-500">
16+
<li>
17+
<a href="/" class="transition-colors hover:text-slate-900">Home</a>
18+
</li>
19+
<li>
20+
<Icon name="lucide:chevron-right" class="h-3.5 w-3.5 text-slate-400" />
21+
</li>
22+
<li>
23+
<a href="/projects" class="transition-colors hover:text-slate-900">Docs</a>
24+
</li>
25+
<li>
26+
<Icon name="lucide:chevron-right" class="h-3.5 w-3.5 text-slate-400" />
27+
</li>
28+
{isIndex ? (
29+
<li>
30+
<span class="font-medium text-slate-900">{projectName}</span>
31+
</li>
32+
) : (
33+
<>
34+
<li>
35+
<a
36+
href={`/projects/${projectSlug}`}
37+
class="transition-colors hover:text-slate-900"
38+
>
39+
{projectName}
40+
</a>
41+
</li>
42+
<li>
43+
<Icon name="lucide:chevron-right" class="h-3.5 w-3.5 text-slate-400" />
44+
</li>
45+
<li>
46+
<span class="font-medium text-slate-900">{currentTitle}</span>
47+
</li>
48+
</>
49+
)}
50+
</ol>
51+
</nav>

src/components/DocsContent.astro

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
3+
---
4+
5+
<article class="prose prose-slate max-w-none prose-headings:font-semibold prose-a:text-purple-700 prose-code:text-purple-700 prose-pre:bg-slate-900">
6+
<slot />
7+
</article>
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
---
2+
import { getCollection } from "astro:content";
3+
import { Icon } from "astro-icon/components";
4+
import type { DocumentedProject } from "../lib/types";
5+
6+
interface Props {
7+
project: DocumentedProject;
8+
currentPath: string;
9+
}
10+
11+
const SECTION_ORDER = ["overview", "guides", "resources"] as const;
12+
13+
const { project, currentPath } = Astro.props;
14+
15+
const allDocs = await getCollection("docs", (entry) => {
16+
return entry.data.project === project.slug;
17+
});
18+
19+
const ordered = allDocs.sort((a, b) => {
20+
const sectionDiff =
21+
SECTION_ORDER.indexOf(a.data.section) - SECTION_ORDER.indexOf(b.data.section);
22+
if (sectionDiff !== 0) return sectionDiff;
23+
return a.data.order - b.data.order;
24+
});
25+
26+
function getDocPath(entry: (typeof allDocs)[number]): string {
27+
const slug = entry.id.replace(`${project.slug}/`, "").replace(project.slug, "");
28+
if (!slug || slug === "index") {
29+
return `/projects/${project.slug}`;
30+
}
31+
return `/projects/${project.slug}/${slug}`;
32+
}
33+
34+
const currentIndex = ordered.findIndex((e) => getDocPath(e) === currentPath);
35+
const prev = currentIndex > 0 ? ordered[currentIndex - 1] : null;
36+
const next = currentIndex < ordered.length - 1 ? ordered[currentIndex + 1] : null;
37+
---
38+
39+
{(prev || next) && (
40+
<nav aria-label="Page navigation" class="mt-12 flex items-stretch gap-4 border-t border-slate-200 pt-6">
41+
{prev ? (
42+
<a
43+
href={getDocPath(prev)}
44+
class="group flex flex-1 items-center gap-3 rounded-lg border border-slate-200 px-4 py-3 transition-colors hover:border-purple-300 hover:bg-purple-50"
45+
>
46+
<Icon
47+
name="lucide:arrow-left"
48+
class="h-4 w-4 shrink-0 text-slate-400 transition-colors group-hover:text-purple-600"
49+
/>
50+
<div class="min-w-0">
51+
<div class="text-xs text-slate-400">Previous</div>
52+
<div class="truncate text-sm font-medium text-slate-700 group-hover:text-purple-700">
53+
{prev.data.title}
54+
</div>
55+
</div>
56+
</a>
57+
) : (
58+
<div class="flex-1" />
59+
)}
60+
{next ? (
61+
<a
62+
href={getDocPath(next)}
63+
class="group flex flex-1 items-center justify-end gap-3 rounded-lg border border-slate-200 px-4 py-3 text-right transition-colors hover:border-purple-300 hover:bg-purple-50"
64+
>
65+
<div class="min-w-0">
66+
<div class="text-xs text-slate-400">Next</div>
67+
<div class="truncate text-sm font-medium text-slate-700 group-hover:text-purple-700">
68+
{next.data.title}
69+
</div>
70+
</div>
71+
<Icon
72+
name="lucide:arrow-right"
73+
class="h-4 w-4 shrink-0 text-slate-400 transition-colors group-hover:text-purple-600"
74+
/>
75+
</a>
76+
) : (
77+
<div class="flex-1" />
78+
)}
79+
</nav>
80+
)}

src/components/DocsSidebar.astro

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
---
2+
import { getCollection } from "astro:content";
3+
import { Icon } from "astro-icon/components";
4+
import type { DocumentedProject } from "../lib/types";
5+
6+
interface Props {
7+
project: DocumentedProject;
8+
currentPath: string;
9+
}
10+
11+
const { project, currentPath } = Astro.props;
12+
13+
const allDocs = await getCollection("docs", (entry) => {
14+
return entry.data.project === project.slug;
15+
});
16+
17+
const sorted = allDocs.sort((a, b) => a.data.order - b.data.order);
18+
19+
const sections = [
20+
{ key: "overview" as const, label: "Overview" },
21+
{ key: "guides" as const, label: "Guides" },
22+
{ key: "resources" as const, label: "Resources" },
23+
];
24+
25+
function getDocPath(entry: (typeof allDocs)[number]): string {
26+
// The glob loader generates ids like "ex_machina" for index.md
27+
// and "ex_machina/getting-started" for other files
28+
const slug = entry.id.replace(`${project.slug}/`, "").replace(project.slug, "");
29+
if (!slug || slug === "index") {
30+
return `/projects/${project.slug}`;
31+
}
32+
return `/projects/${project.slug}/${slug}`;
33+
}
34+
---
35+
36+
<aside class="w-64 shrink-0">
37+
<nav class="sticky top-20 space-y-6">
38+
<div>
39+
<a
40+
href={`/projects/${project.slug}`}
41+
class="text-lg font-semibold text-slate-900 hover:text-purple-700 transition-colors"
42+
>
43+
{project.name}
44+
</a>
45+
</div>
46+
47+
{sections.map((section) => {
48+
const entries = sorted.filter((e) => e.data.section === section.key);
49+
if (entries.length === 0) return null;
50+
return (
51+
<div>
52+
<h3 class="mb-2 text-xs font-semibold uppercase tracking-wider text-slate-400">
53+
{section.label}
54+
</h3>
55+
<ul class="space-y-1">
56+
{entries.map((entry) => {
57+
const href = getDocPath(entry);
58+
const isActive = currentPath === href;
59+
return (
60+
<li>
61+
<a
62+
href={href}
63+
class:list={[
64+
"block rounded-md px-3 py-1.5 text-sm transition-colors",
65+
isActive
66+
? "bg-purple-50 font-medium text-purple-700"
67+
: "text-slate-600 hover:bg-slate-50 hover:text-slate-900",
68+
]}
69+
>
70+
{entry.data.title}
71+
</a>
72+
</li>
73+
);
74+
})}
75+
</ul>
76+
</div>
77+
);
78+
})}
79+
80+
<hr class="border-slate-200" />
81+
82+
<div class="space-y-2">
83+
<a
84+
href={project.hexdocsUrl}
85+
target="_blank"
86+
rel="noopener noreferrer"
87+
class="flex items-center gap-2 text-sm text-slate-500 hover:text-purple-700 transition-colors"
88+
>
89+
<Icon name="lucide:book-open" class="h-4 w-4" />
90+
API Reference
91+
<Icon name="lucide:external-link" class="h-3 w-3" />
92+
</a>
93+
<a
94+
href={project.githubUrl}
95+
target="_blank"
96+
rel="noopener noreferrer"
97+
class="flex items-center gap-2 text-sm text-slate-500 hover:text-purple-700 transition-colors"
98+
>
99+
<Icon name="simple-icons:github" class="h-4 w-4" />
100+
GitHub
101+
<Icon name="lucide:external-link" class="h-3 w-3" />
102+
</a>
103+
<a
104+
href={project.hexUrl}
105+
target="_blank"
106+
rel="noopener noreferrer"
107+
class="flex items-center gap-2 text-sm text-slate-500 hover:text-purple-700 transition-colors"
108+
>
109+
<Icon name="simple-icons:elixir" class="h-4 w-4" />
110+
Hex.pm
111+
<Icon name="lucide:external-link" class="h-3 w-3" />
112+
</a>
113+
</div>
114+
</nav>
115+
</aside>

src/components/HexDocsLink.astro

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
import { Icon } from "astro-icon/components";
3+
4+
interface Props {
5+
url: string;
6+
name: string;
7+
}
8+
9+
const { url, name } = Astro.props;
10+
---
11+
12+
<a
13+
href={url}
14+
target="_blank"
15+
rel="noopener noreferrer"
16+
class="inline-flex items-center gap-2 rounded-lg border border-purple-200 bg-purple-50 px-4 py-3 text-sm font-medium text-purple-700 transition-colors hover:bg-purple-100"
17+
>
18+
<Icon name="lucide:book-open" class="h-4 w-4" />
19+
{name} API Reference on HexDocs
20+
<Icon name="lucide:external-link" class="h-3.5 w-3.5" />
21+
</a>

src/components/ProjectCard.astro

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
---
22
import type { Project } from "../lib/types";
3+
import { DOCUMENTED_PROJECT_SLUGS } from "../lib/constants";
34
import { Icon } from "astro-icon/components";
45
import Badge from "./Badge.astro";
56
@@ -8,12 +9,14 @@ interface Props {
89
}
910
1011
const { project } = Astro.props;
12+
const hasDocs = DOCUMENTED_PROJECT_SLUGS.has(project.name);
13+
const href = hasDocs ? `/projects/${project.name}` : project.url;
14+
const isExternal = !hasDocs;
1115
---
1216

1317
<a
14-
href={project.url}
15-
target="_blank"
16-
rel="noopener noreferrer"
18+
href={href}
19+
{...isExternal ? { target: "_blank", rel: "noopener noreferrer" } : {}}
1720
class:list={[
1821
"group block rounded-xl border bg-white p-6 transition-all hover:shadow-lg hover:-translate-y-0.5",
1922
project.isFeatured ? "border-purple-200 ring-1 ring-purple-100" : "border-slate-200",

src/content.config.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { defineCollection, z } from "astro:content";
2+
import { glob } from "astro/loaders";
3+
4+
const docs = defineCollection({
5+
loader: glob({ pattern: "**/*.{md,mdx}", base: "./src/data/docs" }),
6+
schema: z.object({
7+
title: z.string(),
8+
description: z.string().optional(),
9+
project: z.string(),
10+
order: z.number().default(0),
11+
section: z.enum(["overview", "guides", "resources"]).default("guides"),
12+
}),
13+
});
14+
15+
export const collections = { docs };

0 commit comments

Comments
 (0)