feat: add toc with accordion animation, add optional toc toggle, lower heti css priority

This commit is contained in:
radishzzz 2025-03-28 00:26:32 +00:00
parent ab74c0abdf
commit dc17f304c1
22 changed files with 280 additions and 167 deletions

View file

@ -2,6 +2,7 @@
import { OverlayScrollbars } from 'overlayscrollbars'
function setupScrollbar() {
// Add scrollbar to body
const bodyElement = document.body
if (!bodyElement.hasAttribute('data-scrollbar-initialized')) {
OverlayScrollbars({
@ -20,6 +21,7 @@ function setupScrollbar() {
bodyElement.setAttribute('data-scrollbar-initialized', 'true')
}
// Add scrollbar to code blocks
const preElements = document.querySelectorAll('pre')
preElements.forEach((pre) => {
if (!pre.hasAttribute('data-scrollbar-initialized')) {
@ -30,7 +32,7 @@ function setupScrollbar() {
},
}, {
scrollbars: {
theme: 'scrollbar-code',
theme: 'scrollbar-widget',
autoHide: 'leave',
autoHideDelay: 500,
},
@ -39,6 +41,25 @@ function setupScrollbar() {
pre.setAttribute('data-scrollbar-initialized', 'true')
}
})
// Add scrollbar to TOC content
const tocElement = document.getElementById('toc-content')
if (tocElement && !tocElement.hasAttribute('data-scrollbar-initialized')) {
OverlayScrollbars({
target: tocElement,
cancel: {
nativeScrollbarsOverlaid: true,
},
}, {
scrollbars: {
theme: 'scrollbar-widget',
autoHide: 'leave',
autoHideDelay: 500,
},
})
tocElement.setAttribute('data-scrollbar-initialized', 'true')
}
}
setupScrollbar()
@ -64,7 +85,7 @@ document.addEventListener('astro:after-swap', setupScrollbar)
--os-handle-min-size: 12%;
}
.scrollbar-code {
.scrollbar-widget {
--os-size: 0.6rem;
--os-padding-perpendicular: 0.1rem;
--os-padding-axis: 0.2rem;
@ -80,7 +101,7 @@ document.addEventListener('astro:after-swap', setupScrollbar)
}
@media (max-width: 1023px) {
.os-scrollbar {
.os-scrollbar.scrollbar-body {
display: none !important;
}
}

View file

@ -0,0 +1,112 @@
---
import type { MarkdownHeading } from 'astro'
import { ui } from '@/i18n/ui'
import { getPageInfo } from '@/utils/page'
interface Props {
headings: MarkdownHeading[]
}
const { currentLang } = getPageInfo(Astro.url.pathname)
const currentUI = ui[currentLang as keyof typeof ui]
const { headings = [] } = Astro.props
const filteredHeadings = headings.filter(heading =>
heading.depth >= 2
&& heading.depth <= 4
&& heading.text !== 'Footnotes',
)
---
{filteredHeadings.length > 0 && (
<div
class="relative mb-6 bg-secondary/5"
border="~ secondary/5 rounded solid"
transition="~ duration-300 ease-in-out"
>
{/* Accordion toggle for expandable TOC */}
<input
type="checkbox"
id="toc-toggle"
class="accordion-toggle"
hidden
/>
<label
for="toc-toggle"
class="absolute inset-0 z-99 cursor-pointer"
></label>
{/* TOC title bar */}
<div class="h-12 w-full flex items-center bg-secondary/0">
<span class="toc-title">
{currentUI.toc}
</span>
</div>
{/* Expandable content wrapper */}
<div class="accordion-wrapper">
<nav
id="toc-content"
class="accordion-content"
aria-label="Table of Contents"
>
<ul class="toc-list">
{filteredHeadings.map(heading => (
<li
class:list={{
'ml-0': heading.depth === 2,
'ml-4': heading.depth === 3,
'ml-8': heading.depth === 4,
}}
>
<a
href={`#${heading.slug}`}
class:list={[
{ 'toc-link-h2': heading.depth === 2 },
{ 'toc-link-h3': heading.depth === 3 },
{ 'toc-link-h4': heading.depth === 4 },
]}
>
{heading.text}
</a>
</li>
))}
</ul>
</nav>
</div>
</div>
)}
<!-- Override heti default styles -->
<style>
.toc-title {
--at-apply: 'font-semibold pl-4 px-4 py-3';
}
.toc-list {
--at-apply: 'list-none pl-0 space-y-2 mt-1 mb-4';
}
.toc-link-h2 {
--at-apply: 'text-sm no-underline font-semibold';
}
.toc-link-h3 {
--at-apply: 'text-sm no-underline font-normal';
}
.toc-link-h4 {
--at-apply: 'text-sm no-underline font-normal';
}
/* Initial collapsed state with zero height grid row */
.accordion-wrapper {
--at-apply: 'grid rows-[0fr] duration-300 ease-in-out';
}
.accordion-content {
--at-apply: 'overflow-hidden max-h-66 lg:max-h-82 z-99 pl-4';
}
/* When toggle is checked, expand the wrapper to show content */
.accordion-toggle:checked ~ .accordion-wrapper {
grid-template-rows: 1fr;
}
.accordion-toggle:checked ~ .accordion-wrapper .accordion-content {
--at-apply: 'overflow-y-auto';
}
</style>