[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)
* [Enclosure](../concepts/enclosure.md)
**Meta**
#### Meta
* [Value](../concepts/value.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) => {
console.log(activeHeading, setActiveHeading)
export const Heading = ({ level, observer }) => (props) => {
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));
};
if (observer) {
observer.observe(document.getElementById(props.id));
}
});
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="" />
</Head>
<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"> */}
{/* <Sidebar /> */}
{/* </div> */}
{children}
</main>
<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">
{navLinks.map((item) => (
<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}
</a>
</div>
@ -42,19 +42,23 @@ export default function Layout({ children }) {
</a>
))}
</div>
<p className="flex items-center justify-center mt-8">
Created by
<a
href={siteConfig.authorUrl}
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}
{' '}
<div className="flex flex-col items-center mt-8 text-gray-400">
<p>
Created by
<a
href={siteConfig.authorUrl}
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}
{' '}
</a>
</p>
<p>
Licensed under a CC-By 4.0 International License
</a>
</p>
</p>
</div>
</div>
</footer>
</>

View File

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

View File

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

View File

@ -13,7 +13,7 @@
/* OTHERS */
html {
scroll-behavior: smooth;
/* scroll-behavior: smooth; */
}
/* bg-neutral-800
@ -39,23 +39,30 @@ body {
background: linear-gradient(to right, rgba(255, 255, 255, 0), rgba(255, 255, 255, 1) 100%);
}
/* rehype-toc plugin classes */
/* rehype-toc classes */
.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 {
position: absolute;
content: "On this page";
top: 1.5rem;
line-height: 1.125rem;
color: white;
font-size: 1.125rem;
font-weight: bold;
@apply text-white text-xl font-semibold top-12
}
.toc-item {
@apply leading-5;
@apply leading-3;
}
.toc-item-h1 {
@ -71,7 +78,11 @@ body {
}
.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 {