[components/toc][f]: following current h on scroll

This commit is contained in:
olayway 2022-05-24 17:50:56 +02:00
parent dba48484b9
commit 2ff44763df
4 changed files with 158 additions and 54 deletions

View File

@ -0,0 +1,25 @@
import React, { useEffect } from 'react';
export const Heading = ({ level, activeHeading, setActiveHeading }) => (props) => {
console.log(activeHeading, setActiveHeading)
useEffect(() => {
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
setActiveHeading(entry.target.id);
}
});
},
{ rootMargin: `0% 0% -80% 0%` }
);
observer.observe(document.getElementById(props.id));
return () => {
observer.unobserve(document.getElementById(props.id));
};
});
return React.createElement(`h${level}`, { ...props })
}

View File

@ -1,28 +1,71 @@
import Head from 'next/head'
import dynamic from 'next/dynamic'
import { NextSeo } from 'next-seo'
import siteConfig from "../config/siteConfig"
import LiteYouTubeEmbed from "react-lite-youtube-embed"
import { YOUTUBE_REGEX } from "../lib/constants"
import { NextSeo } from "next-seo";
import ReactPlayer from "react-player/lazy";
import LiteYouTubeEmbed from "react-lite-youtube-embed";
import { useState, useEffect } from "react";
const Anchor = dynamic(() => import('./Anchor').then(module => module.Anchor), {
ssr: false
})
import siteConfig from "../config/siteConfig";
import { YOUTUBE_REGEX } from "../lib/constants";
import getMDXComponents from "./_getMDXComponents";
import { Paragraph } from "./Paragraph";
import { Anchor } from "./Anchor";
const Paragraph = dynamic(() => import("./Paragraph").then(mod => mod.Paragraph))
// const Anchor = dynamic(() => import('./Anchor').then(module => module.Anchor), {
// ssr: false
// })
const components = {
Head,
p: Paragraph,
a: Anchor
}
// const Paragraph = dynamic(() => import("./Paragraph").then(mod => mod.Paragraph))
export default function MdxPage({ children }) {
const { Component, frontmatter: {
title, description, date, keywords, youtube, podcast, image, _raw
}} = children
// import { Toc } from './Toc'
let youtubeThumnbnail
export default function MdxPage({ children, editUrl }) {
const [activeHeading, setActiveHeading] = useState("");
const components = getMDXComponents({
params: {
h: {
activeHeading,
setActiveHeading,
},
},
});
useEffect(() => {
if (activeHeading) {
const tocLink = document.querySelector(
`.toc-link[href="#${activeHeading}"]`
);
tocLink.classList.add("active");
// setTimeout to fix scrolling behavior
// fix switching on-off when two headings are observed
// router push
}
return () => {
if (activeHeading) {
const tocLink = document.querySelector(
`.toc-link[href="#${activeHeading}"]`
);
tocLink.classList.remove("active");
}
};
}, [activeHeading]);
const {
Component,
frontmatter: {
title,
description,
date,
keywords,
youtube,
podcast,
image,
_raw,
},
} = children;
let youtubeThumnbnail;
const youtubeId =
youtube && YOUTUBE_REGEX.test(youtube) && youtube.split(/^|=|\//).pop();
@ -47,12 +90,14 @@ export default function MdxPage({ children }) {
const SeoTitle = title ?? titleFromUrl;
const imageUrl = image
? siteConfig.url + image
: youtubeThumnbnail ? youtubeThumnbnail : null
: youtubeThumnbnail
? youtubeThumnbnail
: null;
// enable editing content only for claims, concepts, and guide for now
const editUrl = ['claims', 'concepts', 'guide'].includes(_raw.sourceFileDir)
const editUrl = ["claims", "concepts", "guide"].includes(_raw.sourceFileDir)
? siteConfig.repoRoot + siteConfig.repoEditPath + _raw.sourceFilePath
: null
: null;
return (
<>
@ -66,25 +111,26 @@ export default function MdxPage({ children }) {
url: `${siteConfig.url}/${_raw.flattenedPath}`,
type: "article",
article: {
tags: keywords ? keywords.split(",") : []
tags: keywords ? keywords.split(",") : [],
},
images: imageUrl
? ([
? [
{
url: imageUrl,
width: 1200,
height: 627,
alt: title,
type: "image/png"
type: "image/png",
},
])
]
: siteConfig.nextSeo.openGraph.images,
}}
additionalMetaTags={[
{ name: "keywords", content: keywords ? keywords : "" }
{ name: "keywords", content: keywords ? keywords : "" },
]}
/>
<article className="px-8 md:pl-[14rem] lg:pr-[14rem] prose max-w-none dark:prose-invert prose-a:break-all mx-auto border-2 border-yellow-500">
{/*<article className="px-8 md:pl-[14rem] lg:pr-[14rem] prose max-w-none dark:prose-invert prose-a:break-all mx-auto border-2 border-yellow-500">*/}
<article className="px-8 lg:pr-[14rem] prose max-w-none dark:prose-invert prose-a:break-all mx-auto border-2 border-yellow-500">
<header>
<div className="mb-6">
{title && <h1 className="mb-0">{title}</h1>}
@ -93,17 +139,18 @@ export default function MdxPage({ children }) {
on {date}
</p>
)}
{description && (
<p className="">{description}</p>
)}
{youtubeId && (
<LiteYouTubeEmbed id={youtubeId} />
)}
{description && <p className="">{description}</p>}
{youtubeId && <LiteYouTubeEmbed id={youtubeId} />}
{podcast && (
<div className="pt-4">
<ul className="list-disc">
<li>
<a className="flex items-center" target="_blank" rel="noopener" href={podcast}>
<a
className="flex items-center"
target="_blank"
rel="noopener"
href={podcast}
>
<div className="w-4 mr-2">
<PodcastIcon />
</div>
@ -120,28 +167,33 @@ export default function MdxPage({ children }) {
<Component components={components} />
</div>
{editUrl && (
<div className='mt-12 mb-6'>
<a className="flex no-underline font-semibold text-yellow-li" href={editUrl} target="_blank">
<div className="mt-12 mb-6">
<a
className="flex no-underline font-semibold text-yellow-li"
href={editUrl}
target="_blank"
>
Edit this page
<span className="mx-1">
<svg xmlns="http://www.w3.org/2000/svg" className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2">
<path strokeLinecap="round" strokeLinejoin="round" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-6 w-6"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
strokeWidth="2"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"
/>
</svg>
</span>
</a>
</div>)}
</main>
<div className="hidden lg:block w-[16rem] px-8 fixed top-16 right-[max(0px,50%-40rem)] bottom-0 overflow-y-auto border-2 border-blue-500">
<nav>
<ul>
<li>Heading 1</li>
<li>Heading 2</li>
<li>Heading 3</li>
<li>Heading 4</li>
<li>Heading 5</li>
</ul>
</nav>
</div>
)}
</main>
</article>
</>
);

View File

@ -0,0 +1,19 @@
import Head from 'next/head'
import { Paragraph } from './Paragraph'
import { Anchor } from './Anchor'
import { Heading } from './Heading'
const getMDXComponents = ({ params: { h } }) => ({
Head,
p: Paragraph,
a: Anchor,
h1: Heading({ level: 1, ...h }),
h2: Heading({ level: 2, ...h }),
h3: Heading({ level: 3, ...h }),
h4: Heading({ level: 4, ...h }),
h5: Heading({ level: 5, ...h }),
h6: Heading({ level: 6, ...h }),
});
export default getMDXComponents;

View File

@ -11,10 +11,17 @@
"@floating-ui/react-dom-interactions": "^0.6.0",
"@headlessui/react": "^1.4.1",
"@heroicons/react": "^1.0.4",
"@jsdevtools/rehype-toc": "^3.0.2",
"@mdx-js/loader": "^2.0.0",
"@mdx-js/react": "^2.0.0",
"@next/mdx": "^12.1.0",
"@silvenon/remark-smartypants": "^1.0.0",
"@tailwindcss/typography": "^0.5.2",
"contentlayer": "^0.1.2",
"framer-motion": "^6.3.3",
"gray-matter": "^4.0.3",
"hast-util-to-string": "^2.0.0",
"mdast-util-to-hast": "^12.1.1",
"next": "^12.1.0",
"next-contentlayer": "^0.1.2",
"next-seo": "^4.28.1",
@ -32,6 +39,7 @@
"prettier": "^2.6.2",
"rehype-autolink-headings": "^6.1.1",
"rehype-slug": "^5.0.1",
"rehype-stringify": "^9.0.3",
"remark-gfm": "^3.0.0",
"remark-parse": "^10.0.1",
"remark-wiki-link-plus": "^1.0.0",