|
17 | 17 | from .state import FeedbackState |
18 | 18 |
|
19 | 19 |
|
| 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 | + |
20 | 131 | def footer_link(text: str, href: str): |
21 | 132 | return rx.link( |
22 | 133 | text, |
@@ -173,6 +284,7 @@ def docpage_footer(path: str): |
173 | 284 | from pcweb.constants import ROADMAP_URL, FORUM_URL |
174 | 285 | from pcweb.views.footer import newsletter_form, menu_socials |
175 | 286 | from pcweb.pages.framework.views.footer_index import dark_mode_toggle |
| 287 | + |
176 | 288 | return rx.el.footer( |
177 | 289 | rx.box( |
178 | 290 | rx.box( |
@@ -205,7 +317,9 @@ def docpage_footer(path: str): |
205 | 317 | footer_link("Home", "/"), |
206 | 318 | footer_link("Templates", gallery.path), |
207 | 319 | 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 | + ), |
209 | 323 | ], |
210 | 324 | ), |
211 | 325 | footer_link_flex( |
@@ -238,7 +352,7 @@ def docpage_footer(path: str): |
238 | 352 | class_name="flex flex-row justify-between items-center w-full", |
239 | 353 | ), |
240 | 354 | dark_mode_toggle(), |
241 | | - class_name="flex flex-col gap-4" |
| 355 | + class_name="flex flex-col gap-4", |
242 | 356 | ), |
243 | 357 | class_name="flex flex-col justify-between gap-10 py-6 lg:py-8 w-full", |
244 | 358 | ), |
@@ -596,13 +710,15 @@ def wrapper(*args, **kwargs) -> rx.Component: |
596 | 710 | if show_right_sidebar and not pseudo_right_bar |
597 | 711 | else " hidden" |
598 | 712 | ), |
| 713 | + id="toc-navigation", |
599 | 714 | ) |
600 | 715 | if not pseudo_right_bar or show_right_sidebar |
601 | 716 | else rx.spacer() |
602 | 717 | ), |
603 | 718 | class_name="justify-center flex flex-row mx-auto mt-0 max-w-[94.5em] h-full min-h-screen w-full", |
604 | 719 | ), |
605 | 720 | class_name="flex flex-col justify-center bg-slate-1 w-full", |
| 721 | + on_mount=rx.call_script(right_sidebar_item_highlight()), |
606 | 722 | ) |
607 | 723 |
|
608 | 724 | components = path.split("/") |
|
0 commit comments