Skip to content

Commit 1969ade

Browse files
committed
chore: improved index page
1 parent 7670a78 commit 1969ade

11 files changed

Lines changed: 186 additions & 292 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@doc-kit/generator-react': patch
3+
---
4+
5+
Render `<!-- DOCUMENTATION_INDEX -->` with a new `DocumentationIndex` UI component

packages/react/src/html/constants.mjs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ export const JSX_IMPORTS = {
2626
name: 'CodeTabs',
2727
source: resolve(ROOT, './ui/components/CodeTabs'),
2828
},
29+
DocumentationIndex: {
30+
name: 'DocumentationIndex',
31+
source: resolve(ROOT, './ui/components/DocumentationIndex'),
32+
},
2933
MDXTooltip: {
3034
name: 'MDXTooltip',
3135
isDefaultExport: false,
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import Badge from '@node-core/ui-components/Common/Badge';
2+
3+
import styles from './index.module.css';
4+
import { STABILITY_KINDS, STABILITY_LABELS } from '../constants.mjs';
5+
6+
/**
7+
* @typedef {Object} DocumentationIndexEntry
8+
* @property {string} api - Basename of the document, linked as `${api}.html`
9+
* @property {string} name - Human-readable name from the document's heading
10+
* @property {string} index - Stability index (e.g. `'2'` or `'1.1'`)
11+
* @property {string} description - Stability description from the document
12+
*/
13+
14+
/**
15+
* @param {DocumentationIndexEntry} props
16+
*/
17+
const IndexEntry = ({ api, name, index, description }) => {
18+
const level = parseInt(index, 10);
19+
const label = STABILITY_LABELS[level] ?? index;
20+
21+
const summary = description
22+
.replace(new RegExp(`^${label}[.:\\s-]*`, 'i'), '')
23+
.split('. ')[0]
24+
.replace(/\.$/, '');
25+
26+
return (
27+
<a className={styles.entry} href={`${api}.html`}>
28+
<span className={styles.title}>
29+
<span className={styles.name}>{name}</span>
30+
31+
<Badge
32+
size="small"
33+
kind={STABILITY_KINDS[level] ?? 'neutral'}
34+
aria-label={`Stability: ${index}`}
35+
>
36+
{label}
37+
</Badge>
38+
</span>
39+
40+
<code className={styles.api}>{api}</code>
41+
42+
{summary && <span className={styles.summary}>{summary}</span>}
43+
</a>
44+
);
45+
};
46+
47+
/**
48+
* @param {{ entries: Array<DocumentationIndexEntry> }} props
49+
*/
50+
export default ({ entries = [] }) => (
51+
<nav className={styles.documentationIndex} aria-label="Documentation index">
52+
{entries.map(entry => (
53+
<IndexEntry key={entry.api} {...entry} />
54+
))}
55+
</nav>
56+
);
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
.documentationIndex {
2+
display: grid;
3+
grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr));
4+
gap: 1rem;
5+
margin-block: 1.5rem;
6+
}
7+
8+
.entry {
9+
display: flex;
10+
flex-direction: column;
11+
gap: 0.375rem;
12+
padding: 1rem;
13+
border: 1px solid var(--color-neutral-200);
14+
border-radius: 0.75rem;
15+
color: inherit;
16+
text-decoration: none;
17+
transition:
18+
border-color 0.15s ease,
19+
background-color 0.15s ease;
20+
}
21+
22+
.entry:hover,
23+
.entry:focus-visible {
24+
border-color: var(--color-neutral-400);
25+
background-color: var(--color-neutral-100);
26+
}
27+
28+
:where([data-theme='dark'], [data-theme='dark'] *) .entry {
29+
border-color: var(--color-neutral-900);
30+
}
31+
32+
:where([data-theme='dark'], [data-theme='dark'] *) .entry:hover,
33+
:where([data-theme='dark'], [data-theme='dark'] *) .entry:focus-visible {
34+
border-color: var(--color-neutral-700);
35+
background-color: var(--color-neutral-950);
36+
}
37+
38+
.title {
39+
display: flex;
40+
align-items: center;
41+
justify-content: space-between;
42+
gap: 0.5rem;
43+
}
44+
45+
.name {
46+
font-weight: 600;
47+
color: var(--color-neutral-900);
48+
}
49+
50+
:where([data-theme='dark'], [data-theme='dark'] *) .name {
51+
color: var(--color-white);
52+
}
53+
54+
.api {
55+
align-self: flex-start;
56+
font-family: var(--font-ibm-plex-mono);
57+
font-size: 0.75rem;
58+
color: var(--color-neutral-800);
59+
}
60+
61+
:where([data-theme='dark'], [data-theme='dark'] *) .api {
62+
color: var(--color-neutral-600);
63+
}
64+
65+
.summary {
66+
font-size: 0.875rem;
67+
color: var(--color-neutral-800);
68+
}
69+
70+
:where([data-theme='dark'], [data-theme='dark'] *) .summary {
71+
color: var(--color-neutral-600);
72+
}

packages/react/src/html/ui/components/MetaBar/index.jsx

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import MetaBar from '@node-core/ui-components/Containers/MetaBar';
44
import GitHubIcon from '@node-core/ui-components/Icons/Social/GitHub';
55

66
import styles from './index.module.css';
7+
import { STABILITY_KINDS, STABILITY_LABELS } from '../constants.mjs';
78

89
import { editURL } from '#theme/config';
910

@@ -12,10 +13,6 @@ const iconMap = {
1213
MD: DocumentIcon,
1314
};
1415

15-
const STABILITY_KINDS = ['error', 'warning', null, 'info'];
16-
const STABILITY_LABELS = ['D', 'E', null, 'L'];
17-
const STABILITY_TOOLTIPS = ['Deprecated', 'Experimental', null, 'Legacy'];
18-
1916
/**
2017
* Renders a heading value with an optional stability badge
2118
* @param {{ value: string, stability: number }} props
@@ -25,9 +22,7 @@ const HeadingValue = ({ value, stability }) => {
2522
return value;
2623
}
2724

28-
const ariaLabel = STABILITY_TOOLTIPS[stability]
29-
? `Stability: ${STABILITY_TOOLTIPS[stability]}`
30-
: undefined;
25+
const label = STABILITY_LABELS[stability];
3126

3227
return (
3328
<>
@@ -37,11 +32,11 @@ const HeadingValue = ({ value, stability }) => {
3732
size="small"
3833
className={styles.badge}
3934
kind={STABILITY_KINDS[stability]}
40-
data-tooltip={STABILITY_TOOLTIPS[stability]}
41-
aria-label={ariaLabel}
35+
data-tooltip={label}
36+
aria-label={label ? `Stability: ${label}` : undefined}
4237
tabIndex={0}
4338
>
44-
{STABILITY_LABELS[stability]}
39+
{label?.[0]}
4540
</Badge>
4641
</>
4742
);
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/**
2+
* UI badge kinds and labels for Node.js API stability levels
3+
*
4+
* @see https://nodejs.org/api/documentation.html#stability-index
5+
*/
6+
export const STABILITY_KINDS = ['error', 'warning', 'default', 'info'];
7+
export const STABILITY_LABELS = [
8+
'Deprecated',
9+
'Experimental',
10+
'Stable',
11+
'Legacy',
12+
];

packages/react/src/jsx-ast/__tests__/generate.test.mjs

Lines changed: 0 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -94,50 +94,4 @@ describe('jsx-ast generate', () => {
9494
['index', 'fs']
9595
);
9696
});
97-
98-
it('only generates an index page when an index document is an input', async () => {
99-
await setConfig({ target: ['jsx-ast'] });
100-
101-
const jsxAstConfig = getConfig('jsx-ast');
102-
jsxAstConfig.generateAllPage = false;
103-
jsxAstConfig.generateNotFoundPage = false;
104-
105-
const seenItems = [];
106-
await collect(
107-
generate([createEntry('fs', 'File system')], createWorker(seenItems))
108-
);
109-
110-
assert.deepEqual(
111-
seenItems.map(({ head }) => head.api),
112-
['fs']
113-
);
114-
});
115-
116-
it('places the stability overview at the DOCUMENTATION_INDEX comment', async () => {
117-
await setConfig({ target: ['jsx-ast'] });
118-
119-
const jsxAstConfig = getConfig('jsx-ast');
120-
jsxAstConfig.generateAllPage = false;
121-
jsxAstConfig.generateNotFoundPage = false;
122-
123-
const index = createEntry('index', 'Index', { stabilityIndex: null });
124-
// The metadata parser turns a `<!-- DOCUMENTATION_INDEX -->` comment into
125-
// this tag on the entry of the section containing it.
126-
index.tags = ['DOCUMENTATION_INDEX'];
127-
128-
const seenItems = [];
129-
await collect(
130-
generate(
131-
[index, createEntry('fs', 'File system')],
132-
createWorker(seenItems)
133-
)
134-
);
135-
136-
const [{ entries }] = seenItems;
137-
const table = entries[0].content.children.at(-1);
138-
139-
assert.equal(table.tagName, 'table');
140-
const [row] = table.children.at(-1).children;
141-
assert.equal(row.children[0].children[0].properties.href, 'fs.html');
142-
});
14397
});

packages/react/src/jsx-ast/constants.mjs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,9 @@ export const AST_NODE_TYPES = {
189189
},
190190
};
191191

192+
// `<!-- DOCUMENTATION_INDEX -->` comment in a source document = a stability index
193+
export const DOCUMENTATION_INDEX_TAG = 'DOCUMENTATION_INDEX';
194+
192195
// These positions are explicity before anything else
193196
export const OVERRIDDEN_POSITIONS = [
194197
'index', // https://github.com/nodejs/node/blob/main/doc/api/index.md

packages/react/src/jsx-ast/generate.mjs

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,32 @@ import getConfig from '@doc-kit/core/utils/configuration/index.mjs';
22
import { groupNodesByModule } from '@doc-kit/core/utils/generators.mjs';
33
import { jsx, toJs } from 'estree-util-to-js';
44

5+
import { DOCUMENTATION_INDEX_TAG } from './constants.mjs';
6+
import { createJSXElement } from './utils/ast.mjs';
57
import buildContent from './utils/buildContent.mjs';
6-
import { injectDocumentationIndex } from './utils/documentationIndex.mjs';
78
import { getSortedHeadNodes } from './utils/getSortedHeadNodes.mjs';
9+
import { JSX_IMPORTS } from '../html/constants.mjs';
810
import { buildNotFoundPage } from './utils/synthetic/404.mjs';
911
import { buildAllPage } from './utils/synthetic/all.mjs';
1012

13+
/**
14+
* Builds the `<DocumentationIndex />` element
15+
*
16+
* @param {Array<import('@doc-kit/core/generators/metadata/types').MetadataEntry>} moduleEntries
17+
*/
18+
const buildDocumentationIndex = moduleEntries =>
19+
createJSXElement(JSX_IMPORTS.DocumentationIndex.name, {
20+
inline: false,
21+
entries: getSortedHeadNodes(moduleEntries)
22+
.filter(entry => entry.stability)
23+
.map(({ api, heading, stability }) => ({
24+
api,
25+
name: heading.data.name,
26+
index: stability.data.index,
27+
description: stability.data.description,
28+
})),
29+
});
30+
1131
/**
1232
* Builds the `{ head, entries }` page descriptors for all configured synthetic
1333
* pages. The descriptors are cheap to build; the expensive `buildContent` step
@@ -60,13 +80,16 @@ export async function processChunk(slicedInput, itemIndices) {
6080
*/
6181
export async function* generate(input, worker) {
6282
// The `index` page is only generated when an `index` document is part of
63-
// the input; the module list for the synthetic pages and the stability
64-
// overview excludes it.
83+
// the input; the module list for the synthetic pages and the documentation
84+
// index excludes it.
6585
const moduleInput = input.filter(entry => entry.api !== 'index');
6686

67-
// Sections tagged with a `<!-- DOCUMENTATION_INDEX -->` comment (e.g. in
68-
// the `index` document) receive the Stability Overview of all modules.
69-
injectDocumentationIndex(input, moduleInput);
87+
// Sections tagged with a `<!-- DOCUMENTATION_INDEX -->` build an index
88+
for (const entry of input) {
89+
if (entry.tags?.includes(DOCUMENTATION_INDEX_TAG)) {
90+
entry.content.children.push(buildDocumentationIndex(moduleInput));
91+
}
92+
}
7093

7194
// Create sliced input: each item contains head + its module's entries
7295
// This avoids sending all 4700+ entries to every worker

0 commit comments

Comments
 (0)