Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
82 commits
Select commit Hold shift + click to select a range
f7c81bf
cleanup from base-stack
abrulic Jul 14, 2025
6e8dfcf
sidebar initial working version
abrulic Jul 14, 2025
58084d7
functional table of content component
abrulic Jul 19, 2025
dfbeff7
small change in the knip.json
abrulic Jul 19, 2025
8dede72
custom script to validate content folder positions inside frontmatter…
abrulic Jul 20, 2025
8d06e3e
updated readme.md file
abrulic Jul 20, 2025
d0ab191
updated readme.md file
abrulic Jul 20, 2025
03dcf30
small refactoring and added documnetation for some components
abrulic Jul 20, 2025
6d23125
footer component
abrulic Jul 20, 2025
9fb1e8c
initial theme switcher, changes on UI, additional components
abrulic Jul 22, 2025
b8d8d87
fonts, some fixes
abrulic Jul 23, 2025
4b4bf90
small change
abrulic Jul 23, 2025
67d4cb7
convention xx-file-name.mdx update - still updates needed
abrulic Jul 24, 2025
0fd0ec2
refactoring
abrulic Jul 24, 2025
3029d09
small changes and updated package.json
abrulic Jul 25, 2025
d7286cc
ts fix?
abrulic Jul 25, 2025
c9d553c
ts fix?
abrulic Jul 25, 2025
2c04dc6
ts fix?
abrulic Jul 25, 2025
e70c006
ts fix?
abrulic Jul 25, 2025
06780d2
ts fix?
abrulic Jul 25, 2025
de6cfdd
sidebar fix
abrulic Jul 27, 2025
eb3f746
added content folder and some fixes and improvements in UI
abrulic Jul 28, 2025
0e29737
removed _index route, added index.mdx file for hopemage, reorganized …
abrulic Jul 29, 2025
d6b73ed
refactoring
abrulic Aug 1, 2025
892c14c
small update in update-frontmatter logic
abrulic Aug 1, 2025
7d2842e
small refactoring
abrulic Aug 3, 2025
5eaed66
small ui improvements
abrulic Aug 3, 2025
1d3e062
small ui improvements
abrulic Aug 3, 2025
82a39a9
refactoring
abrulic Aug 4, 2025
a4e605c
refactoring
abrulic Aug 7, 2025
c960702
theme toggle fix
abrulic Aug 7, 2025
02369ea
refactoring ad vitest tests for some helper functions
abrulic Aug 8, 2025
6e2bfde
small refactoring
abrulic Aug 8, 2025
d17c3be
fix in breadcrumbs building
abrulic Aug 8, 2025
8f460c3
fix in breadcrumbs building
abrulic Aug 8, 2025
45158bb
unit tests and small improvements
abrulic Aug 10, 2025
9e5044c
comments and tests
abrulic Aug 10, 2025
6191bc9
initial changes
abrulic Aug 12, 2025
7d53709
small fixes
abrulic Aug 12, 2025
05f4738
fixed so it doesnt contains v1.0.1 now
abrulic Aug 13, 2025
5045de0
fixes
abrulic Aug 13, 2025
b9f2941
small update
abrulic Aug 13, 2025
ffc1fb7
small fixes
abrulic Aug 13, 2025
22ce1df
small change
abrulic Aug 13, 2025
d472088
Merge branch 'initial-setup-and-layout' into generate-documentation
abrulic Aug 14, 2025
fe80fa0
dropdown versions and fixed build script
abrulic Aug 17, 2025
536f9c7
check-with no verify
abrulic Aug 17, 2025
45bab85
small fix in sidebar
abrulic Aug 19, 2025
fbbf8c2
working version
abrulic Aug 20, 2025
87b24cc
for now in url for documentation page it will be shown every verion, …
abrulic Aug 20, 2025
f395428
dropdown fix
abrulic Aug 20, 2025
fc1c8fb
load content collections fix
abrulic Aug 20, 2025
d967db8
sorting tags in versions.ts
abrulic Aug 20, 2025
679a95a
refactoring
abrulic Aug 20, 2025
f3a6b68
improvements
abrulic Aug 21, 2025
43ce350
Merge branch 'initial-setup-and-layout' into generate-documentation
abrulic Aug 21, 2025
53b94af
update
abrulic Aug 21, 2025
57f2ab2
Merge branch 'main' into generate-documentation
abrulic Aug 22, 2025
bca9ef6
updated docs.build.ts
abrulic Aug 22, 2025
737337d
updates with docs.build.ts
abrulic Aug 22, 2025
ae7c793
small update
abrulic Aug 22, 2025
2eac1d9
updates
abrulic Aug 26, 2025
09954a2
changes
abrulic Aug 28, 2025
5b85ad1
updates
abrulic Sep 1, 2025
826779a
small changes
abrulic Sep 1, 2025
bde6b6a
initial
abrulic Sep 1, 2025
1610336
small fix in breadcrumbs tests
abrulic Sep 1, 2025
98aa2e6
small update
abrulic Sep 1, 2025
b880f2b
Merge branch 'generate-documentation' into llms-txt
abrulic Sep 1, 2025
efedf80
refactoring
abrulic Sep 1, 2025
1d76ae5
refactoring
abrulic Sep 1, 2025
d63f628
small fix
abrulic Sep 1, 2025
5933ef3
small fix
abrulic Sep 1, 2025
fe42cc6
update so llms.txt is dedicated per version
abrulic Sep 1, 2025
11d33af
small update
abrulic Sep 1, 2025
9ab0f76
removed DEFAULT_BRANCH env from the yml and passed using cli args in …
abrulic Sep 1, 2025
58dd43e
updated generate-docs script
abrulic Sep 2, 2025
3d207ea
small fix
abrulic Sep 2, 2025
1f392ac
Merge branch 'generate-documentation' into llms-txt
abrulic Sep 2, 2025
786fd98
fix for versions - fixed naming of the files and functions
abrulic Sep 3, 2025
fd58db3
small fix in naming
abrulic Sep 3, 2025
b06fb7b
Merge pull request #8 from forge-42/llms-txt
abrulic Sep 3, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions .github/workflows/build-documentation.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: 📚🚀 Build documentation on release

on:
release:
types: [published]
workflow_dispatch: {}

concurrency:
group: docs-build-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
build-docs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup pnpm
uses: pnpm/action-setup@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version-file: "package.json"
cache: "pnpm"

- name: Install deps
run: pnpm install --prefer-offline --frozen-lockfile

- name: Generate docs
working-directory: docs
run: pnpm run generate:docs
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,6 @@ blob-report

# Content collections output files
.content-collections

# Output base directory of the documentation
generated-docs/
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ The project is built using the [@forge-42/base-stack](https://github.com/forge-4

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.


`resources/`

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

4. Add `content` folder

5. Start the development server:
5. Run `pnpm run generate:docs` script

6. Start the development server:
```bash
pnpm run dev
```
5. Happy coding!
7. Happy coding!
4 changes: 2 additions & 2 deletions app/components/sidebar/build-breadcrumbs.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { href } from "react-router"
import { splitSlug } from "~/utils/split-slug"
import { splitSlugAndAppendVersion } from "~/utils/split-slug-and-append-version"
import type { SidebarSection } from "./sidebar"

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

const walk = (section: SidebarSection, acc: string[]) => {
for (const doc of section.documentationPages) {
const docPath = href("/:version/:section/:subsection?/:filename", splitSlug(doc.slug))
const docPath = href("/:version/:section/:subsection?/:filename", splitSlugAndAppendVersion(doc.slug))
if (docPath === pathname) {
trail = [...acc, section.title, doc.title]
return true
Expand Down
5 changes: 2 additions & 3 deletions app/components/sidebar/sidebar-section.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { NavLink, href } from "react-router"
import { AccordionItem } from "~/ui/accordion"
import { splitSlug } from "~/utils/split-slug"
import { splitSlugAndAppendVersion } from "~/utils/split-slug-and-append-version"
import type { SidebarSection } from "./sidebar"

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

const SectionItemLink = ({ documentPage, depth, onItemClick }: SectionItemLinkProps) => {
const indentClass = getIndentClass(depth)

return (
<NavLink
prefetch="intent"
to={href("/:version/:section/:subsection?/:filename", splitSlug(documentPage.slug))}
to={href("/:version/:section/:subsection?/:filename", splitSlugAndAppendVersion(documentPage.slug))}
onClick={onItemClick}
className={({ isActive, isPending }) =>
`block rounded-md px-3 py-2 text-xs sm:text-sm md:text-base ${indentClass}
Expand Down
1 change: 0 additions & 1 deletion app/components/sidebar/sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { MobileSidebarProvider } from "./mobile-sidebar-context"
export type SidebarSection = {
title: string
slug: string
sectionId: string
subsections: SidebarSection[]
documentationPages: {
title: string
Expand Down
106 changes: 46 additions & 60 deletions app/components/sidebar/tests/build-breadcrumbs.test.tsx
Original file line number Diff line number Diff line change
@@ -1,94 +1,80 @@
import { buildBreadcrumb } from "../build-breadcrumbs"
import { describe, expect, it, vi } from "vitest"
import type { SidebarSection } from "../sidebar"

vi.mock("~/utils/split-slug-and-append-version", () => ({
splitSlugAndAppendVersion: (slug: string) => {
const parts = slug.split("/").filter(Boolean)
const version = "v1.0.0"

if (parts.length === 2) {
const [section, filename] = parts
return { version, section, filename }
}
if (parts.length === 3) {
const [section, subsection, filename] = parts
return { version, section, subsection, filename }
}

throw new Error(`Bad slug in test: ${slug}`)
},
}))

import { buildBreadcrumb } from "../build-breadcrumbs"

type Doc = { slug: string; title: string }
const sec = (over: Partial<SidebarSection>) => ({
const makeDoc = (slug: string, title: string): Doc => ({ slug, title })

type MinimalSection = Pick<SidebarSection, "title" | "slug" | "documentationPages" | "subsections">
const makeSection = (overrides: Partial<MinimalSection> = {}) => ({
title: "",
slug: "",
sectionId: "",
documentationPages: [],
subsections: [],
...over,
...overrides,
})

const doc = (slug: string, title: string): Doc => ({ slug, title })

describe("buildBreadcrumb test suite", () => {
describe("buildBreadcrumb (versioned paths via splitSlugAndAppendVersion)", () => {
it("returns [] when pathname doesn't match any doc", () => {
const items = [
sec({
makeSection({
title: "Getting Started",
slug: "v1/started",
documentationPages: [doc("v1/started/intro", "Intro")],
slug: "getting-started",
documentationPages: [makeDoc("getting-started/intro", "Intro")],
}),
]

// biome-ignore lint/suspicious/noExplicitAny: we can use any in tests
const result = buildBreadcrumb(items as any, "/v1/started/unknown")
expect(result).toEqual([])
expect(buildBreadcrumb(items, "/v1.0.0/getting-started/unknown")).toEqual([])
})

it("returns [section, doc] for a top-level doc", () => {
const items = [
sec({
makeSection({
title: "Getting Started",
slug: "v1/started",
documentationPages: [doc("v1/started/intro", "Intro")],
slug: "getting-started",
documentationPages: [makeDoc("getting-started/intro", "Intro")],
}),
]

// biome-ignore lint/suspicious/noExplicitAny: we can use any in tests
const result = buildBreadcrumb(items as any, "/v1/started/intro")
expect(result).toEqual(["Getting Started", "Intro"])
expect(buildBreadcrumb(items, "/v1.0.0/getting-started/intro")).toEqual(["Getting Started", "Intro"])
})

it("returns full trail for a nested doc (root → sub → doc)", () => {
const items = [
sec({
makeSection({
title: "Configuration",
slug: "v1/configuration",
slug: "configuration",
subsections: [
sec({
makeSection({
title: "Advanced",
slug: "v1/configuration/advanced",
documentationPages: [doc("v1/configuration/advanced/tuning", "Tuning")],
}),
],
documentationPages: [doc("v1/configuration/setup", "Setup")],
}),
]

// biome-ignore lint/suspicious/noExplicitAny: we can use any in tests
const result = buildBreadcrumb(items as any, "/v1/configuration/advanced/tuning")
expect(result).toEqual(["Configuration", "Advanced", "Tuning"])
})

it("stops at the first matching branch across multiple roots", () => {
const items = [
sec({
title: "Alpha",
slug: "v1/alpha",
documentationPages: [doc("v1/alpha/readme", "Readme")],
}),
sec({
title: "Beta",
slug: "v1/beta",
subsections: [
sec({
title: "Deep",
slug: "v1/beta/deep",
documentationPages: [doc("v1/beta/deep/file", "File")],
slug: "configuration/advanced",
documentationPages: [makeDoc("configuration/advanced/tuning", "Tuning")],
}),
],
documentationPages: [makeDoc("configuration/setup", "Setup")],
}),
]

// biome-ignore lint/suspicious/noExplicitAny: we can use any in tests
const result1 = buildBreadcrumb(items as any, "/v1/alpha/readme")
expect(result1).toEqual(["Alpha", "Readme"])

// biome-ignore lint/suspicious/noExplicitAny: we can use any in tests
const result2 = buildBreadcrumb(items as any, "/v1/beta/deep/file")
expect(result2).toEqual(["Beta", "Deep", "File"])
expect(buildBreadcrumb(items, "/v1.0.0/configuration/advanced/tuning")).toEqual([
"Configuration",
"Advanced",
"Tuning",
])
})
})
55 changes: 55 additions & 0 deletions app/components/versions-dropdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { useState } from "react"
import { useNavigate } from "react-router"
import { Icon } from "~/ui/icon/icon"
import { getCurrentVersion, homepageUrlWithVersion, isKnownVersion } from "~/utils/version-resolvers"
import { versions } from "~/utils/versions"

export function VersionDropdown() {
const navigate = useNavigate()
const { version: currentVersion } = getCurrentVersion()
const [selectedVersion, setSelectedVersion] = useState(currentVersion)

function onChange(e: React.ChangeEvent<HTMLSelectElement>) {
const next = e.target.value
if (next === currentVersion) return

setSelectedVersion(isKnownVersion(next) ? next : currentVersion)

const to = homepageUrlWithVersion(next)
const nav = () => {
navigate(to)
e.target.blur()
}
if (document.startViewTransition) document.startViewTransition(nav)
else nav()
}

return (
<div className="relative inline-block text-[var(--color-text-normal)]">
<select
id="version"
name="version"
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"
value={selectedVersion}
onChange={onChange}
aria-label="Select documentation version"
>
{versions.map((v) => (
<option
key={v}
value={v}
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)]" : ""}
`}
>
{v}
</option>
))}
</select>

<Icon
name="ChevronDown"
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"
/>
</div>
)
}
4 changes: 2 additions & 2 deletions app/hooks/use-previous-next-pages.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { href, useLocation } from "react-router"
import type { SidebarSection } from "~/components/sidebar/sidebar"
import { flattenSidebarItems } from "~/utils/flatten-sidebar"
import { splitSlug } from "~/utils/split-slug"
import { splitSlugAndAppendVersion } from "~/utils/split-slug-and-append-version"

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

return {
title: item.title,
to: href("/:version/:section/:subsection?/:filename", splitSlug(item.slug)),
to: href("/:version/:section/:subsection?/:filename", splitSlugAndAppendVersion(item.slug)),
}
}

Expand Down
4 changes: 3 additions & 1 deletion app/routes.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { type RouteConfig, index, layout, route } from "@react-router/dev/routes"

export default [
index("routes/index.tsx"),
layout("routes/documentation-layout.tsx", [
index("routes/documentation-homepage.tsx"),
route(":version?/home", "routes/documentation-homepage.tsx"),
route(":version/:section/:subsection?/:filename", "routes/documentation-page.tsx"),
]),
route("sitemap-index.xml", "routes/sitemap-index[.]xml.ts"),
route("robots.txt", "routes/robots[.]txt.ts"),
route("resource/*", "routes/resource.locales.ts"),
route("$", "routes/$.tsx"),
route("sitemap/:lang.xml", "routes/sitemap.$lang[.]xml.ts"),
route(":version?/llms.txt", "routes/llms[.]txt.ts"),
] satisfies RouteConfig
16 changes: 9 additions & 7 deletions app/routes/documentation-homepage.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import { allPages } from "content-collections"
import GithubContributeLinks from "~/components/github-contribute-links"
import PageMdxArticle from "~/components/page-mdx-article"
import { loadContentCollections } from "~/utils/load-content-collections"
import { resolveVersionForHomepage } from "~/utils/version-resolvers"
import type { Route } from "./+types/documentation-homepage"

export async function loader() {
const page = allPages.find((post) => post._meta.path === "_index")
if (!page) {
throw new Response("Not Found", { status: 404 })
}
return { page }
export async function loader({ params }: Route.LoaderArgs) {
const { version } = resolveVersionForHomepage(params.version)
const { allPages } = await loadContentCollections(version)
const page = allPages.find((p) => p._meta.path === "_index")
if (!page) throw new Response("Not Found", { status: 404 })

return { page, version }
}

export default function DocumentationHomepage({ loaderData }: Route.ComponentProps) {
Expand Down
21 changes: 14 additions & 7 deletions app/routes/documentation-layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,29 @@ import { Header } from "~/components/header"
import { Logo } from "~/components/logo"
import { Sidebar } from "~/components/sidebar/sidebar"
import { ThemeToggle } from "~/components/theme-toggle"
import { VersionDropdown } from "~/components/versions-dropdown"
import { createSidebarTree } from "~/utils/create-sidebar-tree"
import { resolveVersionForLayout } from "~/utils/version-resolvers"
import type { Route } from "./+types/documentation-layout"

export async function loader() {
return { sidebarTree: createSidebarTree() }
export async function loader({ params, request }: Route.LoaderArgs) {
const { version } = resolveVersionForLayout(params.version, request)
const sidebarTree = await createSidebarTree(version)
return { sidebarTree, version }
}

export default function DocumentationLayout({ loaderData }: Route.ComponentProps) {
const { sidebarTree } = loaderData

return (
<div className="block min-h-screen bg-[var(--color-background)] 2xl:container 2xl:mx-auto">
<Header>
<Logo>
{/* Replace with your Logo */}
<span>REACT ROUTER DEVTOOLS</span>
</Logo>
<div className="flex items-start gap-3">
<Logo>
{/* Replace with your Logo */}
<span className="p-0">REACT ROUTER DEVTOOLS</span>
</Logo>
<VersionDropdown />
</div>
<ThemeToggle />
</Header>

Expand Down
Loading