Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 6 additions & 0 deletions .changeset/configurable-navigation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@node-core/doc-kit': patch
---

Add `web.navigation`, which supplies the sidebar groups (`navigation.sidebar`)
and the navigation bar items (`navigation.navbar`) from configuration.
19 changes: 19 additions & 0 deletions beta/doc-kit.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,24 @@ export default {
web: {
remoteConfigUrl:
'https://raw.githubusercontent.com/nodejs/doc-kit/main/beta/site.json',

navigation: {
// This preview is deployed at a subdomain, so
// these links need to be absolute
navbar: [
{ text: 'Learn', link: 'https://nodejs.org/en/learn' },
{ text: 'About', link: 'https://nodejs.org/en/about' },
{ text: 'Download', link: 'https://nodejs.org/en/download' },
{ text: 'Docs', link: 'https://nodejs.org/docs/latest/api/' },
{
text: 'Contribute',
link: 'https://github.com/nodejs/node/blob/main/CONTRIBUTING.md',
},
{
text: 'Courses',
link: 'https://training.linuxfoundation.org/openjs-certification-candidate-resources/',
},
],
},
},
};
54 changes: 52 additions & 2 deletions packages/core/src/generators/web/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ The `web` generator accepts the following configuration options:
| `imports` | `object` | See below | Object mapping `#theme/` aliases to component paths for customization |
| `virtualImports` | `object` | `{}` | Additional virtual module mappings supplied to the server and client builds |
| `components` | `object` | `{}` | Maps JSX tag names to component imports, enabling JSX-in-MDX (see below) |
| `navigation` | `object` | `{}` | Sidebar groups and navigation bar items (see below) |
| `bundler` | `WebBundler` | Vite adapter | Adapter that renders server entries and writes the client and HTML output (see below) |

### `head`
Expand Down Expand Up @@ -71,6 +72,56 @@ export default {
> via `head`, including `og:title` (which mirrors the per-page title) and
> `og:type`. The UI stylesheet bundles its fonts locally.

### `navigation`

The `navigation` object supplies the site's two navigation surfaces. Both keys
are optional; omit either one to keep that component's default.

| Key | Type | Description |
| --------- | ------- | ---------------------------------------------------------------------------------------------------------- |
| `sidebar` | `array` | Sidebar groups, each `{ groupName, items }`. Defaults to one `API Documentation` group holding every page. |
| `navbar` | `array` | Navigation bar items, each `{ text, link, target? }`. Defaults to none, which renders no items. |

Sidebar items are `{ label, link }` and may nest through an `items` array of
their own. A `label` is plain text, except that backticked spans render as
`<code>` (``'`fs` Generator'``), matching how page headings are rendered. A
`link` is a page path without its extension (`/fs`, `/generators/web`): it is
resolved against the page being rendered, so it obeys `useAbsoluteURLs` and
highlights while it is the current page. Links starting with `http://` or
`https://` are used as authored.

Navigation bar links are always used as authored, since they typically point
outside the generated site. Give them a `target` of `'_blank'` to open in a new
tab and mark them with an external-link icon.

```js
// doc-kit.config.mjs
export default {
web: {
navigation: {
sidebar: [
{
groupName: 'Guides',
items: [{ label: 'Getting started', link: '/getting-started' }],
},
{
groupName: 'Reference',
items: [{ label: '`fs`', link: '/fs' }],
},
],
navbar: [
{ text: 'Learn', link: 'https://nodejs.org/en/learn' },
{ text: 'Download', link: 'https://nodejs.org/en/download' },
],
},
},
};
```

The sidebar also renders a version `<Select>` built from `changelog`. A site
configured without one has no versions to switch between, so the control is
omitted rather than rendered empty.

### Bundler adapters

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

### Available exports

All scalar (non-object) configuration values are automatically exported. The defaults include:

| Export | Type | Description |
| ------------------------ | ------------------------------ | --------------------------------------------------------------------------------------------------------------------- |
| `project` | `string` | Project name (e.g. `'Node.js'`) |
Expand All @@ -271,6 +320,7 @@ All scalar (non-object) configuration values are automatically exported. The def
| `versions` | `Array<{ url, label, major }>` | Pre-computed version entries with labels and URL templates (only `{path}` remains for per-page use) |
| `editURL` | `string` | Partially populated "edit this page" URL template (only `{path}` remains) |
| `pages` | `Array<[string, string]>` | Sorted `[name, path]` tuples for sidebar navigation |
| `navigation` | `object` | Mirrors the configured `navigation` (consumed by the built-in `SideBar` and `NavBar`) |
| `useAbsoluteURLs` | `boolean` | Whether internal links use absolute URLs (mirrors config value) |
| `baseURL` | `string` | Base URL for the documentation site (used when `useAbsoluteURLs` is `true`) |
| `languageDisplayNameMap` | `Map<string, string>` | Shiki language alias → display name map for code blocks |
Expand Down
3 changes: 3 additions & 0 deletions packages/core/src/generators/web/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ export default createLazyGenerator({
// see the web generator README for the shape and shorthand.
components: {},

// The SideBar and NavBar navigation items
navigation: {},

// When omitted, the Vite adapter is loaded lazily during generation.
bundler: undefined,
}),
Expand Down
11 changes: 11 additions & 0 deletions packages/core/src/generators/web/types.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import type { JSXContent } from '../jsx-ast/utils/buildContent.mjs';
import type { GlobalConfiguration } from '../../utils/configuration/types';
import type SideBar from '@node-core/ui-components/Containers/Sidebar';
import type NavBar from '@node-core/ui-components/Containers/NavBar';
import type { ComponentProps } from 'preact';

// An attribute bag rendered into an HTML tag. `true` becomes a valueless
// attribute (e.g. `crossorigin`); `false`/`null`/`undefined` are omitted.
Expand Down Expand Up @@ -66,6 +69,14 @@ export type Configuration = {
// `JSX_IMPORTS`. Pair each entry with a matching `imports` alias to resolve the
// `source` to a real module path.
components: Record<string, JSXImportConfig | string>;
// Sidebar groups and navigation-bar items. Both keys are optional; omitting
// one keeps that component's default. Sidebar links are page paths resolved
// per page (absolute URLs pass through); navigation-bar links are verbatim.
navigation: {
sidebar?: ComponentProps<typeof SideBar>['groups'];
navbar?: ComponentProps<typeof NavBar>['navItems'];
Comment thread
avivkeller marked this conversation as resolved.
// TODO(@avivkeller): `navigation.showCrossLinks`
};
// Optional bundler adapter. When omitted, the Vite adapter is loaded lazily.
bundler?: WebBundler;
};
Expand Down
7 changes: 5 additions & 2 deletions packages/core/src/generators/web/ui/components/NavBar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import GitHubIcon from '@node-core/ui-components/Icons/Social/GitHub';
import SearchBox from './SearchBox';
import { useTheme } from '../hooks/useTheme.mjs';

import { repository, showSearchBox } from '#theme/config';
import { repository, showSearchBox, navigation } from '#theme/config';
import Logo from '#theme/Logo';

/**
Expand All @@ -19,7 +19,10 @@ export default ({ metadata }) => {
<NavBar
Logo={Logo}
sidebarItemTogglerAriaLabel="Toggle navigation menu"
navItems={[]}
navItems={navigation.navbar ?? []}
// Drives the active-item highlight, and is dereferenced for every item
// whose link is site-absolute, so it must always be a string.
pathname={metadata.path}
>
{showSearchBox && <SearchBox pathname={metadata.path} />}
<ThemeToggle
Expand Down
67 changes: 47 additions & 20 deletions packages/core/src/generators/web/ui/components/SideBar/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import styles from './index.module.css';
import { relativeOrAbsolute } from '../../utils/relativeOrAbsolute.mjs';
import { renderLabel } from '../../utils/renderLabel.jsx';

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

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

/**
* Builds the sidebar groups
*
* @param {import('../../types').SerializedMetadata} metadata
*/
const buildGroups = metadata => {
const toLink = path =>
metadata.path === path
? `${metadata.basename}.html`
: `${relativeOrAbsolute(path, metadata.path)}.html`;

const toItem = ({ label, link, items }) => ({
label: renderLabel(label),
link: /^https?:/.test(link) ? link : toLink(link),
...(items && { items: items.map(toItem) }),
});

if (navigation.sidebar) {
return navigation.sidebar.map(({ groupName, items }) => ({
groupName,
items: items.map(toItem),
}));
}

return [
{
groupName: 'API Documentation',
items: pages.map(([label, link]) => toItem({ label, link })),
},
];
};

/**
* Sidebar component for MDX documentation with version selection and page navigation
* @param {{ metadata: import('../../types').SerializedMetadata }} props
Expand All @@ -37,32 +69,27 @@ export default ({ metadata }) => {
label,
}));

const items = pages.map(([heading, path]) => ({
label: renderLabel(heading),
link:
metadata.path === path
? `${metadata.basename}.html`
: `${relativeOrAbsolute(path, metadata.path)}.html`,
}));

return (
<SideBar
pathname={`${metadata.basename}.html`}
groups={[{ groupName: 'API Documentation', items }]}
groups={buildGroups(metadata)}
onSelect={redirect}
as={props => <a {...props} rel="prefetch" />}
title="Navigation"
>
<div>
<Select
label={`${project} version`}
values={compatibleVersions}
inline={true}
className={styles.select}
placeholder={`v${version.version}`}
onChange={redirect}
/>
</div>
{/* A site built without a `changelog` has no versions to switch between. */}
{versions.length > 0 && (
<div>
<Select
label={`${project} version`}
values={compatibleVersions}
inline={true}
className={styles.select}
placeholder={`v${version.version}`}
onChange={redirect}
/>
</div>
)}
</SideBar>
);
};
1 change: 1 addition & 0 deletions packages/core/src/generators/web/ui/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ declare module '#theme/config' {
export const templatePath: Configuration['templatePath'];
export const title: Configuration['title'];
export const useAbsoluteURLs: Configuration['useAbsoluteURLs'];
export const navigation: Configuration['navigation'];

// From config generation
export const version: SemVer;
Expand Down
36 changes: 30 additions & 6 deletions www/doc-kit.config.mjs
Original file line number Diff line number Diff line change
@@ -1,13 +1,24 @@
import { readFileSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { globSync, readFileSync } from 'node:fs';
import { basename, dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';

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

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

const generatorItems = globSync('packages/core/src/generators/*/README.md', {
cwd: REPO,
})
.map(file => basename(dirname(file)))
.sort()
.map(name => ({
label: `\`${name}\` Generator`,
link: `/generators/${name}`,
}));

const REPOSITORY = 'nodejs/doc-kit';
const BASE_URL = 'https://doc-kit-docs.vercel.app';

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

imports: {
// Sidebar order and grouping are not configurable; see the component.
'#theme/Sidebar': join(ROOT, 'theme', 'SideBar.jsx'),
navigation: {
sidebar: [
{
groupName: 'Pages',
items: [
{ label: '`doc-kit`', link: '/index' },
{ label: 'Getting started', link: '/getting-started' },
{ label: 'Configuration', link: '/configuration' },
{ label: 'Creating Commands', link: '/commands' },
{ label: 'Creating Generators', link: '/generators' },
{ label: 'Specification', link: '/specification' },
{ label: 'Creating Comparators', link: '/comparators' },
],
},
{ groupName: 'Generators', items: generatorItems },
],
},

head: {
Expand Down
Loading
Loading