diff --git a/docs/src/components/demo/Demo.css b/docs/src/components/demo/Demo.css index 358e4e5858..9caf537613 100644 --- a/docs/src/components/demo/Demo.css +++ b/docs/src/components/demo/Demo.css @@ -3,6 +3,15 @@ background-color: var(--color-content); border: 1px solid var(--color-gray-200); border-radius: var(--radius-md); + + /* Coordinate the placement of Quick Nav that follows the main Demo */ + &[data-before-quick-nav] { + float: left; + width: 100%; + & + * + * { + clear: left; + } + } } .DemoPlayground { diff --git a/docs/src/components/quick-nav/rehypeQuickNav.mjs b/docs/src/components/quick-nav/rehypeQuickNav.mjs index c588eafe5c..2f4e349991 100644 --- a/docs/src/components/quick-nav/rehypeQuickNav.mjs +++ b/docs/src/components/quick-nav/rehypeQuickNav.mjs @@ -1,11 +1,16 @@ // @ts-check +/** + * @import {Nodes} from 'hast' + */ import { createMdxElement } from 'docs/src/mdx/createMdxElement.mjs'; +import { toString } from 'hast-util-to-string'; const ROOT = 'QuickNav.Root'; const TITLE = 'QuickNav.Title'; const LIST = 'QuickNav.List'; const ITEM = 'QuickNav.Item'; const LINK = 'QuickNav.Link'; +const DOC_DEMO = 'Demo'; const DOC_SUBTITLE = 'Subtitle'; /** @@ -21,11 +26,6 @@ const DOC_SUBTITLE = 'Subtitle'; */ export function rehypeQuickNav() { return (tree, file) => { - const h1 = tree.children.find( - /** @param {{ tagName: string; }} node */ - (node) => node.tagName === 'h1', - ); - /** @type {TocEntry[]} */ const toc = file.data.toc; const root = createMdxElement({ @@ -33,14 +33,53 @@ export function rehypeQuickNav() { children: toc.flatMap(getNodeFromEntry).filter(Boolean), }); - if (!toc.length) { + if (!toc?.length) { return; } - // Place quick nav after the `` that immediately follows the first `

`, - // or after the first `

` if a matching `` wasn't found. - let index = tree.children.indexOf(h1) + 2; // Adding "2" because there's also a line break below h1 - index = tree.children[index]?.name === DOC_SUBTITLE ? index + 1 : index; + // Determine the placement of Quick Nav up next + + /** @type {{ tagName?: string; name?: string; attributes?: Record[] }[]} */ + const contentNodes = tree.children.filter( + /** @param {Nodes} child */ + (child) => { + // Filter out nodes that don't produce texts + const hasChildren = 'children' in child && child.children?.length; + const isText = child.type === 'text'; + const isDemo = 'name' in child && child.name === 'Demo'; + return ((hasChildren || isText) && toString(child).trim()) || isDemo; + }, + ); + + const h1 = tree.children.find( + /** @param {{ tagName: string; }} node */ + (node) => node.tagName === 'h1', + ); + + const subtitle = contentNodes.find((node, i) => { + const prev = contentNodes[i - 1]; + return node.name === DOC_SUBTITLE && prev === h1; + }); + + let nodeBefore = contentNodes.find((node, i) => { + const prev = contentNodes[i - 1]; + return node.name === DOC_DEMO && prev === subtitle; + }); + + // Add a styling hook if a `` element was found + if (nodeBefore) { + nodeBefore.attributes ??= []; + nodeBefore.attributes.push({ + type: 'mdxJsxAttribute', + name: 'data-before-quick-nav', + value: '', + }); + } else { + // Otherwise, place the Quick Nav node after a fallback + nodeBefore = subtitle ?? h1; + } + + const index = tree.children.indexOf(nodeBefore) + 1; tree.children.splice(index, 0, root); }; } diff --git a/docs/src/mdx-components.tsx b/docs/src/mdx-components.tsx index 15e0a69949..f5ba7ef46d 100644 --- a/docs/src/mdx-components.tsx +++ b/docs/src/mdx-components.tsx @@ -66,7 +66,9 @@ export const mdxComponents: MDXComponents = { td: Table.Cell, // Custom components - Demo: (props) => , + Demo: (props) => ( + + ), QuickNav, AttributesTable: (props) => , CssVariablesTable: (props) => ,