feat(website): add anchor links to docs headings
Hover a heading to reveal a # link on the left that updates the URL hash without layout shift, matching GitHub README behavior.
This commit is contained in:
Generated
+82
@@ -24760,6 +24760,12 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/github-slugger": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/glob": {
|
"node_modules/glob": {
|
||||||
"version": "10.5.0",
|
"version": "10.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
|
||||||
@@ -25201,6 +25207,32 @@
|
|||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/hast-util-heading-rank": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/hast-util-heading-rank/-/hast-util-heading-rank-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-EJKb8oMUXVHcWZTDepnr+WNbfnXKFNf9duMesmr4S8SXTJBJ9M4Yok08pu9vxdJwdlGRhVumk9mEhkEvKGifwA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/hast": "^3.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/unified"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/hast-util-is-element": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/hast": "^3.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/unified"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/hast-util-to-jsx-runtime": {
|
"node_modules/hast-util-to-jsx-runtime": {
|
||||||
"version": "2.3.6",
|
"version": "2.3.6",
|
||||||
"resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz",
|
"resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz",
|
||||||
@@ -25228,6 +25260,19 @@
|
|||||||
"url": "https://opencollective.com/unified"
|
"url": "https://opencollective.com/unified"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/hast-util-to-string": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/hast-util-to-string/-/hast-util-to-string-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/hast": "^3.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/unified"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/hast-util-whitespace": {
|
"node_modules/hast-util-whitespace": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz",
|
||||||
@@ -33575,6 +33620,41 @@
|
|||||||
"regjsparser": "bin/parser"
|
"regjsparser": "bin/parser"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/rehype-autolink-headings": {
|
||||||
|
"version": "7.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/rehype-autolink-headings/-/rehype-autolink-headings-7.1.0.tgz",
|
||||||
|
"integrity": "sha512-rItO/pSdvnvsP4QRB1pmPiNHUskikqtPojZKJPPPAVx9Hj8i8TwMBhofrrAYRhYOOBZH9tgmG5lPqDLuIWPWmw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/hast": "^3.0.0",
|
||||||
|
"@ungap/structured-clone": "^1.0.0",
|
||||||
|
"hast-util-heading-rank": "^3.0.0",
|
||||||
|
"hast-util-is-element": "^3.0.0",
|
||||||
|
"unified": "^11.0.0",
|
||||||
|
"unist-util-visit": "^5.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/unified"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/rehype-slug": {
|
||||||
|
"version": "6.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/rehype-slug/-/rehype-slug-6.0.0.tgz",
|
||||||
|
"integrity": "sha512-lWyvf/jwu+oS5+hL5eClVd3hNdmwM1kAC0BUvEGD19pajQMIzcNUd/k9GsfQ+FfECvX+JE+e9/btsKH0EjJT6A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/hast": "^3.0.0",
|
||||||
|
"github-slugger": "^2.0.0",
|
||||||
|
"hast-util-heading-rank": "^3.0.0",
|
||||||
|
"hast-util-to-string": "^3.0.0",
|
||||||
|
"unist-util-visit": "^5.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/unified"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/remark-parse": {
|
"node_modules/remark-parse": {
|
||||||
"version": "11.0.0",
|
"version": "11.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz",
|
||||||
@@ -39129,6 +39209,8 @@
|
|||||||
"react": "^19.1.4",
|
"react": "^19.1.4",
|
||||||
"react-dom": "^19.1.4",
|
"react-dom": "^19.1.4",
|
||||||
"react-markdown": "^10.1.0",
|
"react-markdown": "^10.1.0",
|
||||||
|
"rehype-autolink-headings": "^7.1.0",
|
||||||
|
"rehype-slug": "^6.0.0",
|
||||||
"wrangler": "^4.59.1"
|
"wrangler": "^4.59.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -20,6 +20,8 @@
|
|||||||
"react": "^19.1.4",
|
"react": "^19.1.4",
|
||||||
"react-dom": "^19.1.4",
|
"react-dom": "^19.1.4",
|
||||||
"react-markdown": "^10.1.0",
|
"react-markdown": "^10.1.0",
|
||||||
|
"rehype-autolink-headings": "^7.1.0",
|
||||||
|
"rehype-slug": "^6.0.0",
|
||||||
"wrangler": "^4.59.1"
|
"wrangler": "^4.59.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import rehypeAutolinkHeadings from "rehype-autolink-headings";
|
||||||
|
import rehypeSlug from "rehype-slug";
|
||||||
|
import type { PluggableList } from "unified";
|
||||||
|
|
||||||
|
export const docsRehypePlugins: PluggableList = [
|
||||||
|
rehypeSlug,
|
||||||
|
[
|
||||||
|
rehypeAutolinkHeadings,
|
||||||
|
{
|
||||||
|
behavior: "prepend",
|
||||||
|
properties: { className: "heading-anchor", ariaLabel: "Link to this section" },
|
||||||
|
content: {
|
||||||
|
type: "element",
|
||||||
|
tagName: "span",
|
||||||
|
properties: { className: "heading-anchor-icon", ariaHidden: "true" },
|
||||||
|
children: [{ type: "text", value: "#" }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
];
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
import ReactMarkdown from "react-markdown";
|
import ReactMarkdown from "react-markdown";
|
||||||
import { getDoc } from "~/docs";
|
import { getDoc } from "~/docs";
|
||||||
|
import { docsRehypePlugins } from "~/docs-rehype";
|
||||||
import { pageMeta } from "~/meta";
|
import { pageMeta } from "~/meta";
|
||||||
|
|
||||||
export const Route = createFileRoute("/docs/$")({
|
export const Route = createFileRoute("/docs/$")({
|
||||||
@@ -24,5 +25,5 @@ function DocsPage() {
|
|||||||
return <p className="text-muted-foreground">Doc not found.</p>;
|
return <p className="text-muted-foreground">Doc not found.</p>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <ReactMarkdown>{doc.content}</ReactMarkdown>;
|
return <ReactMarkdown rehypePlugins={docsRehypePlugins}>{doc.content}</ReactMarkdown>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
import ReactMarkdown from "react-markdown";
|
import ReactMarkdown from "react-markdown";
|
||||||
import { getDoc } from "~/docs";
|
import { getDoc } from "~/docs";
|
||||||
|
import { docsRehypePlugins } from "~/docs-rehype";
|
||||||
import { pageMeta } from "~/meta";
|
import { pageMeta } from "~/meta";
|
||||||
|
|
||||||
export const Route = createFileRoute("/docs/")({
|
export const Route = createFileRoute("/docs/")({
|
||||||
@@ -17,5 +18,5 @@ export const Route = createFileRoute("/docs/")({
|
|||||||
function DocsIndex() {
|
function DocsIndex() {
|
||||||
const doc = getDoc("");
|
const doc = getDoc("");
|
||||||
if (!doc) return <p className="text-muted-foreground">Doc not found.</p>;
|
if (!doc) return <p className="text-muted-foreground">Doc not found.</p>;
|
||||||
return <ReactMarkdown>{doc.content}</ReactMarkdown>;
|
return <ReactMarkdown rehypePlugins={docsRehypePlugins}>{doc.content}</ReactMarkdown>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,6 +94,16 @@
|
|||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.docs-prose h1,
|
||||||
|
.docs-prose h2,
|
||||||
|
.docs-prose h3,
|
||||||
|
.docs-prose h4,
|
||||||
|
.docs-prose h5,
|
||||||
|
.docs-prose h6 {
|
||||||
|
position: relative;
|
||||||
|
scroll-margin-top: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
.docs-prose h1 {
|
.docs-prose h1 {
|
||||||
color: var(--color-foreground);
|
color: var(--color-foreground);
|
||||||
font-size: 1.875rem;
|
font-size: 1.875rem;
|
||||||
@@ -121,6 +131,39 @@
|
|||||||
margin-bottom: 0.75rem;
|
margin-bottom: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.docs-prose .heading-anchor {
|
||||||
|
position: absolute;
|
||||||
|
right: 100%;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
padding-right: 0.375rem;
|
||||||
|
text-decoration: none;
|
||||||
|
color: rgba(255, 255, 255, 0.4);
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.docs-prose .heading-anchor:hover {
|
||||||
|
color: var(--color-foreground);
|
||||||
|
}
|
||||||
|
|
||||||
|
.docs-prose h1:hover .heading-anchor,
|
||||||
|
.docs-prose h2:hover .heading-anchor,
|
||||||
|
.docs-prose h3:hover .heading-anchor,
|
||||||
|
.docs-prose h4:hover .heading-anchor,
|
||||||
|
.docs-prose h5:hover .heading-anchor,
|
||||||
|
.docs-prose h6:hover .heading-anchor,
|
||||||
|
.docs-prose .heading-anchor:focus {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.docs-prose .heading-anchor-icon {
|
||||||
|
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
.docs-prose p {
|
.docs-prose p {
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
|
|||||||
Reference in New Issue
Block a user