Skip to content
Snippets Groups Projects
Toc.tsx 4.69 KiB
Newer Older
import { Link } from "@onegeo/gatsby-theme-onegeo"
Toavina's avatar
Toavina committed
import React from "react"
import { useEffect, useState } from "react"
import { useHeadsObserver } from "./utils/hook"
Toavina's avatar
Toavina committed
const rnd = (() => {
    const gen = (min: number, max: number) =>
Toavina's avatar
Toavina committed
        max++ &&
        [...Array(max - min)].map((s, i) => String.fromCharCode(min + i))
Toavina's avatar
Toavina committed

Toavina's avatar
Toavina committed
    const sets = {
        alphaLower: gen(97, 122),
        alphaUpper: gen(65, 90),
    }
Toavina's avatar
Toavina committed

    function* iter(len: number, set: any) {
Toavina's avatar
Toavina committed
        if (set.length < 1) set = Object.values(sets).flat()
        for (let i = 0; i < len; i++)
            yield set[(Math.random() * set.length) | 0]
    }
Toavina's avatar
Toavina committed

Toavina's avatar
Toavina committed
    return Object.assign(
        (len:number, ...set:any) => [...iter(len, set.flat())].join(""),
Toavina's avatar
Toavina committed
        sets
    )
})()
Toavina's avatar
Toavina committed

const navStyle = {
Toavina's avatar
Toavina committed
    position: "sticky -webkit-sticky",
    top: "24px",
    maxHeight: "calc(100vh - 40px)",
    overflow: "auto",
} as unknown as React.CSSProperties

let navulliStyle = {
Toavina's avatar
Toavina committed
    marginBottom: "15px",
} as React.CSSProperties
const getClassName = (level: number) => {
Toavina's avatar
Toavina committed
    switch (level) {
        case 1:
            navulliStyle = {
                ...navulliStyle,
                marginLeft: "0px",
            }
            return "head1"
        case 2:
            navulliStyle = {
                ...navulliStyle,
                marginLeft: "10px",
            }
            return "head2"
        case 3:
            navulliStyle = {
                ...navulliStyle,
                marginLeft: "20px",
            }
            return "head3"
        case 4:
            navulliStyle = {
                ...navulliStyle,
                marginLeft: "30px",
            }
            return "head4"
        default:
            return undefined
    }
}

interface Props {
Toavina's avatar
Toavina committed
    className?: string
}

const Toc = (props: Props) => {
Toavina's avatar
Toavina committed
    const { className = "" } = props
    const [headings, setHeadings] = useState<any>([])
    const { activeId } = useHeadsObserver()
Toavina's avatar
Toavina committed
    useEffect(() => {
        const elements = Array.from(
            document.querySelectorAll("h1, h2, h3, h4")
        ).map((elem) => ({
            id: elem.id ? elem.id : (elem.id = rnd(4)),
            text: elem.textContent,
            level: Number(elem.nodeName.charAt(1)),
        }))
        setHeadings(elements)
    }, [])
Toavina's avatar
Toavina committed
    return (
        <aside
            className={`w-72 min-w-min fixed right-0 h-screen bg-slate-50 p-5 ${className}`}
Toavina's avatar
Toavina committed
            <div className="text-2xl pb-5 flex flex-row">
                <svg
                    xmlns="http://www.w3.org/2000/svg"
                    fill="none"
                    viewBox="0 0 24 24"
                    strokeWidth={1.5}
                    stroke="currentColor"
                    className="w-6 h-6 mt-2"
                >
                    <path
                        strokeLinecap="round"
                        strokeLinejoin="round"
                        d="M8.25 6.75h12M8.25 12h12m-12 5.25h12M3.75 6.75h.007v.008H3.75V6.75zm.375 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zM3.75 12h.007v.008H3.75V12zm.375 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zm-.375 5.25h.007v.008H3.75v-.008zm.375 0a.375.375 0 11-.75 0 .375.375 0 01.75 0z"
                    />
                </svg>
                <p className="px-2 mt-0.5"> Menu interne </p>
            </div>
            <nav style={navStyle}>
                <ul style={navulliStyle}>
                    {headings.map((heading:any) => (
Toavina's avatar
Toavina committed
                        <li
                            key={heading.id}
                            className={getClassName(heading.level)}
                            style={navulliStyle}
                        >
                            <Link
                                to={`#${heading.id}`}
                                onClick={(e:any) => {
Toavina's avatar
Toavina committed
                                    e.preventDefault()
                                    const element = document.querySelector(
                                        `#${heading.id}`
                                    )
                                    if (element) {
                                        element?.scrollIntoView({
                                            behavior: "smooth",
                                        })
                                    } else {
                                        console.log("No element")
                                    }
                                }}
                                activeClassName={`${activeId == heading.id ? "font-bold" : "font-normal"} font-serif`}
Toavina's avatar
Toavina committed
                            >
                                {heading.text}
                            </Link>
Toavina's avatar
Toavina committed
                        </li>
                    ))}
                </ul>
            </nav>
        </aside>
    )
}
Toavina's avatar
Toavina committed
export default Toc