PageNav (On-Page ToC)
Relevant source files
The following files were used as context for generating this wiki page:
The PageNav component is a client-side React component that generates an "On This Page" table of contents for documentation pages. It dynamically inspects the rendered page content for heading elements (h2, h3, h4), builds a hierarchical navigation tree, and displays it as a sticky sidebar. The component enhances user experience by providing a clear overview of the page structure and allowing for quick navigation to specific sections. It also highlights the currently active section as the user scrolls through the document.
The component is an integral part of the main documentation layout and is designed to be responsive, appearing only on larger screen sizes.
Sources: src/components/PageNav.tsx, src/layouts/DocsLayout.tsx:150
Component Integration
The PageNav component is rendered within the DocsLayout, which serves as the primary layout for all documentation pages. It is placed alongside the main content area, creating a two-column layout on wider screens.
Sources: src/layouts/DocsLayout.tsx:150
Layout and Styling
PageNav is positioned to the right of the main prose content. Its visibility and width are controlled by Tailwind CSS utility classes.
- It is hidden by default and only displayed on
lgscreens and larger (hidden lg:flex). - It has a fixed width of
300pxand a minimum width defined aspageNav(200px) in the Tailwind configuration.
Sources: src/components/PageNav.tsx:81, tailwind.config.js:41
The following diagram shows the basic layout structure:
Props
The component receives the current page's title as a prop, which is used to trigger updates when the user navigates to a new page.
| Prop | Type | Description | Source File |
|---|---|---|---|
title | string | The title of the current documentation page. | src/components/PageNav.tsx:8 |
The PageNav component is instantiated in DocsLayout.tsx like this:
// src/layouts/DocsLayout.tsx:150
<PageNav title={frontMatter.title} />
Sources: src/layouts/DocsLayout.tsx:150
Header Discovery and Tree Building
The core functionality of PageNav is to scan the DOM for headings and build a hierarchical tree structure representing the document's outline. This process is initiated within a useEffect hook that runs whenever the page title prop changes.
Sources: src/components/PageNav.tsx:59-64
Data Structure
The discovered headers are organized into a tree using the IHeader interface, which is defined locally within the component.
// src/components/PageNav.tsx:11-16
interface IHeader {
level: number;
title: string;
id: string;
subHeaders: IHeader[];
}
This recursive structure allows for nesting headers, such as an h3 under an h2.
Sources: src/components/PageNav.tsx:11-16
Discovery and Building Process
The following flowchart illustrates how the header tree is constructed:
The key functions involved in this process are:
| Function | Description | Source File |
|---|---|---|
nodeToHeader | Converts an HTMLHeadingElement DOM node into an IHeader object. | src/components/PageNav.tsx:18-23 |
buildHeaderTreeRec | Recursively processes a list of heading nodes to build a nested tree structure based on heading levels. | src/components/PageNav.tsx:25-45 |
buildHeaderTree | Initializes the recursive tree-building process. | src/components/PageNav.tsx:47-57 |
Active Section Highlighting
PageNav automatically highlights the link corresponding to the section of the page currently in the user's viewport. This is managed through a combination of a scroll event listener and React state.
Sources: src/components/PageNav.tsx:66-80
Scroll Event Handling
A useEffect hook sets up and tears down a scroll event listener on the window object.
The handleScroll function:
- Gets all heading elements from the document.
- Calculates the current scroll position (
window.scrollY + 100to provide a top offset). - Iterates through the headings to find the last one whose
offsetTopis less than or equal to the scroll position. - Updates the
activeIdstate with the ID of this "active" heading.
Sources: src/components/PageNav.tsx:67-79
The sequence diagram below shows this interaction:
Auto-Scrolling the Navigation List
Another useEffect hook observes changes to the activeId state. When the active heading changes, it finds the corresponding anchor element in the PageNav list and smoothly scrolls it into the nearest visible area of the navigation pane. This ensures the active link is always visible to the user.
// src/components/PageNav.tsx:82-88
useEffect(() => {
if (activeId && linkRefs.current[activeId]) {
linkRefs.current[activeId]?.scrollIntoView({
block: "nearest",
behavior: "smooth",
});
}
}, [activeId]);
Sources: src/components/PageNav.tsx:82-88
Rendering and Navigation
The final step is to render the hierarchical list of links. This is handled by the recursive HeaderList component.
Sources: src/components/PageNav.tsx:112-146
HeaderList Component
This component iterates over an array of IHeader objects. For each header, it renders a list item (<li>) containing a Link. If the header has subHeaders, it recursively calls itself to render the nested list.
- Nesting: The
nestingprop determines the indentation level (ml-0,ml-6,ml-10) for each list, creating a visual hierarchy. - Active State: It compares each header's
idwith theactiveIdprop to apply conditional styling for the active link (font-medium text-pink-500). - Navigation: The
Linkcomponent'sonClickhandler uses thescrollToIDutility to perform a smooth scroll to the corresponding section on the page.
Sources: src/components/PageNav.tsx:107-109, src/components/PageNav.tsx:112-146
Summary
The PageNav component is a self-contained and dynamic table of contents system for the Railway documentation. By inspecting the DOM and responding to user scroll events, it provides essential navigation context and improves the readability of long content pages. Its integration into DocsLayout ensures it is a consistent feature across the documentation site.
Sources: src/components/PageNav.tsx, src/layouts/DocsLayout.tsx