mirror of
https://github.com/reonokiy/blog.nokiy.net.git
synced 2025-06-16 19:51:07 +02:00
✨ feat: add toc with accordion animation, add optional toc toggle, lower heti css priority
This commit is contained in:
parent
ab74c0abdf
commit
dc17f304c1
22 changed files with 280 additions and 167 deletions
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
112
src/components/Widgets/TOC.astro
Normal file
112
src/components/Widgets/TOC.astro
Normal 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>
|
Loading…
Add table
Add a link
Reference in a new issue