[components/toc][f]: highlight active h in toc

This commit is contained in:
olayway 2022-05-26 00:08:56 +02:00
parent ccc29184f6
commit 90cbf2be8f
9 changed files with 114 additions and 82 deletions

View File

@ -192,7 +192,7 @@ Understand the deeper theoretical concepts behind the technical and economic cla
* [Predatory inclusion](../concepts/predatory-inclusion.md) * [Predatory inclusion](../concepts/predatory-inclusion.md)
* [Enclosure](../concepts/enclosure.md) * [Enclosure](../concepts/enclosure.md)
**Meta** #### Meta
* [Value](../concepts/value.md) * [Value](../concepts/value.md)
* [Risk](../concepts/risk.md) * [Risk](../concepts/risk.md)

View File

@ -1,24 +1,10 @@
import React, { useEffect } from 'react'; import React, { useEffect, useState } from 'react';
export const Heading = ({ level, activeHeading, setActiveHeading }) => (props) => { export const Heading = ({ level, observer }) => (props) => {
console.log(activeHeading, setActiveHeading)
useEffect(() => { useEffect(() => {
const observer = new IntersectionObserver( if (observer) {
(entries) => { observer.observe(document.getElementById(props.id));
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 }) return React.createElement(`h${level}`, { ...props })

View File

@ -17,18 +17,18 @@ export default function Layout({ children }) {
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="" /> <link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="" />
</Head> </Head>
<Nav /> <Nav />
<main className="max-w-7xl mx-auto px-2 sm:px-6 md:px-8 border-2 border-green-500"> <main className="max-w-7xl relative mx-auto px-2 sm:px-6 md:px-8">
{/* <div className="hidden md:block w-[16rem] fixed top-16 left-[max(0px,calc(50%-40rem))] bottom-0 right-auto px-8 overflow-y-auto border-2 border-red-500"> */} {/* <div className="hidden md:block w-[16rem] fixed top-16 left-[max(0px,calc(50%-40rem))] bottom-0 right-auto px-8 overflow-y-auto border-2 border-red-500"> */}
{/* <Sidebar /> */} {/* <Sidebar /> */}
{/* </div> */} {/* </div> */}
{children} {children}
</main> </main>
<footer className="w-full h-24 mt-16"> <footer className="w-full h-24 mt-16">
<div className="max-w-7xl mx-auto py-12 px-4 overflow-hidden sm:px-6 md:px-8"> <div className="max-w-7xl mx-auto py-12 px-4 sm:px-6 md:px-8 overflow-hidden">
<nav className="-mx-5 -my-2 flex flex-wrap justify-center" aria-label="Footer"> <nav className="-mx-5 -my-2 flex flex-wrap justify-center" aria-label="Footer">
{navLinks.map((item) => ( {navLinks.map((item) => (
<div key={item.name} className="px-5 py-2"> <div key={item.name} className="px-5 py-2">
<a href={item.href} className="text-base text-gray-500 hover:text-gray-900"> <a href={item.href} className="text-base text-gray-400 hover:text-gray-500">
{item.name} {item.name}
</a> </a>
</div> </div>
@ -42,19 +42,23 @@ export default function Layout({ children }) {
</a> </a>
))} ))}
</div> </div>
<p className="flex items-center justify-center mt-8"> <div className="flex flex-col items-center mt-8 text-gray-400">
Created by <p>
<a Created by
href={siteConfig.authorUrl} <a
target="_blank" href={siteConfig.authorUrl}
rel="noopener noreferrer" target="_blank"
> rel="noopener noreferrer"
<img src={siteConfig.authorLogo} alt={siteConfig.author} width="20" height="20" className="mx-2 h-6 inline-block" /> >
{siteConfig.author} <img src={siteConfig.authorLogo} alt={siteConfig.author} width="20" height="20" className="mx-2 h-6 inline-block" />
{' '} {siteConfig.author}
{' '}
</a>
</p>
<p>
Licensed under a CC-By 4.0 International License Licensed under a CC-By 4.0 International License
</a> </p>
</p> </div>
</div> </div>
</footer> </footer>
</> </>

View File

@ -2,9 +2,10 @@ import { NextSeo } from "next-seo";
import LiteYouTubeEmbed from "react-lite-youtube-embed"; import LiteYouTubeEmbed from "react-lite-youtube-embed";
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import siteConfig from "../config/siteConfig";
import { YOUTUBE_REGEX } from "../lib/constants"; import { YOUTUBE_REGEX } from "../lib/constants";
import siteConfig from "../config/siteConfig";
import getMDXComponents from "./_getMDXComponents"; import getMDXComponents from "./_getMDXComponents";
import getObserver from "./_getIntersectionObserver"
import { Paragraph } from "./Paragraph"; import { Paragraph } from "./Paragraph";
import { Anchor } from "./Anchor"; import { Anchor } from "./Anchor";
@ -14,38 +15,29 @@ import { Anchor } from "./Anchor";
// const Paragraph = dynamic(() => import("./Paragraph").then(mod => mod.Paragraph)) // const Paragraph = dynamic(() => import("./Paragraph").then(mod => mod.Paragraph))
// import { Toc } from './Toc'
export default function MdxPage({ children }) { export default function MdxPage({ children }) {
const [activeHeading, setActiveHeading] = useState(""); const [activeHeading, setActiveHeading] = useState("");
const observer = getObserver((entry) => {
if (entry.isIntersecting) {
setActiveHeading(entry.target.id);
}
})
const components = getMDXComponents({ const components = getMDXComponents({
params: { h: {
h: { observer
activeHeading, }
setActiveHeading, })
},
},
});
useEffect(() => { useEffect(() => {
if (activeHeading) { if (activeHeading) {
const tocLink = document.querySelector( const tocLink = document.querySelector(`.toc-link[href="#${activeHeading}"]`)
`.toc-link[href="#${activeHeading}"]`
);
tocLink.classList.add("active"); tocLink.classList.add("active");
// setTimeout to fix scrolling behavior return () => {
// fix switching on-off when two headings are observed tocLink.classList.remove("active")
// router push
}
return () => {
if (activeHeading) {
const tocLink = document.querySelector(
`.toc-link[href="#${activeHeading}"]`
);
tocLink.classList.remove("active");
} }
}; };
}, [activeHeading]); }, [activeHeading]);
@ -128,8 +120,8 @@ export default function MdxPage({ children }) {
{ 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-[1rem] 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"> <article className="px-12 lg:pr-[22rem] prose max-w-none dark:prose-invert prose-a:break-all mx-auto">
<header> <header>
<div className="mb-6"> <div className="mb-6">
{title && <h1 className="mb-0">{title}</h1>} {title && <h1 className="mb-0">{title}</h1>}
@ -161,10 +153,8 @@ export default function MdxPage({ children }) {
)} )}
</div> </div>
</header> </header>
<main> <main className="my-12">
<div className="my-6"> <Component components={components} />
<Component components={components} />
</div>
{editUrl && ( {editUrl && (
<div className="mt-12 mb-6"> <div className="mt-12 mb-6">
<a <a

21
site/components/Toc.js Normal file
View File

@ -0,0 +1,21 @@
import Link from 'next/link'
export const Toc = ({ headings }) => {
return (
<nav>
<h4>
On this page
</h4>
{ headings.map(({url, title, level}, i) => (
<div key={i} className={`flex leading-5 pl-${(level-1)*4}`}>
<Link href={`#${url}`} >
<a className="py-1 text-slate-400 hover:text-white no-underline transition-colors">
{ title }
</a>
</Link>
</div>
))}
</nav>
)
}

View File

@ -0,0 +1,16 @@
const getIntersectionObserver = (callback) => {
if (typeof window !== 'undefined') {
return new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
callback(entry);
});
},
{ root: null,
rootMargin: `0% 0% -85% 0%`
}
);
}
};
export default getIntersectionObserver;

View File

@ -4,16 +4,20 @@ import { Anchor } from './Anchor'
import { Heading } from './Heading' import { Heading } from './Heading'
const getMDXComponents = ({ params: { h } }) => ({ /**
* @param {object} [props] Props passed to each component
* @param {object} [props.h] Props passed to Heading component
*/
const getMDXComponents = (props) => ({
Head, Head,
p: Paragraph, p: Paragraph,
a: Anchor, a: Anchor,
h1: Heading({ level: 1, ...h }), h1: Heading({ level: 1, ...props?.h }),
h2: Heading({ level: 2, ...h }), h2: Heading({ level: 2, ...props?.h }),
h3: Heading({ level: 3, ...h }), h3: Heading({ level: 3, ...props?.h }),
h4: Heading({ level: 4, ...h }), h4: Heading({ level: 4, ...props?.h }),
h5: Heading({ level: 5, ...h }), h5: Heading({ level: 5, ...props?.h }),
h6: Heading({ level: 6, ...h }), h6: Heading({ level: 6, ...props?.h }),
}); });
export default getMDXComponents; export default getMDXComponents;

View File

@ -64,7 +64,7 @@ export default makeSource({
rehypePlugins: [ rehypePlugins: [
rehypeSlug, rehypeSlug,
rehypeAutolinkHeadings, rehypeAutolinkHeadings,
[ rehypeToc, { position: 'beforeend' } ] [ rehypeToc, { position: 'afterend' } ]
] ]
} }
}) })

View File

@ -13,7 +13,7 @@
/* OTHERS */ /* OTHERS */
html { html {
scroll-behavior: smooth; /* scroll-behavior: smooth; */
} }
/* bg-neutral-800 /* bg-neutral-800
@ -39,23 +39,30 @@ body {
background: linear-gradient(to right, rgba(255, 255, 255, 0), rgba(255, 255, 255, 1) 100%); background: linear-gradient(to right, rgba(255, 255, 255, 0), rgba(255, 255, 255, 1) 100%);
} }
/* rehype-toc plugin classes */ /* rehype-toc classes */
.toc { .toc {
@apply hidden lg:block w-[16rem] p-8 pt-14 fixed top-16 bottom-0 right-[max(0px,50%-40rem)] overflow-y-auto border-2 border-blue-500; @apply
hidden
lg:block
w-[22rem]
p-12 /* 1.5rem */
pl-12
py-24
fixed
top-16
/* bottom-0 */
right-[max(2rem,calc(50%-40rem+2rem))]
overflow-y-auto
} }
.toc::before { .toc::before {
position: absolute; position: absolute;
content: "On this page"; content: "On this page";
top: 1.5rem; @apply text-white text-xl font-semibold top-12
line-height: 1.125rem;
color: white;
font-size: 1.125rem;
font-weight: bold;
} }
.toc-item { .toc-item {
@apply leading-5; @apply leading-3;
} }
.toc-item-h1 { .toc-item-h1 {
@ -71,7 +78,11 @@ body {
} }
.toc-item .toc-link { .toc-item .toc-link {
@apply text-sm text-slate-400 hover:text-white no-underline break-normal @apply text-sm text-slate-400 no-underline break-normal
}
.toc-item .toc-link:not(.active) {
@apply hover:text-white
} }
.toc-link.active { .toc-link.active {