Skip to content

Commit ee65fdd

Browse files
authored
Merge pull request #115 from DefGuard/defguard-vs-fortinet
Refactor defguard-vs-fortinet page with new content and sidebar navig…
2 parents 65de7d2 + afa10f8 commit ee65fdd

File tree

2 files changed

+519
-247
lines changed

2 files changed

+519
-247
lines changed
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
---
2+
interface Props {
3+
sections: Array<{
4+
id: string;
5+
title: string;
6+
}>;
7+
title?: string;
8+
}
9+
10+
const { sections, title = "On this page" } = Astro.props;
11+
---
12+
13+
<nav class="sidebar-nav">
14+
<p>{title}</p>
15+
<ul>
16+
{sections.map((section) => (
17+
<li>
18+
<a href={`#${section.id}`} data-section={section.id}>
19+
{section.title}
20+
</a>
21+
</li>
22+
))}
23+
</ul>
24+
</nav>
25+
26+
<style lang="scss">
27+
.sidebar-nav {
28+
height: fit-content;
29+
padding: 1rem;
30+
background: var(--surface-frame-bg);
31+
border-radius: 8px;
32+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
33+
34+
&::-webkit-scrollbar {
35+
width: 4px;
36+
}
37+
38+
&::-webkit-scrollbar-track {
39+
background: transparent;
40+
}
41+
42+
&::-webkit-scrollbar-thumb {
43+
background: var(--text-body-secondary);
44+
border-radius: 2px;
45+
}
46+
47+
p{
48+
margin-bottom: 1rem;
49+
border-bottom: 1px solid var(--text-body-secondary);
50+
@include typography(paragraph);
51+
font-size: calc(0.75rem * var(--font-scale-factor));
52+
}
53+
54+
ul {
55+
list-style: none;
56+
padding: 0;
57+
margin: 0;
58+
display: flex;
59+
flex-direction: column;
60+
gap: 0.5rem;
61+
62+
li {
63+
margin: 0;
64+
padding: 0;
65+
66+
a {
67+
display: block;
68+
padding: 0.2rem 0.5rem;
69+
text-decoration: none;
70+
color: var(--text-body-primary);
71+
transition: all 0.2s ease;
72+
@include typography(menu);
73+
font-size: calc(0.9rem * var(--font-scale-factor));
74+
75+
&:hover {
76+
background: var(--surface-main-primary);
77+
color: white;
78+
}
79+
80+
&.active {
81+
border-right: 4px solid var(--surface-main-primary);
82+
}
83+
}
84+
}
85+
}
86+
}
87+
</style>
88+
89+
<script>
90+
// Only run this script if the sidebar nav exists on the page
91+
const sidebarNav = document.querySelector('.sidebar-nav');
92+
if (sidebarNav) {
93+
// Initialize sections visibility and currently active section
94+
const visibilityMap: Record<string, number> = {};
95+
let currentActiveSection: string | null = null;
96+
97+
// Thresholds for visibility changes
98+
const thresholdsArray = Array.from({ length: 101 }, (_, idx) => idx / 100);
99+
100+
// Intersection Observer logic
101+
const intersectionCallback = (entries: IntersectionObserverEntry[]) => {
102+
entries.forEach((entry: IntersectionObserverEntry) => {
103+
if (entry.target.id) {
104+
visibilityMap[entry.target.id] = entry.intersectionRatio;
105+
}
106+
});
107+
108+
// Determine the most visible section
109+
const visibleSections = Object.entries(visibilityMap)
110+
.filter(([, ratio]: [string, number]) => ratio > 0)
111+
.sort((a: [string, number], b: [string, number]) => {
112+
// Primary: sections with >= 0.7 ratio first, secondary: vertical order
113+
const aHighVis = a[1] >= 0.7 ? 1 : 0;
114+
const bHighVis = b[1] >= 0.7 ? 1 : 0;
115+
if (bHighVis !== aHighVis) {
116+
return bHighVis - aHighVis;
117+
}
118+
119+
// Secondary sort by vertical position
120+
const aElement = document.getElementById(a[0]);
121+
const bElement = document.getElementById(b[0]);
122+
if (aElement && bElement) {
123+
return aElement.offsetTop - bElement.offsetTop;
124+
}
125+
return 0;
126+
});
127+
128+
const newActiveSection = visibleSections.length ? visibleSections[0][0] : null;
129+
if (newActiveSection && newActiveSection !== currentActiveSection) {
130+
document.querySelectorAll('.sidebar-nav a.active').forEach(el => el.classList.remove('active'));
131+
document.querySelector(`.sidebar-nav a[data-section="${newActiveSection}"]`)?.classList.add('active');
132+
currentActiveSection = newActiveSection;
133+
}
134+
};
135+
136+
const observerConfig: IntersectionObserverInit = {
137+
root: null,
138+
rootMargin: "0px 0px -30% 0px",
139+
threshold: thresholdsArray,
140+
};
141+
142+
const observer = new IntersectionObserver(intersectionCallback, observerConfig);
143+
144+
// Observe sections
145+
const sectionsToObserve = document.querySelectorAll(".with-sidebar-nav section[id]");
146+
sectionsToObserve.forEach((section) => {
147+
const sectionId = section.getAttribute("id");
148+
if (sectionId) {
149+
visibilityMap[sectionId] = 0;
150+
observer.observe(section);
151+
}
152+
});
153+
154+
// Initial intersection calculation
155+
setTimeout(() => {
156+
intersectionCallback([]);
157+
}, 100);
158+
159+
// Smooth-scroll event listener
160+
document.querySelectorAll(".sidebar-nav a").forEach((link) => {
161+
link.addEventListener("click", (e) => {
162+
e.preventDefault();
163+
const targetId = link.getAttribute("href")?.substring(1);
164+
if (targetId) {
165+
const targetElement = document.getElementById(targetId);
166+
if (targetElement) {
167+
targetElement.scrollIntoView({
168+
behavior: "smooth",
169+
block: "start",
170+
});
171+
}
172+
}
173+
});
174+
});
175+
}
176+
</script>
177+

0 commit comments

Comments
 (0)