Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit cf7cce2

Browse files
author
Brijesh Bittu
committedMar 6, 2025·
Add in-page quick-nav
1 parent 8a76647 commit cf7cce2

File tree

10 files changed

+156
-22
lines changed

10 files changed

+156
-22
lines changed
 

‎docs/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"@stefanprobst/rehype-extract-toc": "^2.2.1",
1919
"clipboard-copy": "4.0.1",
2020
"clsx": "^2.1.1",
21+
"estree-util-value-to-estree": "^3.3.2",
2122
"hast-util-to-string": "^3.0.1",
2223
"next": "15.1.6",
2324
"react": "^19.0.0",

‎docs/src/components/ContentPage.tsx

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { mdxComponents } from 'docs/mdx-components';
1+
import { useMDXComponents } from 'docs/mdx-components';
22
import { readMarkdown } from 'docs/utils/mdx';
33
import { notFound } from 'next/navigation';
44

@@ -7,5 +7,6 @@ export default async function ContentPage({ basePath, slug }: { basePath: string
77
if (!MdxContent) {
88
return notFound();
99
}
10-
return <MdxContent components={mdxComponents} />;
10+
const components = useMDXComponents();
11+
return <MdxContent components={components} />;
1112
}

‎docs/src/components/QuickNav.pigment.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export const Root = styled.nav(({ theme }) => ({
1414
$quickNavItemLineHeight: t('$text.md.lineHeight'),
1515
$quickNavItemPaddingY: 'calc($quickNavItemHeight / 2 - $quickNavItemLineHeight / 2)',
1616
/* The variable is used in JS positioning logic */
17-
$top: -1,
17+
$top: '-1px',
1818
$marginTop: '5.75rem' /* Match hero code block top */,
1919
...applyText(theme, 'md'),
2020
zIndex: 1,

‎docs/src/mdx-components.tsx

+11-7
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
11
import * as React from 'react';
2-
import type { MDXComponents } from 'mdx/types';
2+
import type { MDXComponents as BaseMdxComponents } from 'mdx/types';
33

44
import * as CodeBlock from './components/CodeBlock';
55
import * as Styled from './mdx-components.pigment';
6+
import * as QuickNav from './components/QuickNav';
67
import { Link } from './components/Link';
78
import { getChildrenText } from './utils/getChildrenText';
89
import { Changelog } from './components/Changelog';
910
import { LinkIcon } from './icons/LinkIcon';
1011

12+
interface MDXComponents {
13+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
14+
[key: string]: React.FC<any> | MDXComponents;
15+
}
16+
1117
export const mdxComponents: MDXComponents = {
1218
a: (props) => <Link {...props} />,
1319
h1: (props) => (
@@ -58,20 +64,18 @@ export const mdxComponents: MDXComponents = {
5864
return <meta {...props} />;
5965
},
6066
Changelog,
61-
LinkIcon: (props) => {
62-
console.log(props);
63-
return <LinkIcon width={16} />;
64-
},
67+
LinkIcon: () => <LinkIcon width={16} />,
68+
QuickNav,
6569
};
6670

6771
export const inlineMdxComponents: MDXComponents = {
6872
...mdxComponents,
6973
p: (props) => <p {...props} />,
7074
};
7175

72-
export function useMDXComponents(comp: MDXComponents): MDXComponents {
76+
export function useMDXComponents(comp: MDXComponents = {}) {
7377
return {
7478
...comp,
7579
...mdxComponents,
76-
};
80+
} as BaseMdxComponents;
7781
}

‎docs/src/utils/mdx.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import rehyeAutolinkHeading from 'rehype-autolink-headings';
1313

1414
import { rehypeSubtitle } from './rehype/rehypeSubtitle';
1515
import { rehypeInlineCode } from './rehype/rehypeInlineCode';
16+
import { rehypeQuickNav } from './rehype/rehypeQuickNav';
1617

1718
const theme: Theme = {
1819
name: 'pigment-css',
@@ -361,8 +362,8 @@ export async function renderMdx(mdxSource: string) {
361362
},
362363
},
363364
],
364-
// rehypeAutoLinkContent,
365365
rehypeExtractToc,
366+
rehypeQuickNav,
366367
rehypeSubtitle,
367368
rehypeInlineCode,
368369
],
+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// @ts-check
2+
import { valueToEstree } from 'estree-util-value-to-estree';
3+
4+
export function createMdxElement({
5+
name,
6+
children,
7+
props = {},
8+
}: {
9+
name: string;
10+
children: unknown[];
11+
props?: Record<string, unknown>;
12+
}) {
13+
// Native HTML elements are rendered as regular tags
14+
if (name.toLowerCase() === name) {
15+
return {
16+
type: 'element',
17+
tagName: name,
18+
properties: props,
19+
children,
20+
};
21+
}
22+
23+
return {
24+
type: 'mdxJsxFlowElement',
25+
name,
26+
attributes: Object.entries(props).map(([key, value]) => ({
27+
type: 'mdxJsxAttribute',
28+
name: key,
29+
value: getAttributeValue(value),
30+
})),
31+
data: { _mdxExplicitJsx: true },
32+
children,
33+
};
34+
}
35+
36+
/**
37+
* @param {unknown} value
38+
*/
39+
function getAttributeValue(value: unknown) {
40+
if (typeof value === 'string') {
41+
return value;
42+
}
43+
44+
return {
45+
type: 'mdxJsxAttributeValueExpression',
46+
value: JSON.stringify(value),
47+
data: {
48+
estree: {
49+
type: 'Program',
50+
body: [
51+
{
52+
type: 'ExpressionStatement',
53+
expression: valueToEstree(value),
54+
},
55+
],
56+
sourceType: 'module',
57+
},
58+
},
59+
};
60+
}

‎docs/src/utils/rehype/rehypeAutoLinkContent.ts

+1-9
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,6 @@
11
import { visitParents } from 'unist-util-visit-parents';
2-
import type { evaluate } from '@mdx-js/mdx';
2+
import { Pluggable } from './rehypeSubtitle';
33

4-
export type Pluggable = Exclude<
5-
Parameters<typeof evaluate>[1]['rehypePlugins'],
6-
undefined | null
7-
>[number];
8-
9-
/**
10-
* Unwrap potential paragraphs inside `<Subtitle>`
11-
*/
124
export const rehypeAutoLinkContent: Pluggable = () => {
135
return (tree) => {
146
visitParents(tree, (node) => {
+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import { TocEntry } from '@stefanprobst/rehype-extract-toc';
2+
import { Pluggable } from './rehypeSubtitle';
3+
import { createMdxElement } from './createMdxElement';
4+
5+
const ROOT = 'QuickNav.Root';
6+
const TITLE = 'QuickNav.Title';
7+
const LIST = 'QuickNav.List';
8+
const ITEM = 'QuickNav.Item';
9+
const LINK = 'QuickNav.Link';
10+
11+
export const rehypeQuickNav: Pluggable = () => {
12+
return (tree, file) => {
13+
const toc = file.data.toc;
14+
if (!toc) {
15+
return;
16+
}
17+
const root = createMdxElement({
18+
name: ROOT,
19+
children: toc.flatMap(getNodeFromEntry).filter(Boolean),
20+
});
21+
22+
if (!toc.length) {
23+
return;
24+
}
25+
26+
tree.children.unshift(root);
27+
};
28+
};
29+
30+
function getNodeFromEntry({ value, depth, id, children }: TocEntry) {
31+
const sub = createMdxElement({
32+
name: LIST,
33+
children: [],
34+
});
35+
36+
// Ignore <h4>'s and below
37+
if (depth < 3 && children?.length) {
38+
sub.children = children.map(getNodeFromEntry);
39+
}
40+
41+
if (depth === 1) {
42+
// Insert "(Top)" link
43+
sub.children?.unshift(getNodeFromEntry({ value: '(Top)', id: '', depth: 2 }));
44+
45+
return [
46+
// Insert a top-level title
47+
createMdxElement({
48+
name: TITLE,
49+
children: [{ type: 'text', value }],
50+
}),
51+
sub,
52+
];
53+
}
54+
55+
const link = createMdxElement({
56+
name: LINK,
57+
children: [{ type: 'text', value }],
58+
props: {
59+
href: `#${id}`,
60+
},
61+
});
62+
63+
return createMdxElement({
64+
name: ITEM,
65+
children: [link, sub],
66+
});
67+
}

‎packages/pigment-css-utils/src/theme.ts

-2
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@ import { serializeStyles } from '@emotion/serialize';
22
import setWith from 'lodash/setWith';
33
import { getCSSVar } from './utils/processStyle';
44

5-
import { getCSSVar } from './utils/processStyle';
6-
75
interface Theme extends Record<string, unknown> {}
86

97
const PIGMENT_LAYERS = ['globals', 'utils', 'base', 'variants', 'compoundvariants', 'sx'];

‎pnpm-lock.yaml

+10
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)