wip: improve spec and faq

This commit is contained in:
2026-01-10 22:05:40 +00:00
parent 208219ca2c
commit 7c5c7691c3
11 changed files with 109 additions and 124 deletions

View File

@@ -53,11 +53,16 @@ const { items } = Astro.props;
</svg>
</button>
<div
class="hidden pb-6 text-gray-600 dark:text-gray-400
prose-spec spec-content"
class="grid grid-rows-[0fr] transition-[grid-template-rows] duration-300 ease-out"
data-faq-content
set:html={item.answer}
/>
>
<div class="overflow-hidden">
<div
class="pb-6 text-gray-600 dark:text-gray-400 prose-spec spec-content"
set:html={item.answer}
/>
</div>
</div>
</div>
))
}
@@ -80,33 +85,13 @@ const { items } = Astro.props;
trigger.addEventListener("click", () => {
const isExpanded = trigger.getAttribute("aria-expanded") === "true";
// Close all other items
items.forEach((otherItem) => {
if (otherItem !== item) {
const otherTrigger = otherItem.querySelector("[data-faq-trigger]");
const otherContent = otherItem.querySelector("[data-faq-content]");
const otherIcon = otherItem.querySelector("[data-faq-icon]");
otherTrigger?.setAttribute("aria-expanded", "false");
otherContent?.classList.add("hidden");
otherIcon?.classList.remove("rotate-180");
}
});
// Toggle current item
trigger.setAttribute("aria-expanded", isExpanded ? "false" : "true");
content.classList.toggle("hidden", isExpanded);
content.classList.toggle("grid-rows-[1fr]", !isExpanded);
content.classList.toggle("grid-rows-[0fr]", isExpanded);
icon.classList.toggle("rotate-180", !isExpanded);
});
});
// Open first item by default
const firstTrigger = items[0]?.querySelector(
"[data-faq-trigger]"
) as HTMLButtonElement;
if (firstTrigger) {
firstTrigger.click();
}
}
// Initialize on load

View File

@@ -19,15 +19,15 @@ const { items } = Astro.props;
class="text-xs font-semibold uppercase tracking-wider mb-4
text-gray-500 dark:text-gray-500"
>
On This Page
Table of Contents
</div>
{
items.map((item) => (
<a
href={`#${item.id}`}
class:list={[
"sidebar-link block py-2 px-4 text-sm rounded-md -ml-0.5",
"border-l-2 border-transparent transition-colors",
"sidebar-link block py-2 px-4 text-sm rounded-md",
"transition-colors",
"text-gray-500 hover:text-gray-950 hover:bg-gray-100",
"dark:hover:text-gray-50 dark:hover:bg-gray-900",
item.level === 3 && "pl-6 text-[0.8125rem]",
@@ -110,8 +110,8 @@ const { items } = Astro.props;
<a
href={`#${item.id}`}
class:list={[
"sidebar-link block py-2 px-4 text-sm rounded-md -ml-0.5",
"border-l-2 border-transparent transition-colors",
"sidebar-link block py-2 px-4 text-sm rounded-md",
"transition-colors",
"text-gray-500 hover:text-gray-950 hover:bg-gray-100",
"dark:hover:text-gray-50 dark:hover:bg-gray-900",
item.level === 3 && "pl-6 text-[0.8125rem]",
@@ -130,38 +130,51 @@ const { items } = Astro.props;
function initSpecSidebar() {
// Active section tracking
const sidebarLinks = document.querySelectorAll("[data-sidebar-link]");
const sections = new Map<string, Element>();
const sections: { id: string; element: Element }[] = [];
sidebarLinks.forEach((link) => {
const id = link.getAttribute("data-section-id");
if (id) {
const section = document.getElementById(id);
if (section) {
sections.set(id, section);
sections.push({ id, element: section });
}
}
});
// Intersection Observer for active state
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const id = entry.target.getAttribute("id");
sidebarLinks.forEach((link) => {
const linkId = link.getAttribute("data-section-id");
link.classList.toggle("active", linkId === id);
});
}
});
},
{
rootMargin: "-20% 0% -60% 0%",
threshold: 0,
}
);
// Highlight the section whose top is closest to viewport top
function updateActiveSection() {
const headerOffset = 100; // Account for sticky header
let activeId = sections[0]?.id;
sections.forEach((section) => observer.observe(section));
for (const { id, element } of sections) {
const rect = element.getBoundingClientRect();
// Find the last section whose top has scrolled past the header
if (rect.top <= headerOffset) {
activeId = id;
}
}
sidebarLinks.forEach((link) => {
const linkId = link.getAttribute("data-section-id");
link.classList.toggle("active", linkId === activeId);
});
}
// Update on scroll with throttling
let ticking = false;
window.addEventListener("scroll", () => {
if (!ticking) {
requestAnimationFrame(() => {
updateActiveSection();
ticking = false;
});
ticking = true;
}
});
// Initial update
updateActiveSection();
// Mobile TOC drawer
const toggleBtn = document.getElementById("spec-toc-toggle");
@@ -210,7 +223,6 @@ const { items } = Astro.props;
<style>
/* Active state for sidebar links (toggled by JavaScript) */
.sidebar-link.active {
border-left-color: #0284c7;
color: #0284c7;
background-color: rgba(14, 165, 233, 0.15);
}