Skip to content

Commit 96e1c26

Browse files
authored
feat: add configuration for nav (#964)
1 parent 4278975 commit 96e1c26

10 files changed

Lines changed: 174 additions & 107 deletions

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@node-core/doc-kit': patch
3+
---
4+
5+
Add `web.navigation`, which supplies the sidebar groups (`navigation.sidebar`)
6+
and the navigation bar items (`navigation.navbar`) from configuration.

beta/doc-kit.config.mjs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,24 @@ export default {
22
web: {
33
remoteConfigUrl:
44
'https://raw.githubusercontent.com/nodejs/doc-kit/main/beta/site.json',
5+
6+
navigation: {
7+
// This preview is deployed at a subdomain, so
8+
// these links need to be absolute
9+
navbar: [
10+
{ text: 'Learn', link: 'https://nodejs.org/en/learn' },
11+
{ text: 'About', link: 'https://nodejs.org/en/about' },
12+
{ text: 'Download', link: 'https://nodejs.org/en/download' },
13+
{ text: 'Docs', link: 'https://nodejs.org/docs/latest/api/' },
14+
{
15+
text: 'Contribute',
16+
link: 'https://github.com/nodejs/node/blob/main/CONTRIBUTING.md',
17+
},
18+
{
19+
text: 'Courses',
20+
link: 'https://training.linuxfoundation.org/openjs-certification-candidate-resources/',
21+
},
22+
],
23+
},
524
},
625
};

packages/core/src/generators/web/README.md

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ The `web` generator accepts the following configuration options:
2525
| `imports` | `object` | See below | Object mapping `#theme/` aliases to component paths for customization |
2626
| `virtualImports` | `object` | `{}` | Additional virtual module mappings supplied to the server and client builds |
2727
| `components` | `object` | `{}` | Maps JSX tag names to component imports, enabling JSX-in-MDX (see below) |
28+
| `navigation` | `object` | `{}` | Sidebar groups and navigation bar items (see below) |
2829
| `bundler` | `WebBundler` | Vite adapter | Adapter that renders server entries and writes the client and HTML output (see below) |
2930

3031
### `head`
@@ -71,6 +72,56 @@ export default {
7172
> via `head`, including `og:title` (which mirrors the per-page title) and
7273
> `og:type`. The UI stylesheet bundles its fonts locally.
7374
75+
### `navigation`
76+
77+
The `navigation` object supplies the site's two navigation surfaces. Both keys
78+
are optional; omit either one to keep that component's default.
79+
80+
| Key | Type | Description |
81+
| --------- | ------- | ---------------------------------------------------------------------------------------------------------- |
82+
| `sidebar` | `array` | Sidebar groups, each `{ groupName, items }`. Defaults to one `API Documentation` group holding every page. |
83+
| `navbar` | `array` | Navigation bar items, each `{ text, link, target? }`. Defaults to none, which renders no items. |
84+
85+
Sidebar items are `{ label, link }` and may nest through an `items` array of
86+
their own. A `label` is plain text, except that backticked spans render as
87+
`<code>` (``'`fs` Generator'``), matching how page headings are rendered. A
88+
`link` is a page path without its extension (`/fs`, `/generators/web`): it is
89+
resolved against the page being rendered, so it obeys `useAbsoluteURLs` and
90+
highlights while it is the current page. Links starting with `http://` or
91+
`https://` are used as authored.
92+
93+
Navigation bar links are always used as authored, since they typically point
94+
outside the generated site. Give them a `target` of `'_blank'` to open in a new
95+
tab and mark them with an external-link icon.
96+
97+
```js
98+
// doc-kit.config.mjs
99+
export default {
100+
web: {
101+
navigation: {
102+
sidebar: [
103+
{
104+
groupName: 'Guides',
105+
items: [{ label: 'Getting started', link: '/getting-started' }],
106+
},
107+
{
108+
groupName: 'Reference',
109+
items: [{ label: '`fs`', link: '/fs' }],
110+
},
111+
],
112+
navbar: [
113+
{ text: 'Learn', link: 'https://nodejs.org/en/learn' },
114+
{ text: 'Download', link: 'https://nodejs.org/en/download' },
115+
],
116+
},
117+
},
118+
};
119+
```
120+
121+
The sidebar also renders a version `<Select>` built from `changelog`. A site
122+
configured without one has no versions to switch between, so the control is
123+
omitted rather than rendered empty.
124+
74125
### Bundler adapters
75126

76127
The `bundler` option accepts a small Doc Kit adapter rather than configuration
@@ -261,8 +312,6 @@ import { project, repository, editURL } from '#theme/config';
261312

262313
### Available exports
263314

264-
All scalar (non-object) configuration values are automatically exported. The defaults include:
265-
266315
| Export | Type | Description |
267316
| ------------------------ | ------------------------------ | --------------------------------------------------------------------------------------------------------------------- |
268317
| `project` | `string` | Project name (e.g. `'Node.js'`) |
@@ -271,6 +320,7 @@ All scalar (non-object) configuration values are automatically exported. The def
271320
| `versions` | `Array<{ url, label, major }>` | Pre-computed version entries with labels and URL templates (only `{path}` remains for per-page use) |
272321
| `editURL` | `string` | Partially populated "edit this page" URL template (only `{path}` remains) |
273322
| `pages` | `Array<[string, string]>` | Sorted `[name, path]` tuples for sidebar navigation |
323+
| `navigation` | `object` | Mirrors the configured `navigation` (consumed by the built-in `SideBar` and `NavBar`) |
274324
| `useAbsoluteURLs` | `boolean` | Whether internal links use absolute URLs (mirrors config value) |
275325
| `baseURL` | `string` | Base URL for the documentation site (used when `useAbsoluteURLs` is `true`) |
276326
| `languageDisplayNameMap` | `Map<string, string>` | Shiki language alias → display name map for code blocks |

packages/core/src/generators/web/index.mjs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,9 @@ export default {
9999
// see the web generator README for the shape and shorthand.
100100
components: {},
101101

102+
// The SideBar and NavBar navigation items
103+
navigation: {},
104+
102105
// When omitted, the Vite adapter is loaded lazily during generation.
103106
bundler: undefined,
104107
}),

packages/core/src/generators/web/types.d.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import type { JSXContent } from '../jsx-ast/utils/buildContent.mjs';
22
import type { GlobalConfiguration } from '../../utils/configuration/types';
3+
import type SideBar from '@node-core/ui-components/Containers/Sidebar';
4+
import type NavBar from '@node-core/ui-components/Containers/NavBar';
5+
import type { ComponentProps } from 'preact';
36

47
// An attribute bag rendered into an HTML tag. `true` becomes a valueless
58
// attribute (e.g. `crossorigin`); `false`/`null`/`undefined` are omitted.
@@ -66,6 +69,14 @@ export type Configuration = {
6669
// `JSX_IMPORTS`. Pair each entry with a matching `imports` alias to resolve the
6770
// `source` to a real module path.
6871
components: Record<string, JSXImportConfig | string>;
72+
// Sidebar groups and navigation-bar items. Both keys are optional; omitting
73+
// one keeps that component's default. Sidebar links are page paths resolved
74+
// per page (absolute URLs pass through); navigation-bar links are verbatim.
75+
navigation: {
76+
sidebar?: ComponentProps<typeof SideBar>['groups'];
77+
navbar?: ComponentProps<typeof NavBar>['navItems'];
78+
// TODO(@avivkeller): `navigation.showCrossLinks`
79+
};
6980
// Optional bundler adapter. When omitted, the Vite adapter is loaded lazily.
7081
bundler?: WebBundler;
7182
};

packages/core/src/generators/web/ui/components/NavBar.jsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import GitHubIcon from '@node-core/ui-components/Icons/Social/GitHub';
66
import SearchBox from './SearchBox';
77
import { useTheme } from '../hooks/useTheme.mjs';
88

9-
import { repository, showSearchBox } from '#theme/config';
9+
import { repository, showSearchBox, navigation } from '#theme/config';
1010
import Logo from '#theme/Logo';
1111

1212
/**
@@ -19,7 +19,10 @@ export default ({ metadata }) => {
1919
<NavBar
2020
Logo={Logo}
2121
sidebarItemTogglerAriaLabel="Toggle navigation menu"
22-
navItems={[]}
22+
navItems={navigation.navbar ?? []}
23+
// Drives the active-item highlight, and is dereferenced for every item
24+
// whose link is site-absolute, so it must always be a string.
25+
pathname={metadata.path}
2326
>
2427
{showSearchBox && <SearchBox pathname={metadata.path} />}
2528
<ThemeToggle

packages/core/src/generators/web/ui/components/SideBar/index.jsx

Lines changed: 47 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import styles from './index.module.css';
55
import { relativeOrAbsolute } from '../../utils/relativeOrAbsolute.mjs';
66
import { renderLabel } from '../../utils/renderLabel.jsx';
77

8-
import { project, version, versions, pages } from '#theme/config';
8+
import { project, version, versions, navigation, pages } from '#theme/config';
99

1010
/**
1111
* Extracts the major version number from a version string.
@@ -20,6 +20,38 @@ const getMajorVersion = v => parseInt(String(v).match(/\d+/)?.[0] ?? '0', 10);
2020
*/
2121
const redirect = url => (window.location.href = url);
2222

23+
/**
24+
* Builds the sidebar groups
25+
*
26+
* @param {import('../../types').SerializedMetadata} metadata
27+
*/
28+
const buildGroups = metadata => {
29+
const toLink = path =>
30+
metadata.path === path
31+
? `${metadata.basename}.html`
32+
: `${relativeOrAbsolute(path, metadata.path)}.html`;
33+
34+
const toItem = ({ label, link, items }) => ({
35+
label: renderLabel(label),
36+
link: /^https?:/.test(link) ? link : toLink(link),
37+
...(items && { items: items.map(toItem) }),
38+
});
39+
40+
if (navigation.sidebar) {
41+
return navigation.sidebar.map(({ groupName, items }) => ({
42+
groupName,
43+
items: items.map(toItem),
44+
}));
45+
}
46+
47+
return [
48+
{
49+
groupName: 'API Documentation',
50+
items: pages.map(([label, link]) => toItem({ label, link })),
51+
},
52+
];
53+
};
54+
2355
/**
2456
* Sidebar component for MDX documentation with version selection and page navigation
2557
* @param {{ metadata: import('../../types').SerializedMetadata }} props
@@ -37,32 +69,27 @@ export default ({ metadata }) => {
3769
label,
3870
}));
3971

40-
const items = pages.map(([heading, path]) => ({
41-
label: renderLabel(heading),
42-
link:
43-
metadata.path === path
44-
? `${metadata.basename}.html`
45-
: `${relativeOrAbsolute(path, metadata.path)}.html`,
46-
}));
47-
4872
return (
4973
<SideBar
5074
pathname={`${metadata.basename}.html`}
51-
groups={[{ groupName: 'API Documentation', items }]}
75+
groups={buildGroups(metadata)}
5276
onSelect={redirect}
5377
as={props => <a {...props} rel="prefetch" />}
5478
title="Navigation"
5579
>
56-
<div>
57-
<Select
58-
label={`${project} version`}
59-
values={compatibleVersions}
60-
inline={true}
61-
className={styles.select}
62-
placeholder={`v${version.version}`}
63-
onChange={redirect}
64-
/>
65-
</div>
80+
{/* A site built without a `changelog` has no versions to switch between. */}
81+
{versions.length > 0 && (
82+
<div>
83+
<Select
84+
label={`${project} version`}
85+
values={compatibleVersions}
86+
inline={true}
87+
className={styles.select}
88+
placeholder={`v${version.version}`}
89+
onChange={redirect}
90+
/>
91+
</div>
92+
)}
6693
</SideBar>
6794
);
6895
};

packages/core/src/generators/web/ui/types.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ declare module '#theme/config' {
1717
export const templatePath: Configuration['templatePath'];
1818
export const title: Configuration['title'];
1919
export const useAbsoluteURLs: Configuration['useAbsoluteURLs'];
20+
export const navigation: Configuration['navigation'];
2021

2122
// From config generation
2223
export const version: SemVer;

www/doc-kit.config.mjs

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,24 @@
1-
import { readFileSync } from 'node:fs';
2-
import { dirname, join } from 'node:path';
1+
import { globSync, readFileSync } from 'node:fs';
2+
import { basename, dirname, join } from 'node:path';
33
import { fileURLToPath } from 'node:url';
44

55
const ROOT = dirname(fileURLToPath(import.meta.url));
6+
const REPO = join(ROOT, '..');
67

78
const { version } = JSON.parse(
8-
readFileSync(join(ROOT, '..', 'packages', 'core', 'package.json'), 'utf-8')
9+
readFileSync(join(REPO, 'packages', 'core', 'package.json'), 'utf-8')
910
);
1011

12+
const generatorItems = globSync('packages/core/src/generators/*/README.md', {
13+
cwd: REPO,
14+
})
15+
.map(file => basename(dirname(file)))
16+
.sort()
17+
.map(name => ({
18+
label: `\`${name}\` Generator`,
19+
link: `/generators/${name}`,
20+
}));
21+
1122
const REPOSITORY = 'nodejs/doc-kit';
1223
const BASE_URL = 'https://doc-kit-docs.vercel.app';
1324

@@ -58,9 +69,22 @@ export default {
5869
// each slug back to its true origin.
5970
editURL: `https://github.com/${REPOSITORY}`,
6071

61-
imports: {
62-
// Sidebar order and grouping are not configurable; see the component.
63-
'#theme/Sidebar': join(ROOT, 'theme', 'SideBar.jsx'),
72+
navigation: {
73+
sidebar: [
74+
{
75+
groupName: 'Pages',
76+
items: [
77+
{ label: '`doc-kit`', link: '/index' },
78+
{ label: 'Getting started', link: '/getting-started' },
79+
{ label: 'Configuration', link: '/configuration' },
80+
{ label: 'Creating Commands', link: '/commands' },
81+
{ label: 'Creating Generators', link: '/generators' },
82+
{ label: 'Specification', link: '/specification' },
83+
{ label: 'Creating Comparators', link: '/comparators' },
84+
],
85+
},
86+
{ groupName: 'Generators', items: generatorItems },
87+
],
6488
},
6589

6690
head: {

0 commit comments

Comments
 (0)