Files
zhaoguiyang.site/src/components/layout/TableOfContents.astro
zguiyang 0d050b0c14 refactor: update styles and animations across pages for improved consistency and aesthetics
- Changed background gradients and color schemes in `now.astro` and `projects.astro` to use primary colors.
- Updated text styles and backgrounds to enhance readability and visual appeal.
- Added new font imports for better typography.
- Introduced custom animations and hover effects in `global.css` for enhanced user interaction.
- Adjusted CSS variables for a more cohesive design across light and dark modes.
2026-03-13 14:39:09 +08:00

152 lines
4.8 KiB
Plaintext

---
import { type Lang } from '@/types/i18n';
import { ScrollArea } from '@/components/ui/scroll-area';
interface Props {
lang: Lang;
}
const { lang } = Astro.props;
const title = lang === 'zh' ? '目录' : 'Table of Contents';
---
<div>
<div id="nav-content" class="sticky xl:w-72 w-full top-14">
<div class="flex flex-col gap-3 p-4">
<h3 class="dark:text-zinc-200 text-blacktext font-bold tracking-wide text-sm sm:text-base uppercase flex items-center">
<svg class="w-4 h-4 sm:w-5 sm:h-5 mr-2 text-primary" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 10h16M4 14h16M4 18h16" />
</svg>
{title}
</h3>
<ScrollArea className="w-full" client:load>
<div class="text-neutral-500 dark:text-neutral-300 pr-4">
<ul id="toc-list" class="leading-relaxed text-sm sm:text-base border-l dark:border-neutral-500/20 border-blacktext/20 mt-4">
</ul>
</div>
</ScrollArea>
</div>
</div>
</div>
<script>
/**
* Initialize table of contents functionality
* Extracts headings from article content and creates navigation links
*/
document.addEventListener("DOMContentLoaded", function () {
const tocList = document.getElementById("toc-list");
const content = document.querySelector("article");
if (!tocList || !content) return;
// Extract h1, h2, h3, h4 headings from article content
const headers = content.querySelectorAll("h1, h2, h3, h4");
let currentUl = tocList;
/**
* Process each heading and create corresponding TOC entry
*/
headers.forEach((header, index) => {
// Generate ID if not present
if (!header.id) {
header.id = header.textContent?.trim().toLowerCase().replace(/\s+/g, "-") + "-" + index;
}
const li = document.createElement("li");
const link = document.createElement("a");
link.href = `#${header.id}`;
link.textContent = header.textContent?.trim() || header.id;
// Apply styling based on heading level
const level = parseInt(header.tagName.charAt(1));
link.classList.add(
"block", "w-full", "text-left", "py-1.5", "px-3", "text-sm",
"transition-all", "duration-200", "border-l-2", "border-transparent",
"text-muted-foreground", "hover:text-foreground", "hover:bg-muted/30"
);
// Add indentation based on heading level
if (level === 1) {
link.classList.add("font-semibold");
} else if (level === 2) {
link.classList.add("ml-2");
} else if (level === 3) {
link.classList.add("ml-4");
} else if (level === 4) {
link.classList.add("ml-6");
}
li.appendChild(link);
tocList.appendChild(li);
/**
* Handle smooth scroll on link click
*/
link.addEventListener("click", function (e) {
e.preventDefault();
const targetElement = document.getElementById(header.id);
if (targetElement) {
targetElement.scrollIntoView({ behavior: "smooth", block: "start" });
}
});
});
/**
* Set up intersection observer for active section tracking
* Only highlights the active section without auto-scrolling
*/
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
const id = entry.target.getAttribute("id");
const link = document.querySelector(`#toc-list a[href="#${id}"]`);
if (entry.isIntersecting) {
// Remove active state from all links
document.querySelectorAll("#toc-list a").forEach((el) => {
el.classList.remove(
"bg-primary/10", "text-primary", "border-l-primary", "font-medium"
);
el.classList.add("text-muted-foreground", "border-transparent");
});
// Add active state to current link
if (link) {
link.classList.remove("text-muted-foreground", "border-transparent");
link.classList.add(
"bg-primary/10", "text-primary", "border-l-primary", "font-medium"
);
}
}
});
},
{
rootMargin: "-20% 0% -35% 0%",
threshold: 0
}
);
// Observe all headings for intersection
headers.forEach((header) => observer.observe(header));
});
</script>
<style>
/* Additional styling for smooth transitions */
#toc-list a:hover {
transform: translateX(2px);
background-color: rgba(168, 85, 247, 0.05);
}
#toc-list a:focus {
outline: 2px solid rgba(168, 85, 247, 0.3);
outline-offset: 1px;
}
/* Smooth transition for active state */
#toc-list a {
position: relative;
transition: all 0.2s ease;
}
</style>