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_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": {
|
||||
"version": "10.5.0",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
|
||||
@@ -25201,6 +25207,32 @@
|
||||
"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": {
|
||||
"version": "2.3.6",
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz",
|
||||
@@ -33575,6 +33620,41 @@
|
||||
"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": {
|
||||
"version": "11.0.0",
|
||||
"resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz",
|
||||
@@ -39129,6 +39209,8 @@
|
||||
"react": "^19.1.4",
|
||||
"react-dom": "^19.1.4",
|
||||
"react-markdown": "^10.1.0",
|
||||
"rehype-autolink-headings": "^7.1.0",
|
||||
"rehype-slug": "^6.0.0",
|
||||
"wrangler": "^4.59.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
"react": "^19.1.4",
|
||||
"react-dom": "^19.1.4",
|
||||
"react-markdown": "^10.1.0",
|
||||
"rehype-autolink-headings": "^7.1.0",
|
||||
"rehype-slug": "^6.0.0",
|
||||
"wrangler": "^4.59.1"
|
||||
},
|
||||
"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 ReactMarkdown from "react-markdown";
|
||||
import { getDoc } from "~/docs";
|
||||
import { docsRehypePlugins } from "~/docs-rehype";
|
||||
import { pageMeta } from "~/meta";
|
||||
|
||||
export const Route = createFileRoute("/docs/$")({
|
||||
@@ -24,5 +25,5 @@ function DocsPage() {
|
||||
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 ReactMarkdown from "react-markdown";
|
||||
import { getDoc } from "~/docs";
|
||||
import { docsRehypePlugins } from "~/docs-rehype";
|
||||
import { pageMeta } from "~/meta";
|
||||
|
||||
export const Route = createFileRoute("/docs/")({
|
||||
@@ -17,5 +18,5 @@ export const Route = createFileRoute("/docs/")({
|
||||
function DocsIndex() {
|
||||
const doc = getDoc("");
|
||||
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;
|
||||
}
|
||||
|
||||
.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 {
|
||||
color: var(--color-foreground);
|
||||
font-size: 1.875rem;
|
||||
@@ -121,6 +131,39 @@
|
||||
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 {
|
||||
margin-top: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
|
||||
Reference in New Issue
Block a user