mirror of
https://github.com/jimeh/commonflow.org.git
synced 2026-02-19 05:46:40 +00:00
wip: improve spec and faq
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user