From ee6b4a8698503e9b0d9d779cc7aed31406f108d6 Mon Sep 17 00:00:00 2001 From: avivkeller Date: Sun, 9 Aug 2026 17:51:59 -0400 Subject: [PATCH] chore: new index page --- .changeset/odd-snails-rest.md | 5 + packages/react/src/html/constants.mjs | 4 + .../html/ui/components/Layout/IndexPage.jsx | 25 ++++ .../ui/components/Layout/IndexPage.module.css | 22 ++++ .../src/html/ui/components/MetaBar/index.jsx | 37 ++---- .../src/html/ui/components/StabilityBadge.jsx | 29 +++++ .../utils/synthetic/__tests__/index.test.mjs | 114 ------------------ .../src/jsx-ast/utils/synthetic/index.mjs | 72 +++-------- 8 files changed, 111 insertions(+), 197 deletions(-) create mode 100644 .changeset/odd-snails-rest.md create mode 100644 packages/react/src/html/ui/components/Layout/IndexPage.jsx create mode 100644 packages/react/src/html/ui/components/Layout/IndexPage.module.css create mode 100644 packages/react/src/html/ui/components/StabilityBadge.jsx delete mode 100644 packages/react/src/jsx-ast/utils/synthetic/__tests__/index.test.mjs diff --git a/.changeset/odd-snails-rest.md b/.changeset/odd-snails-rest.md new file mode 100644 index 00000000..bdfa5ebc --- /dev/null +++ b/.changeset/odd-snails-rest.md @@ -0,0 +1,5 @@ +--- +'@doc-kit/generator-react': patch +--- + +Redesign the index page diff --git a/packages/react/src/html/constants.mjs b/packages/react/src/html/constants.mjs index 3864313f..40756725 100644 --- a/packages/react/src/html/constants.mjs +++ b/packages/react/src/html/constants.mjs @@ -65,6 +65,10 @@ export const JSX_IMPORTS = { name: 'FunctionSignature', source: '@node-core/ui-components/Containers/FunctionSignature', }, + IndexPage: { + name: 'IndexPage', + source: resolve(ROOT, './ui/components/Layout/IndexPage'), + }, ArrowUpRightIcon: { name: 'ArrowUpRightIcon', source: '@heroicons/react/24/solid/ArrowUpRightIcon', diff --git a/packages/react/src/html/ui/components/Layout/IndexPage.jsx b/packages/react/src/html/ui/components/Layout/IndexPage.jsx new file mode 100644 index 00000000..fde3721f --- /dev/null +++ b/packages/react/src/html/ui/components/Layout/IndexPage.jsx @@ -0,0 +1,25 @@ +import styles from './IndexPage.module.css'; +import StabilityBadge from '../StabilityBadge'; + +import { project, version } from '#theme/config'; + +/** + * Body of the synthetic `index.html` page + */ +export default ({ modules }) => ( + <> +

+ The API reference documentation for {project} v{version.version}. +

+ + + +); diff --git a/packages/react/src/html/ui/components/Layout/IndexPage.module.css b/packages/react/src/html/ui/components/Layout/IndexPage.module.css new file mode 100644 index 00000000..f3720abf --- /dev/null +++ b/packages/react/src/html/ui/components/Layout/IndexPage.module.css @@ -0,0 +1,22 @@ +.moduleIndex { + list-style: none; + padding: 0; + + li { + padding: 0.5rem 0; + } + + /* Draw a rule between items */ + li + li { + border-top: 1px solid var(--color-brand-200); + } +} + +:where([data-theme='dark'], [data-theme='dark'] *) .moduleIndex li + li { + border-color: var(--color-brand-800); +} + +.badge { + display: inline-block; + margin-left: 0.25rem; +} diff --git a/packages/react/src/html/ui/components/MetaBar/index.jsx b/packages/react/src/html/ui/components/MetaBar/index.jsx index 5f705efd..35c6202e 100644 --- a/packages/react/src/html/ui/components/MetaBar/index.jsx +++ b/packages/react/src/html/ui/components/MetaBar/index.jsx @@ -1,9 +1,9 @@ import { CodeBracketIcon, DocumentIcon } from '@heroicons/react/24/outline'; -import Badge from '@node-core/ui-components/Common/Badge'; import MetaBar from '@node-core/ui-components/Containers/MetaBar'; import GitHubIcon from '@node-core/ui-components/Icons/Social/GitHub'; import styles from './index.module.css'; +import StabilityBadge from '../StabilityBadge'; import { editURL } from '#theme/config'; @@ -12,40 +12,17 @@ const iconMap = { MD: DocumentIcon, }; -const STABILITY_KINDS = ['error', 'warning', null, 'info']; -const STABILITY_LABELS = ['D', 'E', null, 'L']; -const STABILITY_TOOLTIPS = ['Deprecated', 'Experimental', null, 'Legacy']; - /** * Renders a heading value with an optional stability badge * @param {{ value: string, stability: number }} props */ -const HeadingValue = ({ value, stability }) => { - if (stability === 2) { - return value; - } - - const ariaLabel = STABILITY_TOOLTIPS[stability] - ? `Stability: ${STABILITY_TOOLTIPS[stability]}` - : undefined; - - return ( - <> - {value} +const HeadingValue = ({ value, stability }) => ( + <> + {value} - - {STABILITY_LABELS[stability]} - - - ); -}; + + +); /** * MetaBar component that displays table of contents and page metadata diff --git a/packages/react/src/html/ui/components/StabilityBadge.jsx b/packages/react/src/html/ui/components/StabilityBadge.jsx new file mode 100644 index 00000000..910ecb12 --- /dev/null +++ b/packages/react/src/html/ui/components/StabilityBadge.jsx @@ -0,0 +1,29 @@ +import Badge from '@node-core/ui-components/Common/Badge'; + +const STABILITY_KINDS = ['error', 'warning', null, 'info']; +const STABILITY_LABELS = ['D', 'E', null, 'L']; +const STABILITY_TOOLTIPS = ['Deprecated', 'Experimental', null, 'Legacy']; + +/** + * Compact stability badge used next to API names + * + * @param {{ stability: number, className?: string }} props + */ +export default ({ stability, className }) => { + if (!STABILITY_LABELS[stability]) { + return null; + } + + return ( + + {STABILITY_LABELS[stability]} + + ); +}; diff --git a/packages/react/src/jsx-ast/utils/synthetic/__tests__/index.test.mjs b/packages/react/src/jsx-ast/utils/synthetic/__tests__/index.test.mjs deleted file mode 100644 index f083e905..00000000 --- a/packages/react/src/jsx-ast/utils/synthetic/__tests__/index.test.mjs +++ /dev/null @@ -1,114 +0,0 @@ -import assert from 'node:assert/strict'; -import { describe, it } from 'node:test'; - -import { buildIndexPage, buildStabilityOverview } from '../index.mjs'; - -const fakeHead = (api, name, stabilityIndex, depth = 1) => ({ - api, - heading: { depth, data: { name, text: name, slug: api } }, - stability: - stabilityIndex == null - ? null - : { - data: { - index: String(stabilityIndex), - description: `${name} stable. Long-form description.`, - }, - }, -}); - -const findChild = (node, tagName) => - node.children.find(child => child.tagName === tagName); - -describe('buildIndexPage', () => { - it('returns a synthetic `index` head with an "Index" heading', () => { - const { head } = buildIndexPage([]); - - assert.equal(head.api, 'index'); - assert.equal(head.path, '/index'); - assert.equal(head.basename, 'index'); - assert.equal(head.heading.data.name, 'Index'); - assert.equal(head.synthetic, true); - }); - - it('sorts the stability overview rows alphabetically by API name', () => { - const { entries } = buildIndexPage([ - fakeHead('fs', 'fs', 2), - fakeHead('assert', 'assert', 2), - fakeHead('crypto', 'crypto', 2), - ]); - - const table = findChild(entries[0].content, 'table'); - const rows = findChild(table, 'tbody').children; - const names = rows.map( - row => row.children[0].children[0].children[0].value - ); - - assert.deepEqual(names, ['assert', 'crypto', 'fs']); - }); -}); - -describe('buildStabilityOverview', () => { - it('renders a header row and one body row per entry', () => { - const table = buildStabilityOverview([ - fakeHead('fs', 'fs', 2), - fakeHead('crypto', 'crypto', 1), - ]); - - assert.equal(table.tagName, 'table'); - const headerRow = findChild(findChild(table, 'thead'), 'tr'); - assert.deepEqual( - headerRow.children.map(c => c.children[0].value), - ['API', 'Stability'] - ); - - assert.equal(findChild(table, 'tbody').children.length, 2); - }); - - it('formats the stability cell with a colored badge and first sentence', () => { - const table = buildStabilityOverview([fakeHead('fs', 'fs', 1)]); - - const row = findChild(table, 'tbody').children[0]; - const stabilityCell = row.children[1]; - const badge = stabilityCell.children[0]; - - assert.equal(badge.name, 'Badge'); - assert.deepEqual( - badge.attributes.map(({ name, value }) => [name, value]), - [ - ['size', 'small'], - ['kind', 'warning'], - ['aria-label', 'Stability: 1'], - ] - ); - assert.equal(badge.children[0].value, '1'); - assert.equal(stabilityCell.children[1].value, ' fs stable'); - }); - - it('uses a default badge for stable entries', () => { - const table = buildStabilityOverview([fakeHead('fs', 'fs', 2)]); - - const row = findChild(table, 'tbody').children[0]; - const badge = row.children[1].children[0]; - const kind = badge.attributes.find(attr => attr.name === 'kind'); - - assert.equal(kind.value, 'default'); - }); - - it('builds a relative link to the module HTML page', () => { - const table = buildStabilityOverview([fakeHead('fs', 'fs', 2)]); - - const row = findChild(table, 'tbody').children[0]; - const link = row.children[0].children[0]; - - assert.equal(link.tagName, 'a'); - assert.equal(link.properties.href, 'fs.html'); - assert.equal(link.children[0].value, 'fs'); - }); - - it('renders an empty body when no entries are passed', () => { - const table = buildStabilityOverview([]); - - assert.equal(findChild(table, 'tbody').children.length, 0); - }); -}); diff --git a/packages/react/src/jsx-ast/utils/synthetic/index.mjs b/packages/react/src/jsx-ast/utils/synthetic/index.mjs index a3278202..e0cc94f0 100644 --- a/packages/react/src/jsx-ast/utils/synthetic/index.mjs +++ b/packages/react/src/jsx-ast/utils/synthetic/index.mjs @@ -1,65 +1,25 @@ 'use strict'; -import { h as createElement } from 'hastscript'; +import getConfig from '@doc-kit/core/utils/configuration/index.mjs'; import { createSyntheticHead, wrapAsEntry } from './synthetic.mjs'; import { JSX_IMPORTS } from '../../../html/constants.mjs'; import { createJSXElement } from '../ast.mjs'; import { getSortedHeadNodes } from '../getSortedHeadNodes.mjs'; -const STABILITY_BADGE_KINDS = [ - 'error', - 'warning', - 'default', - 'info', - 'neutral', - 'neutral', -]; - -/** - * Maps a Node.js stability index to a UI badge kind. - * - * @param {string} index - */ -const getStabilityBadgeKind = index => - STABILITY_BADGE_KINDS[parseInt(index, 10)] ?? 'neutral'; - /** - * Builds the Stability Overview table from module heads that declare a - * top-level stability index, mirroring the `legacy-html-all` overview. + * Maps the sorted module heads to the plain props consumed by the `IndexPage` + * component: display name, page href, and numeric stability index (defaulting + * to stable, mirroring the ToC). * * @param {Array} headEntries */ -export const buildStabilityOverview = headEntries => - createElement('table', [ - createElement('thead', [ - createElement('tr', [ - createElement('th', 'API'), - createElement('th', 'Stability'), - ]), - ]), - createElement( - 'tbody', - headEntries.map(({ heading, api, stability }) => - createElement('tr', [ - createElement( - 'td', - createElement('a', { href: `${api}.html` }, heading.data.name) - ), - createElement( - 'td', - createJSXElement(JSX_IMPORTS.Badge.name, { - size: 'small', - kind: getStabilityBadgeKind(stability.data.index), - 'aria-label': `Stability: ${stability.data.index}`, - children: stability.data.index, - }), - ` ${stability.data.description.split('. ')[0]}` - ), - ]) - ) - ), - ]); +export const buildModuleProps = headEntries => + headEntries.map(({ heading, api, stability }) => ({ + name: heading.data.name, + href: `${api}.html`, + stability: parseInt(stability?.data.index ?? '2', 10), + })); /** * Builds the page descriptor for `index.html` @@ -67,14 +27,20 @@ export const buildStabilityOverview = headEntries => * @param {Array} entries */ export const buildIndexPage = entries => { - const head = createSyntheticHead('index', 'Index'); - const moduleEntries = getSortedHeadNodes(entries); + const config = getConfig('jsx-ast'); + const head = createSyntheticHead( + 'index', + `${config.project} Documentation Index` + ); return { head, entries: [ wrapAsEntry(head, [ - buildStabilityOverview(moduleEntries.filter(entry => entry.stability)), + createJSXElement(JSX_IMPORTS.IndexPage.name, { + inline: false, + modules: buildModuleProps(getSortedHeadNodes(entries)), + }), ]), ], };