Skip to content

Commit 86c1a16

Browse files
justin808claude
andauthored
Add package reference links to docs (#110)
* Surface package registries in docs home * Show package table with live versions on docs home and landing The docs home only listed package names as bullets at the very bottom, with no version info. Surface every gem and npm package prominently with its current version. - Render a single Package | Version | Registry | Description table on the docs home, replacing the plain bulleted list (still before "Need more help?"). - Versions come from live shields.io badges, so they stay current with no manual updates. - Add a Packages section to the landing page right after Quick Start. - Introduce prototypes/docusaurus/src/data/packages.json as the single source of truth shared by the docs-home transform and the landing page. - Update prepare-docs tests for the table + badge output. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Move package version badges from home page to footer The full "Gems and npm packages" table was too heavy for the marketing home page. Replace it with a slender, always-visible reference in the site footer. - Remove the PackagesSection (and its table CSS) from the landing page. - Add a "Packages" column to the footer with one live shields.io version badge per package, each labeled with both the name and registry (e.g. "react_on_rails (gem) 16.6.0"), linking to its registry page. - Footer badges are generated from the shared packages.json, the same source as the docs-home Packages table, so the two never drift. The detailed Package | Version | Registry | Description table stays on the docs home, unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Format footer packages as aligned name + version rows The first footer pass rendered each package as one wide combined badge, so the version pills landed at ragged, inconsistent positions and the longest name overflowed the column. - Split each row into a left-aligned monospace name (with a muted gem/npm tag) and a right-aligned live version pill, so the pills line up in a tidy column. - Use flat-square shields badges for a cleaner look. - Size the Packages footer column to its content so the longest name (react-on-rails-pro-node-renderer) and its pill never clip and all pills share one right edge. Verified in light mode, dark mode, and mobile. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 116683b commit 86c1a16

5 files changed

Lines changed: 201 additions & 2 deletions

File tree

prototypes/docusaurus/docusaurus.config.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {themes as prismThemes} from 'prism-react-renderer';
22
import type {Config} from '@docusaurus/types';
33
import type * as Preset from '@docusaurus/preset-classic';
44
import {GlobExcludeDefault} from '@docusaurus/utils';
5+
import packages from './src/data/packages.json';
56

67
// Use Algolia DocSearch when configured, otherwise fall back to local search.
78
// Set ALGOLIA_APP_ID, ALGOLIA_SEARCH_API_KEY, and ALGOLIA_INDEX_NAME env vars
@@ -22,6 +23,24 @@ const localSearchTheme: NonNullable<Config['themes']>[number] = [
2223
},
2324
];
2425

26+
// Slender version reference for the site footer. Reads the shared packages.json
27+
// (same source as the docs-home Packages table) and renders one row per package:
28+
// the package name + registry on the left, a live shields.io version pill on the
29+
// right. CSS aligns the pills into a neat column (see custom.css).
30+
const packageFooterItems = packages.map((pkg) => {
31+
const isGem = pkg.registry === 'rubygems';
32+
const registryShort = isGem ? 'gem' : 'npm';
33+
const page = isGem
34+
? `https://rubygems.org/gems/${pkg.name}`
35+
: `https://www.npmjs.com/package/${pkg.name}`;
36+
const badge = isGem
37+
? `https://img.shields.io/gem/v/${pkg.name}?style=flat-square&label=`
38+
: `https://img.shields.io/npm/v/${pkg.name}?style=flat-square&label=`;
39+
return {
40+
html: `<a class="footer__package" href="${page}" target="_blank" rel="noopener noreferrer"><span class="footer__package-name">${pkg.name} <span class="footer__package-registry">(${registryShort})</span></span><img class="footer__package-version" src="${badge}" alt="${pkg.name} ${registryShort} version" loading="lazy" /></a>`,
41+
};
42+
});
43+
2544
const config: Config = {
2645
title: 'React on Rails',
2746
tagline: 'Integrate React with Rails, including SSR, RSC, and production-grade docs.',
@@ -197,6 +216,10 @@ const config: Config = {
197216
},
198217
],
199218
},
219+
{
220+
title: 'Packages',
221+
items: packageFooterItems,
222+
},
200223
],
201224
copyright: `Copyright © ${new Date().getFullYear()} ShakaCode. Built with Docusaurus.`,
202225
},

prototypes/docusaurus/src/css/custom.css

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,3 +345,46 @@ body {
345345
background: var(--site-surface);
346346
}
347347
}
348+
349+
/* Size the Packages column to its content (instead of an equal 1/4 share) so the
350+
longest package name plus its version pill never clips, and every pill aligns
351+
to the same right edge. :has() targets only the column holding our badges. */
352+
.footer__col:has(.footer__package) {
353+
flex: 0 0 auto;
354+
width: auto;
355+
max-width: none;
356+
}
357+
358+
/* Footer "Packages" column: package name on the left, a live version pill on
359+
the right. justify-content lines the pills up into a tidy column. */
360+
.footer__package {
361+
display: flex;
362+
align-items: center;
363+
justify-content: space-between;
364+
gap: 0.85rem;
365+
padding: 0.12rem 0;
366+
white-space: nowrap;
367+
color: var(--ifm-footer-link-color);
368+
}
369+
370+
.footer__package:hover {
371+
text-decoration: none;
372+
}
373+
374+
.footer__package:hover .footer__package-name {
375+
text-decoration: underline;
376+
}
377+
378+
.footer__package-name {
379+
font-size: 0.82rem;
380+
font-family: var(--ifm-font-family-monospace);
381+
}
382+
383+
.footer__package-registry {
384+
color: var(--ifm-color-content-secondary);
385+
}
386+
387+
.footer__package-version {
388+
height: 18px;
389+
flex: none;
390+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
[
2+
{
3+
"name": "react_on_rails",
4+
"registry": "rubygems",
5+
"description": "Rails integration gem for React on Rails open source."
6+
},
7+
{
8+
"name": "react-on-rails",
9+
"registry": "npm",
10+
"description": "JavaScript runtime and helpers for the open source gem."
11+
},
12+
{
13+
"name": "react_on_rails_pro",
14+
"registry": "rubygems",
15+
"description": "Pro Rails gem for SSR, RSC, streaming, and Node Renderer integration."
16+
},
17+
{
18+
"name": "react-on-rails-pro",
19+
"registry": "npm",
20+
"description": "Pro client package for higher-throughput SSR and related integrations."
21+
},
22+
{
23+
"name": "react-on-rails-pro-node-renderer",
24+
"registry": "npm",
25+
"description": "Dedicated Node.js renderer used by React on Rails Pro."
26+
},
27+
{
28+
"name": "react-on-rails-rsc",
29+
"registry": "npm",
30+
"description": "React Server Components support package."
31+
},
32+
{
33+
"name": "create-react-on-rails-app",
34+
"registry": "npm",
35+
"description": "CLI for scaffolding a new Rails and React app."
36+
}
37+
]

scripts/prepare-docs.mjs

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import fs from "node:fs/promises";
2+
import { readFileSync } from "node:fs";
23
import path from "node:path";
34
import { fileURLToPath } from "node:url";
45
import {
@@ -652,6 +653,65 @@ async function normalizeCodeFences(docsRoot) {
652653
}
653654
}
654655

656+
// Registry-specific URL and shields.io badge builders. The bare `?label=` keeps
657+
// the badge as a colored version pill only; the Registry table column carries
658+
// the npm/RubyGems distinction so the two never repeat.
659+
const registryConfig = {
660+
npm: {
661+
label: "npm",
662+
pageUrl: (name) => `https://www.npmjs.com/package/${name}`,
663+
badgeUrl: (name) => `https://img.shields.io/npm/v/${name}?label=`
664+
},
665+
rubygems: {
666+
label: "RubyGems",
667+
pageUrl: (name) => `https://rubygems.org/gems/${name}`,
668+
badgeUrl: (name) => `https://img.shields.io/gem/v/${name}?label=`
669+
}
670+
};
671+
672+
// Single source of truth for the package list, shared with the landing page
673+
// (src/pages/index.tsx reads the same file). Versions are not stored here; they
674+
// render live from the registries via the shields.io badges above.
675+
const packageReferences = JSON.parse(
676+
readFileSync(
677+
path.join(workspaceRoot, "prototypes", "docusaurus", "src", "data", "packages.json"),
678+
"utf8"
679+
)
680+
);
681+
682+
function packageReferencesMarkdown() {
683+
const rows = packageReferences
684+
.map((entry) => {
685+
const registry = registryConfig[entry.registry];
686+
const pageUrl = registry.pageUrl(entry.name);
687+
const badgeUrl = registry.badgeUrl(entry.name);
688+
return `| [\`${entry.name}\`](${pageUrl}) | [![${entry.name} version](${badgeUrl})](${pageUrl}) | ${registry.label} | ${entry.description} |`;
689+
})
690+
.join("\n");
691+
692+
return `## Packages
693+
694+
React on Rails ships as a Ruby gem with companion npm packages. Versions are pulled live from each registry.
695+
696+
| Package | Version | Registry | Description |
697+
| --- | --- | --- | --- |
698+
${rows}
699+
`;
700+
}
701+
702+
function injectPackageReferences(markdown) {
703+
if (/^## Packages$/m.test(markdown)) {
704+
return markdown;
705+
}
706+
707+
const section = packageReferencesMarkdown();
708+
if (markdown.includes("\n## Need more help?")) {
709+
return markdown.replace("\n## Need more help?", `\n${section}\n## Need more help?`);
710+
}
711+
712+
return `${markdown.trimEnd()}\n\n${section}`;
713+
}
714+
655715
export function docsHomeMarkdown(sourceMarkdown, { hasArchive }) {
656716
const archiveBlock = hasArchive ? "- [Historical Reference](./archive/README.md)\n" : "";
657717
const friendlyLicenseSection = `## Friendly License Model
@@ -660,13 +720,13 @@ export function docsHomeMarkdown(sourceMarkdown, { hasArchive }) {
660720
- Production deployments require a paid license. See [Pro pricing and sign up](https://pro.reactonrails.com/) for current options. If your organization is budget-constrained, [contact us](mailto:justin@shakacode.com) about free or low-cost licenses.
661721
`;
662722

663-
const updated = sourceMarkdown
723+
const updated = injectPackageReferences(sourceMarkdown
664724
.trim()
665725
.replaceAll("(./oss/", "(./")
666726
.replace("](https://reactonrails.com/examples)", "](/examples)")
667727
.replace(/\n- \[Documentation website\]\(https:\/\/reactonrails\.com\/docs\/\)\s*/g, "\n")
668728
.replace(/## Friendly evaluation policy\n\n[\s\S]*?(?=\n## )/, `${friendlyLicenseSection}\n`)
669-
.replace("## Need more help?\n\n", `## Need more help?\n\n${archiveBlock}`);
729+
.replace("## Need more help?\n\n", `## Need more help?\n\n${archiveBlock}`));
670730

671731
return `---\ncustom_edit_url: null\n---\n\n${updated}\n`;
672732
}

scripts/prepare-docs.test.mjs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,42 @@ test("docs homepage uses current friendly license model copy", () => {
8888
assert.doesNotMatch(updated, /Friendly evaluation policy/);
8989
});
9090

91+
test("docs homepage renders a package table with linked names and live version badges", () => {
92+
const sourceMarkdown = `# React on Rails
93+
94+
## Need more help?
95+
`;
96+
97+
const updated = docsHomeMarkdown(sourceMarkdown, { hasArchive: false });
98+
99+
// Heading + table scaffold
100+
assert.match(updated, /## Packages/);
101+
assert.match(updated, /\| Package \| Version \| Registry \| Description \|/);
102+
103+
// Every package: name links to its registry page, version renders as a live
104+
// shields.io badge, and the registry column labels the source.
105+
assert.match(
106+
updated,
107+
/\| \[`react_on_rails`\]\(https:\/\/rubygems\.org\/gems\/react_on_rails\) \| \[!\[react_on_rails version\]\(https:\/\/img\.shields\.io\/gem\/v\/react_on_rails\?label=\)\]\(https:\/\/rubygems\.org\/gems\/react_on_rails\) \| RubyGems \|/
108+
);
109+
assert.match(
110+
updated,
111+
/\| \[`react-on-rails`\]\(https:\/\/www\.npmjs\.com\/package\/react-on-rails\) \| \[!\[react-on-rails version\]\(https:\/\/img\.shields\.io\/npm\/v\/react-on-rails\?label=\)\]\(https:\/\/www\.npmjs\.com\/package\/react-on-rails\) \| npm \|/
112+
);
113+
114+
// Remaining packages each appear with a registry page link and a version badge.
115+
for (const name of [
116+
"react_on_rails_pro",
117+
"react-on-rails-pro",
118+
"react-on-rails-pro-node-renderer",
119+
"react-on-rails-rsc",
120+
"create-react-on-rails-app",
121+
]) {
122+
assert.match(updated, new RegExp(`\\[\`${name}\`\\]`));
123+
assert.match(updated, new RegExp(`img\\.shields\\.io/(npm|gem)/v/${name}\\?label=`));
124+
}
125+
});
126+
91127
test("site sidebar replaces the external changelog link with an internal doc reference", () => {
92128
const source = `const sidebars = {
93129
docsSidebar: [

0 commit comments

Comments
 (0)