Skip to content

Commit 41acb27

Browse files
[ENG-5483] right sidebar on-scroll highlight effect (handles clicks too) (#1359)
* right sidebar on-scroll highlight effect (handles clicks too) * fix ref --------- Co-authored-by: pourhakimi <[email protected]> Co-authored-by: carlosabadia <[email protected]>
1 parent 7605c74 commit 41acb27

File tree

3 files changed

+118
-4
lines changed

3 files changed

+118
-4
lines changed

pcweb/pages/landing/views/companies.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,6 @@ def company_card(image: str, name: str, id: str) -> rx.Component:
209209
)
210210
),
211211
],
212-
ref=id,
213212
id=id,
214213
class_name=(
215214
"relative w-full after:content[''] after:absolute after:z-[1] after:bg-slate-3 after:left-0 after:top-[-1px] after:w-full after:h-[1px] before:content[''] before:absolute before:z-[1] before:bg-slate-3 before:top-0 before:left-[-1px] before:h-full before:w-[1px] group",

pcweb/pages/landing/views/hero.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,6 @@ def preset_cards(text: str, id: str, icon: str) -> rx.Component:
6262
},
6363
class_name="pointer-events-none absolute left-0 top-0 z-10 h-full w-full rounded-[0.625rem] border-[1.5px] bg-[transparent] opacity-0 transition-opacity duration-500 box-border",
6464
),
65-
ref=id,
6665
class_name="relative w-full",
6766
)
6867

pcweb/templates/docpage/docpage.py

Lines changed: 118 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,117 @@
1717
from .state import FeedbackState
1818

1919

20+
def right_sidebar_item_highlight():
21+
return """
22+
function setupTableOfContentsHighlight() {
23+
// Delay to ensure DOM is fully loaded
24+
setTimeout(() => {
25+
const tocLinks = document.querySelectorAll('#toc-navigation a');
26+
const activeClass = 'text-violet-9';
27+
const defaultClass = 'text-slate-9';
28+
29+
function normalizeId(id) {
30+
return id.toLowerCase().replace(/\s+/g, '-');
31+
}
32+
33+
function highlightTocLink() {
34+
// Get the current hash from the URL
35+
const currentHash = window.location.hash.substring(1);
36+
37+
// Reset all links
38+
tocLinks.forEach(link => {
39+
link.classList.remove(activeClass);
40+
link.classList.add(defaultClass);
41+
});
42+
43+
// If there's a hash, find and highlight the corresponding link
44+
if (currentHash) {
45+
const correspondingLink = Array.from(tocLinks).find(link => {
46+
// Extract the ID from the link's href
47+
const linkHash = new URL(link.href).hash.substring(1);
48+
return normalizeId(linkHash) === normalizeId(currentHash);
49+
});
50+
51+
if (correspondingLink) {
52+
correspondingLink.classList.remove(defaultClass);
53+
correspondingLink.classList.add(activeClass);
54+
}
55+
}
56+
}
57+
58+
// Add click event listeners to TOC links to force highlight
59+
tocLinks.forEach(link => {
60+
link.addEventListener('click', (e) => {
61+
// Remove active class from all links
62+
tocLinks.forEach(otherLink => {
63+
otherLink.classList.remove(activeClass);
64+
otherLink.classList.add(defaultClass);
65+
});
66+
67+
// Add active class to clicked link
68+
e.target.classList.remove(defaultClass);
69+
e.target.classList.add(activeClass);
70+
});
71+
});
72+
73+
// Intersection Observer for scroll-based highlighting
74+
const observerOptions = {
75+
root: null,
76+
rootMargin: '-20% 0px -70% 0px',
77+
threshold: 0
78+
};
79+
80+
const observer = new IntersectionObserver((entries) => {
81+
entries.forEach(entry => {
82+
if (entry.isIntersecting) {
83+
const headerId = entry.target.id;
84+
85+
// Find corresponding TOC link
86+
const correspondingLink = Array.from(tocLinks).find(link => {
87+
const linkHash = new URL(link.href).hash.substring(1);
88+
return normalizeId(linkHash) === normalizeId(headerId);
89+
});
90+
91+
if (correspondingLink) {
92+
// Reset all links
93+
tocLinks.forEach(link => {
94+
link.classList.remove(activeClass);
95+
link.classList.add(defaultClass);
96+
});
97+
98+
// Highlight current link
99+
correspondingLink.classList.remove(defaultClass);
100+
correspondingLink.classList.add(activeClass);
101+
}
102+
}
103+
});
104+
}, observerOptions);
105+
106+
// Observe headers
107+
const headerSelectors = Array.from(tocLinks).map(link =>
108+
new URL(link.href).hash.substring(1)
109+
);
110+
111+
headerSelectors.forEach(selector => {
112+
const header = document.getElementById(selector);
113+
if (header) {
114+
observer.observe(header);
115+
}
116+
});
117+
118+
// Initial highlighting
119+
highlightTocLink();
120+
121+
// Handle hash changes
122+
window.addEventListener('hashchange', highlightTocLink);
123+
}, 100);
124+
}
125+
126+
// Run the function when the page loads
127+
setupTableOfContentsHighlight();
128+
"""
129+
130+
20131
def footer_link(text: str, href: str):
21132
return rx.link(
22133
text,
@@ -173,6 +284,7 @@ def docpage_footer(path: str):
173284
from pcweb.constants import ROADMAP_URL, FORUM_URL
174285
from pcweb.views.footer import newsletter_form, menu_socials
175286
from pcweb.pages.framework.views.footer_index import dark_mode_toggle
287+
176288
return rx.el.footer(
177289
rx.box(
178290
rx.box(
@@ -205,7 +317,9 @@ def docpage_footer(path: str):
205317
footer_link("Home", "/"),
206318
footer_link("Templates", gallery.path),
207319
footer_link("Blog", blogs.path),
208-
footer_link("Changelog", "https://github.com/reflex-dev/reflex/releases"),
320+
footer_link(
321+
"Changelog", "https://github.com/reflex-dev/reflex/releases"
322+
),
209323
],
210324
),
211325
footer_link_flex(
@@ -238,7 +352,7 @@ def docpage_footer(path: str):
238352
class_name="flex flex-row justify-between items-center w-full",
239353
),
240354
dark_mode_toggle(),
241-
class_name="flex flex-col gap-4"
355+
class_name="flex flex-col gap-4",
242356
),
243357
class_name="flex flex-col justify-between gap-10 py-6 lg:py-8 w-full",
244358
),
@@ -596,13 +710,15 @@ def wrapper(*args, **kwargs) -> rx.Component:
596710
if show_right_sidebar and not pseudo_right_bar
597711
else " hidden"
598712
),
713+
id="toc-navigation",
599714
)
600715
if not pseudo_right_bar or show_right_sidebar
601716
else rx.spacer()
602717
),
603718
class_name="justify-center flex flex-row mx-auto mt-0 max-w-[94.5em] h-full min-h-screen w-full",
604719
),
605720
class_name="flex flex-col justify-center bg-slate-1 w-full",
721+
on_mount=rx.call_script(right_sidebar_item_highlight()),
606722
)
607723

608724
components = path.split("/")

0 commit comments

Comments
 (0)