Skip to content

Commit ed621dc

Browse files
committed
feat(docs): improve table of contents functionality
1 parent f2c36b1 commit ed621dc

File tree

2 files changed

+59
-48
lines changed

2 files changed

+59
-48
lines changed

apps/docs/src/components/navigation/TableOfContents.astro

Lines changed: 25 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -36,48 +36,40 @@ const tocHeadings = filterTocHeadings(headings);
3636
const tocLinks = document.querySelectorAll('[data-toc-link]');
3737
if (tocLinks.length === 0) return;
3838

39-
const headingIds = Array.from(tocLinks).map((link) => {
40-
const href = link.getAttribute('href');
41-
return href ? href.slice(1) : '';
42-
});
43-
44-
const headings = headingIds
45-
.map((id) => document.getElementById(id))
46-
.filter((el): el is HTMLElement => el !== null);
47-
48-
if (headings.length === 0) return;
49-
50-
const observer = new IntersectionObserver(
51-
(entries) => {
52-
// Find the first visible heading
53-
const visibleEntry = entries.find((entry) => entry.isIntersecting);
39+
function updateFromHash() {
40+
const hash = window.location.hash || '';
41+
tocLinks.forEach((link, index) => {
42+
const href = link.getAttribute('href');
43+
// Match hash, or default to first link if no hash
44+
const isActive = hash ? href === hash : index === 0;
45+
link.setAttribute('aria-current', isActive ? 'true' : 'false');
46+
});
47+
}
5448

55-
if (visibleEntry) {
56-
const id = visibleEntry.target.id;
49+
// Update on click (immediately, before hash changes)
50+
tocLinks.forEach((link, index) => {
51+
link.addEventListener('click', (e) => {
52+
tocLinks.forEach((l) => l.setAttribute('aria-current', 'false'));
53+
link.setAttribute('aria-current', 'true');
5754

58-
// Update aria-current on all links
59-
tocLinks.forEach((link) => {
60-
const href = link.getAttribute('href');
61-
const isActive = href === `#${id}`;
62-
link.setAttribute('aria-current', isActive ? 'true' : 'false');
63-
});
55+
// First link scrolls to top of page
56+
if (index === 0) {
57+
e.preventDefault();
58+
window.scrollTo({ top: 0, behavior: 'smooth' });
59+
history.pushState(null, '', link.getAttribute('href') || '');
6460
}
65-
},
66-
{
67-
rootMargin: '-80px 0px -70% 0px',
68-
threshold: 0,
69-
}
70-
);
71-
72-
headings.forEach((heading) => {
73-
observer.observe(heading);
61+
});
7462
});
7563

64+
// Initial state and hash changes
65+
updateFromHash();
66+
window.addEventListener('hashchange', updateFromHash);
67+
7668
// Cleanup on page transitions
7769
document.addEventListener(
7870
'astro:before-swap',
7971
() => {
80-
observer.disconnect();
72+
window.removeEventListener('hashchange', updateFromHash);
8173
},
8274
{ once: true }
8375
);

apps/docs/src/styles/docs.css

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,20 @@
22
* Shift CSS Documentation Layout Styles
33
*/
44

5+
/* =============================================================================
6+
Smooth Scrolling & Performance
7+
============================================================================= */
8+
9+
html {
10+
scroll-behavior: smooth;
11+
}
12+
13+
@media (prefers-reduced-motion: reduce) {
14+
html {
15+
scroll-behavior: auto;
16+
}
17+
}
18+
519
/* =============================================================================
620
CSS Custom Properties
721
============================================================================= */
@@ -37,6 +51,7 @@
3751
@media (min-width: 1024px) {
3852
.docs-layout {
3953
grid-template-columns: var(--docs-sidebar-width) 1fr var(--docs-toc-width);
54+
align-items: start;
4055
}
4156
}
4257

@@ -55,7 +70,7 @@
5570
padding-inline: var(--docs-gutter);
5671
background: var(--s-surface-base);
5772
border-bottom: 1px solid var(--s-border-default);
58-
backdrop-filter: blur(8px);
73+
transform: translateZ(0); /* GPU acceleration */
5974
}
6075

6176
@media (min-width: 768px) {
@@ -174,25 +189,29 @@
174189

175190
.docs-toc {
176191
display: none;
177-
position: sticky;
178-
top: var(--docs-header-height);
179-
height: calc(100vh - var(--docs-header-height));
180-
overflow-y: auto;
181-
padding: var(--docs-gutter);
182-
font-size: var(--s-text-sm);
183-
184-
/* Hide scrollbar but keep functionality */
185-
scrollbar-width: none; /* Firefox */
186-
-ms-overflow-style: none; /* IE/Edge */
187-
}
188-
189-
.docs-toc::-webkit-scrollbar {
190-
display: none; /* Chrome, Safari, Opera */
191192
}
192193

193194
@media (min-width: 1024px) {
194195
.docs-toc {
195196
display: block;
197+
position: fixed;
198+
top: var(--docs-header-height);
199+
right: 0;
200+
width: var(--docs-toc-width);
201+
max-height: calc(100vh - var(--docs-header-height));
202+
overflow-y: auto;
203+
padding: var(--docs-gutter);
204+
padding-bottom: var(--s-space-16);
205+
font-size: var(--s-text-sm);
206+
transform: translateZ(0); /* GPU acceleration */
207+
208+
/* Hide scrollbar but keep functionality */
209+
scrollbar-width: none; /* Firefox */
210+
-ms-overflow-style: none; /* IE/Edge */
211+
}
212+
213+
.docs-toc::-webkit-scrollbar {
214+
display: none; /* Chrome, Safari, Opera */
196215
}
197216
}
198217

0 commit comments

Comments
 (0)