[component/toc][f]: toc styles adjustment

This commit is contained in:
olayway 2022-05-26 18:53:18 +02:00
parent ac93858d82
commit 73adb71e10
8 changed files with 127 additions and 107 deletions

View File

@ -1,5 +1,6 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
export const Heading = ({ level, observer }) => (props) => { export const Heading = ({ level, observer }) => (props) => {
useEffect(() => { useEffect(() => {
if (observer) { if (observer) {
@ -7,5 +8,9 @@ export const Heading = ({ level, observer }) => (props) => {
} }
}); });
return React.createElement(`h${level}`, { ...props })
return React.createElement(`h${level}`, {
...props,
className: "scroll-mt-16"
})
} }

View File

@ -17,7 +17,7 @@ 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="border-2 border-blue-500 max-w-7xl relative mx-auto px-2 sm:px-6 md:px-8"> <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> */}

View File

@ -6,8 +6,10 @@ import { YOUTUBE_REGEX } from "../lib/constants";
import siteConfig from "../config/siteConfig"; import siteConfig from "../config/siteConfig";
import getMDXComponents from "./_getMDXComponents"; import getMDXComponents from "./_getMDXComponents";
import getObserver from "./_getIntersectionObserver" import getObserver from "./_getIntersectionObserver"
import { Paragraph } from "./Paragraph"; import MdxContent from "./MdxContent"
import { Anchor } from "./Anchor";
// import { Paragraph } from "./Paragraph";
// import { Anchor } from "./Anchor";
// const Anchor = dynamic(() => import('./Anchor').then(module => module.Anchor), { // const Anchor = dynamic(() => import('./Anchor').then(module => module.Anchor), {
// ssr: false // ssr: false
@ -15,46 +17,48 @@ import { Anchor } from "./Anchor";
// const Paragraph = dynamic(() => import("./Paragraph").then(mod => mod.Paragraph)) // const Paragraph = dynamic(() => import("./Paragraph").then(mod => mod.Paragraph))
export default function MdxPage({ body, frontMatter, editUrl }) {
export default function MdxPage({ children }) {
const [activeHeading, setActiveHeading] = useState(""); const [activeHeading, setActiveHeading] = useState("");
const [observer, setObserver] = useState(null);
const observer = getObserver((entry) => { // run only after first render, in order to preserve the observer
if (entry.isIntersecting) { useEffect((() => {
setActiveHeading(entry.target.id); const observer = getObserver((entry) => {
} if (entry.isIntersecting) {
}) setActiveHeading(entry.target.id);
}
});
setObserver(observer);
const components = getMDXComponents({ return observer.disconnect();
h: { }), [])
observer
}
})
useEffect(() => { useEffect(() => {
if (activeHeading) { if (!activeHeading) {
const tocLink = document.querySelector(`.toc-link[href="#${activeHeading}"]`) try {
tocLink.classList.add("active"); const path = window.location.hash;
if (path) {
return () => { setActiveHeading(path.slice(1))
tocLink.classList.remove("active") } else {
const firstTocHeading = document.querySelector(".toc-link");
const href = firstTocHeading.href;
setActiveHeading(href.match(/.+#(.+)/)[1]);
}
} finally {
return
} }
}; }
}, [activeHeading]); const tocLink = document.querySelector(`.toc-link[href="#${activeHeading}"]`)
tocLink.classList.add("active");
return () => {
tocLink.classList.remove("active")
}
}, [activeHeading])
const { const {
Component, title, description, date, authors, youtube, podcast, image, _raw
frontmatter: { } = frontMatter
title,
description,
date,
keywords,
youtube,
podcast,
image,
_raw,
},
} = children;
let youtubeThumnbnail; let youtubeThumnbnail;
@ -120,7 +124,7 @@ export default function MdxPage({ children }) {
{ name: "keywords", content: keywords ? keywords : "" }, { name: "keywords", content: keywords ? keywords : "" },
]} ]}
/> />
<article className="border-2 border-green-500 prose dark:prose-invert prose-a:break-all mx-auto lg:mr-[20rem] p-6"> <article className="prose dark:prose-invert prose-a:break-all mx-auto lg:mr-[20rem] p-6">
<header> <header>
<div className="mb-6"> <div className="mb-6">
{title && <h1 className="mb-0">{title}</h1>} {title && <h1 className="mb-0">{title}</h1>}
@ -152,8 +156,8 @@ export default function MdxPage({ children }) {
)} )}
</div> </div>
</header> </header>
<main className="my-12"> <main className="my-6">
<Component components={components} /> <MdxContent body={body} observer={observer}/>
{editUrl && ( {editUrl && (
<div className="mt-12 mb-6"> <div className="mt-12 mb-6">
<a <a

View File

@ -0,0 +1,26 @@
import React from 'react'
import Head from 'next/head'
import { useMDXComponent } from 'next-contentlayer/hooks';
import { Paragraph } from './Paragraph'
import { Anchor } from './Anchor'
import { Heading } from './Heading'
const MdxContent = ({ body, observer }) => {
const customComponents = {
Head,
p: Paragraph,
a: Anchor,
h1: Heading({ level: 1, observer }),
h2: Heading({ level: 2, observer }),
h3: Heading({ level: 3, observer }),
h4: Heading({ level: 4, observer }),
h5: Heading({ level: 5, observer }),
h6: Heading({ level: 6, observer }),
}
const Component = useMDXComponent(body.code);
return <Component components={ customComponents }/>
};
export default React.memo(MdxContent);

View File

@ -1,22 +1,16 @@
const getIntersectionObserver = (callback) => { const getIntersectionObserver = (callback) => {
if (typeof window !== 'undefined') { return new IntersectionObserver(
return new IntersectionObserver( (entries) => {
(entries) => { entries.forEach((entry) => {
const intersectingEntries = entries.filter((e) => e.isIntersecting); callback(entry);
const firstEntry = intersectingEntries[0]; });
if (firstEntry) { },
callback(firstEntry); {
} root: null,
// entries.forEach((entry) => { // 65px is a navbar height
// callback(entry); rootMargin: `-65px 0% -90% 0%`
// }); }
}, );
{ root: null,
threshold: 0.9,
rootMargin: `0% 0% -90% 0%`
}
);
}
}; };
export default getIntersectionObserver; export default getIntersectionObserver;

View File

@ -1,23 +0,0 @@
import Head from 'next/head'
import { Paragraph } from './Paragraph'
import { Anchor } from './Anchor'
import { Heading } from './Heading'
/**
* @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, ...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

@ -1,19 +1,23 @@
import MdxPage from "../components/MDX"; import { allOtherPages } from 'contentlayer/generated';
import { allOtherPages } from "contentlayer/generated";
import { useMDXComponent } from "next-contentlayer/hooks";
import siteConfig from "../config/siteConfig"; import MdxPage from '../components/MDX';
import siteConfig from "../config/siteConfig"
export default function Page({ body, ...rest }) { export default function Page({ body, ...meta }) {
const Component = useMDXComponent(body.code); const frontMatter = {
const children = { ...meta,
Component, date: meta.date === "Invalid Date" ? null : meta.date,
frontmatter: { created: meta.created === "Invalid Date" ? null : meta.created
...rest, }
},
};
return <MdxPage children={children} />; // enable editing content only for claims, concepts, and guide for now
const editUrl = ['claims', 'concepts', 'guide'].includes(meta._raw.sourceFileDir)
? siteConfig.repoRoot + siteConfig.repoEditPath + meta._raw.sourceFilePath
: null
return (
<MdxPage body={body} frontMatter={frontMatter} editUrl={editUrl} />
);
} }
export const getStaticProps = async ({ params }) => { export const getStaticProps = async ({ params }) => {

View File

@ -13,7 +13,7 @@
/* OTHERS */ /* OTHERS */
html { html {
/* scroll-behavior: smooth; */ scroll-behavior: smooth;
} }
/* bg-neutral-800 /* bg-neutral-800
@ -40,36 +40,32 @@ body {
} }
/* rehype-toc classes */ /* rehype-toc classes */
/* nav element */
.toc { .toc {
@apply @apply
hidden hidden
lg:block lg:block
w-[20rem] w-[20rem]
my-12
pt-12
px-8 px-8
py-24
fixed fixed
top-16 top-16
/* bottom-0 */ bottom-0
right-[max(2rem,calc(50%-40rem+2rem))] right-[max(2rem,calc(50%-40rem+2rem))]
overflow-y-auto overflow-y-auto
border-2 border-l
border-red-500 border-slate-800
} }
/* toc title */
.toc::before { .toc::before {
position: absolute; position: absolute;
content: "On this page"; content: "On this page";
@apply text-white text-xl font-semibold top-12 @apply text-white text-xl font-semibold top-1
}
.toc-item {
@apply leading-3;
}
.toc-item-h1 {
@apply p-0;
} }
/* list (ol) element */
.toc-level { .toc-level {
@apply list-none p-0; @apply list-none p-0;
} }
@ -78,6 +74,20 @@ body {
@apply pl-2 @apply pl-2
} }
/* list item (li) element */
.toc-item {
@apply leading-3;
}
.toc-item-h1 {
@apply p-0;
}
.toc-link {
@apply transition-colors;
}
/* link (a) element */
.toc-item .toc-link { .toc-item .toc-link {
@apply text-sm text-slate-400 no-underline break-normal @apply text-sm text-slate-400 no-underline break-normal
} }