Skip to content

Commit 65c0d00

Browse files
authored
Generate documentation (#6)
* cleanup from base-stack * sidebar initial working version * functional table of content component * small change in the knip.json * custom script to validate content folder positions inside frontmatter, and some fixes in sidebar, toc and ocumentation page * updated readme.md file * updated readme.md file * small refactoring and added documnetation for some components * footer component * initial theme switcher, changes on UI, additional components * fonts, some fixes * small change * convention xx-file-name.mdx update - still updates needed * refactoring * small changes and updated package.json * ts fix? * ts fix? * ts fix? * ts fix? * ts fix? * sidebar fix * added content folder and some fixes and improvements in UI * removed _index route, added index.mdx file for hopemage, reorganized routes.ts * refactoring * small update in update-frontmatter logic * small refactoring * small ui improvements * small ui improvements * refactoring * refactoring * theme toggle fix * refactoring ad vitest tests for some helper functions * small refactoring * fix in breadcrumbs building * fix in breadcrumbs building * unit tests and small improvements * comments and tests * initial changes * small fixes * fixed so it doesnt contains v1.0.1 now * fixes * small update * small fixes * small change * dropdown versions and fixed build script * check-with no verify * small fix in sidebar * working version * for now in url for documentation page it will be shown every verion, even last one * dropdown fix * load content collections fix * sorting tags in versions.ts * refactoring * improvements * update * updated docs.build.ts * updates with docs.build.ts * small update * updates * changes * updates * small changes * initial * small fix in breadcrumbs tests * small update * refactoring * refactoring * small fix * small fix * update so llms.txt is dedicated per version * small update * removed DEFAULT_BRANCH env from the yml and passed using cli args in the generate:docs command * updated generate-docs script * small fix * fix for versions - fixed naming of the files and functions * small fix in naming
1 parent 3e5c7e6 commit 65c0d00

39 files changed

+1964
-1536
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
name: 📚🚀 Build documentation on release
2+
3+
on:
4+
release:
5+
types: [published]
6+
workflow_dispatch: {}
7+
8+
concurrency:
9+
group: docs-build-${{ github.workflow }}-${{ github.ref }}
10+
cancel-in-progress: true
11+
12+
jobs:
13+
build-docs:
14+
runs-on: ubuntu-latest
15+
steps:
16+
- name: Checkout
17+
uses: actions/checkout@v4
18+
with:
19+
fetch-depth: 0
20+
- name: Setup pnpm
21+
uses: pnpm/action-setup@v4
22+
- name: Setup Node
23+
uses: actions/setup-node@v4
24+
with:
25+
node-version-file: "package.json"
26+
cache: "pnpm"
27+
28+
- name: Install deps
29+
run: pnpm install --prefer-offline --frozen-lockfile
30+
31+
- name: Generate docs
32+
working-directory: docs
33+
run: pnpm run generate:docs

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,6 @@ blob-report
8484

8585
# Content collections output files
8686
.content-collections
87+
88+
# Output base directory of the documentation
89+
generated-docs/

README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ The project is built using the [@forge-42/base-stack](https://github.com/forge-4
1111

1212
This folder contains React Router v7 web application folders and files, including components and UI primitives for the documentation site’s interface, internal hooks and utilities, and the tailwind.css file for styling.
1313

14-
1514
`resources/`
1615

1716
This folder contains all the resources used by the documentation site, such as SVG icons, fonts, and other assets.
@@ -97,8 +96,10 @@ pnpm install
9796

9897
4. Add `content` folder
9998

100-
5. Start the development server:
99+
5. Run `pnpm run generate:docs` script
100+
101+
6. Start the development server:
101102
```bash
102103
pnpm run dev
103104
```
104-
5. Happy coding!
105+
7. Happy coding!

app/components/sidebar/build-breadcrumbs.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { href } from "react-router"
2-
import { splitSlug } from "~/utils/split-slug"
2+
import { splitSlugAndAppendVersion } from "~/utils/split-slug-and-append-version"
33
import type { SidebarSection } from "./sidebar"
44

55
// builds a breadcrumb trail from sidebar sections based on the current pathname
@@ -8,7 +8,7 @@ export const buildBreadcrumb = (items: SidebarSection[], pathname: string) => {
88

99
const walk = (section: SidebarSection, acc: string[]) => {
1010
for (const doc of section.documentationPages) {
11-
const docPath = href("/:version/:section/:subsection?/:filename", splitSlug(doc.slug))
11+
const docPath = href("/:version/:section/:subsection?/:filename", splitSlugAndAppendVersion(doc.slug))
1212
if (docPath === pathname) {
1313
trail = [...acc, section.title, doc.title]
1414
return true

app/components/sidebar/sidebar-section.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { NavLink, href } from "react-router"
22
import { AccordionItem } from "~/ui/accordion"
3-
import { splitSlug } from "~/utils/split-slug"
3+
import { splitSlugAndAppendVersion } from "~/utils/split-slug-and-append-version"
44
import type { SidebarSection } from "./sidebar"
55

66
const getIndentClass = (depth: number) => {
@@ -30,11 +30,10 @@ const SectionTitle = ({ title }: { title: string }) => {
3030

3131
const SectionItemLink = ({ documentPage, depth, onItemClick }: SectionItemLinkProps) => {
3232
const indentClass = getIndentClass(depth)
33-
3433
return (
3534
<NavLink
3635
prefetch="intent"
37-
to={href("/:version/:section/:subsection?/:filename", splitSlug(documentPage.slug))}
36+
to={href("/:version/:section/:subsection?/:filename", splitSlugAndAppendVersion(documentPage.slug))}
3837
onClick={onItemClick}
3938
className={({ isActive, isPending }) =>
4039
`block rounded-md px-3 py-2 text-xs sm:text-sm md:text-base ${indentClass}

app/components/sidebar/sidebar.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import { MobileSidebarProvider } from "./mobile-sidebar-context"
88
export type SidebarSection = {
99
title: string
1010
slug: string
11-
sectionId: string
1211
subsections: SidebarSection[]
1312
documentationPages: {
1413
title: string
Lines changed: 46 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,94 +1,80 @@
1-
import { buildBreadcrumb } from "../build-breadcrumbs"
1+
import { describe, expect, it, vi } from "vitest"
22
import type { SidebarSection } from "../sidebar"
33

4+
vi.mock("~/utils/split-slug-and-append-version", () => ({
5+
splitSlugAndAppendVersion: (slug: string) => {
6+
const parts = slug.split("/").filter(Boolean)
7+
const version = "v1.0.0"
8+
9+
if (parts.length === 2) {
10+
const [section, filename] = parts
11+
return { version, section, filename }
12+
}
13+
if (parts.length === 3) {
14+
const [section, subsection, filename] = parts
15+
return { version, section, subsection, filename }
16+
}
17+
18+
throw new Error(`Bad slug in test: ${slug}`)
19+
},
20+
}))
21+
22+
import { buildBreadcrumb } from "../build-breadcrumbs"
23+
424
type Doc = { slug: string; title: string }
5-
const sec = (over: Partial<SidebarSection>) => ({
25+
const makeDoc = (slug: string, title: string): Doc => ({ slug, title })
26+
27+
type MinimalSection = Pick<SidebarSection, "title" | "slug" | "documentationPages" | "subsections">
28+
const makeSection = (overrides: Partial<MinimalSection> = {}) => ({
629
title: "",
730
slug: "",
8-
sectionId: "",
931
documentationPages: [],
1032
subsections: [],
11-
...over,
33+
...overrides,
1234
})
1335

14-
const doc = (slug: string, title: string): Doc => ({ slug, title })
15-
16-
describe("buildBreadcrumb test suite", () => {
36+
describe("buildBreadcrumb (versioned paths via splitSlugAndAppendVersion)", () => {
1737
it("returns [] when pathname doesn't match any doc", () => {
1838
const items = [
19-
sec({
39+
makeSection({
2040
title: "Getting Started",
21-
slug: "v1/started",
22-
documentationPages: [doc("v1/started/intro", "Intro")],
41+
slug: "getting-started",
42+
documentationPages: [makeDoc("getting-started/intro", "Intro")],
2343
}),
2444
]
25-
26-
// biome-ignore lint/suspicious/noExplicitAny: we can use any in tests
27-
const result = buildBreadcrumb(items as any, "/v1/started/unknown")
28-
expect(result).toEqual([])
45+
expect(buildBreadcrumb(items, "/v1.0.0/getting-started/unknown")).toEqual([])
2946
})
3047

3148
it("returns [section, doc] for a top-level doc", () => {
3249
const items = [
33-
sec({
50+
makeSection({
3451
title: "Getting Started",
35-
slug: "v1/started",
36-
documentationPages: [doc("v1/started/intro", "Intro")],
52+
slug: "getting-started",
53+
documentationPages: [makeDoc("getting-started/intro", "Intro")],
3754
}),
3855
]
39-
40-
// biome-ignore lint/suspicious/noExplicitAny: we can use any in tests
41-
const result = buildBreadcrumb(items as any, "/v1/started/intro")
42-
expect(result).toEqual(["Getting Started", "Intro"])
56+
expect(buildBreadcrumb(items, "/v1.0.0/getting-started/intro")).toEqual(["Getting Started", "Intro"])
4357
})
4458

4559
it("returns full trail for a nested doc (root → sub → doc)", () => {
4660
const items = [
47-
sec({
61+
makeSection({
4862
title: "Configuration",
49-
slug: "v1/configuration",
63+
slug: "configuration",
5064
subsections: [
51-
sec({
65+
makeSection({
5266
title: "Advanced",
53-
slug: "v1/configuration/advanced",
54-
documentationPages: [doc("v1/configuration/advanced/tuning", "Tuning")],
55-
}),
56-
],
57-
documentationPages: [doc("v1/configuration/setup", "Setup")],
58-
}),
59-
]
60-
61-
// biome-ignore lint/suspicious/noExplicitAny: we can use any in tests
62-
const result = buildBreadcrumb(items as any, "/v1/configuration/advanced/tuning")
63-
expect(result).toEqual(["Configuration", "Advanced", "Tuning"])
64-
})
65-
66-
it("stops at the first matching branch across multiple roots", () => {
67-
const items = [
68-
sec({
69-
title: "Alpha",
70-
slug: "v1/alpha",
71-
documentationPages: [doc("v1/alpha/readme", "Readme")],
72-
}),
73-
sec({
74-
title: "Beta",
75-
slug: "v1/beta",
76-
subsections: [
77-
sec({
78-
title: "Deep",
79-
slug: "v1/beta/deep",
80-
documentationPages: [doc("v1/beta/deep/file", "File")],
67+
slug: "configuration/advanced",
68+
documentationPages: [makeDoc("configuration/advanced/tuning", "Tuning")],
8169
}),
8270
],
71+
documentationPages: [makeDoc("configuration/setup", "Setup")],
8372
}),
8473
]
85-
86-
// biome-ignore lint/suspicious/noExplicitAny: we can use any in tests
87-
const result1 = buildBreadcrumb(items as any, "/v1/alpha/readme")
88-
expect(result1).toEqual(["Alpha", "Readme"])
89-
90-
// biome-ignore lint/suspicious/noExplicitAny: we can use any in tests
91-
const result2 = buildBreadcrumb(items as any, "/v1/beta/deep/file")
92-
expect(result2).toEqual(["Beta", "Deep", "File"])
74+
expect(buildBreadcrumb(items, "/v1.0.0/configuration/advanced/tuning")).toEqual([
75+
"Configuration",
76+
"Advanced",
77+
"Tuning",
78+
])
9379
})
9480
})
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { useState } from "react"
2+
import { useNavigate } from "react-router"
3+
import { Icon } from "~/ui/icon/icon"
4+
import { getCurrentVersion, homepageUrlWithVersion, isKnownVersion } from "~/utils/version-resolvers"
5+
import { versions } from "~/utils/versions"
6+
7+
export function VersionDropdown() {
8+
const navigate = useNavigate()
9+
const { version: currentVersion } = getCurrentVersion()
10+
const [selectedVersion, setSelectedVersion] = useState(currentVersion)
11+
12+
function onChange(e: React.ChangeEvent<HTMLSelectElement>) {
13+
const next = e.target.value
14+
if (next === currentVersion) return
15+
16+
setSelectedVersion(isKnownVersion(next) ? next : currentVersion)
17+
18+
const to = homepageUrlWithVersion(next)
19+
const nav = () => {
20+
navigate(to)
21+
e.target.blur()
22+
}
23+
if (document.startViewTransition) document.startViewTransition(nav)
24+
else nav()
25+
}
26+
27+
return (
28+
<div className="relative inline-block text-[var(--color-text-normal)]">
29+
<select
30+
id="version"
31+
name="version"
32+
className="cursor-pointer appearance-none rounded-lg border border-[var(--color-border)] bg-[var(--color-background)] py-2.5 pr-10 pl-4 font-medium text-sm shadow-sm transition-all duration-200 hover:bg-[var(--color-border)] focus:border-transparent focus:bg-[var(--color-border)] focus:outline-none focus:ring-none"
33+
value={selectedVersion}
34+
onChange={onChange}
35+
aria-label="Select documentation version"
36+
>
37+
{versions.map((v) => (
38+
<option
39+
key={v}
40+
value={v}
41+
className={`bg-[var(--color-background)] text-[var(--color-text-normal)] hover:bg-[var(--color-background-hover)]${selectedVersion === v ? "font-semibold text-[var(--color-text-active)]" : ""}
42+
`}
43+
>
44+
{v}
45+
</option>
46+
))}
47+
</select>
48+
49+
<Icon
50+
name="ChevronDown"
51+
className="-translate-y-1/2 pointer-events-none absolute top-1/2 right-3 h-4 w-4 text-[var(--color-text-muted)] transition-colors duration-200"
52+
/>
53+
</div>
54+
)
55+
}

app/hooks/use-previous-next-pages.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { href, useLocation } from "react-router"
22
import type { SidebarSection } from "~/components/sidebar/sidebar"
33
import { flattenSidebarItems } from "~/utils/flatten-sidebar"
4-
import { splitSlug } from "~/utils/split-slug"
4+
import { splitSlugAndAppendVersion } from "~/utils/split-slug-and-append-version"
55

66
export function usePreviousNextPages(sections: SidebarSection[]) {
77
const { pathname } = useLocation()
@@ -15,7 +15,7 @@ export function usePreviousNextPages(sections: SidebarSection[]) {
1515

1616
return {
1717
title: item.title,
18-
to: href("/:version/:section/:subsection?/:filename", splitSlug(item.slug)),
18+
to: href("/:version/:section/:subsection?/:filename", splitSlugAndAppendVersion(item.slug)),
1919
}
2020
}
2121

app/routes.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
import { type RouteConfig, index, layout, route } from "@react-router/dev/routes"
22

33
export default [
4+
index("routes/index.tsx"),
45
layout("routes/documentation-layout.tsx", [
5-
index("routes/documentation-homepage.tsx"),
6+
route(":version?/home", "routes/documentation-homepage.tsx"),
67
route(":version/:section/:subsection?/:filename", "routes/documentation-page.tsx"),
78
]),
89
route("sitemap-index.xml", "routes/sitemap-index[.]xml.ts"),
910
route("robots.txt", "routes/robots[.]txt.ts"),
1011
route("resource/*", "routes/resource.locales.ts"),
1112
route("$", "routes/$.tsx"),
1213
route("sitemap/:lang.xml", "routes/sitemap.$lang[.]xml.ts"),
14+
route(":version?/llms.txt", "routes/llms[.]txt.ts"),
1315
] satisfies RouteConfig

0 commit comments

Comments
 (0)