Skip to content

Commit 80790e3

Browse files
author
raineer
committed
Add vertical scroll navigation ton onboarding page
1 parent 3161b96 commit 80790e3

3 files changed

Lines changed: 130 additions & 1030 deletions

File tree

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
"use client";
2+
3+
import { useEffect, useState } from "react";
4+
import { cn } from "@/lib/utils";
5+
6+
// Section configuration - maps to each wrapper component
7+
const SECTIONS = [
8+
{ id: "software", label: "Software Development" },
9+
{ id: "expect", label: "What to Expect" },
10+
{ id: "roadmap", label: "Your Roadmap" },
11+
{ id: "house-rules", label: "House Rules" },
12+
{ id: "team", label: "Meet the Team" },
13+
{ id: "partners", label: "Our Partners" },
14+
{ id: "welcome", label: "Welcome Aboard" },
15+
];
16+
17+
export default function OnboardingStepper() {
18+
const [activeSection, setActiveSection] = useState<string>(SECTIONS[0].id);
19+
20+
useEffect(() => {
21+
// Intersection Observer to track which section is in view
22+
const observerOptions = {
23+
root: null,
24+
rootMargin: "-50% 0px -50% 0px", // Trigger when section crosses middle of viewport
25+
threshold: 0,
26+
};
27+
28+
const observerCallback = (entries: IntersectionObserverEntry[]) => {
29+
entries.forEach((entry) => {
30+
if (entry.isIntersecting) {
31+
setActiveSection(entry.target.id);
32+
}
33+
});
34+
};
35+
36+
const observer = new IntersectionObserver(
37+
observerCallback,
38+
observerOptions
39+
);
40+
41+
// Observe each section
42+
SECTIONS.forEach(({ id }) => {
43+
const element = document.getElementById(id);
44+
if (element) observer.observe(element);
45+
});
46+
47+
return () => observer.disconnect();
48+
}, []);
49+
50+
// Scroll to section when circle is clicked
51+
const scrollToSection = (sectionId: string) => {
52+
const element = document.getElementById(sectionId);
53+
if (element) {
54+
element.scrollIntoView({ behavior: "smooth", block: "center" });
55+
}
56+
};
57+
58+
return (
59+
<nav
60+
className="fixed left-6 top-1/2 z-50 hidden -translate-y-1/2 lg:block"
61+
aria-label="Onboarding progress"
62+
>
63+
<div className="flex flex-col gap-6">
64+
{SECTIONS.map((section, index) => {
65+
const isActive = activeSection === section.id;
66+
const isPassed = SECTIONS.findIndex((s) => s.id === activeSection) > index;
67+
68+
return (
69+
<button
70+
key={section.id}
71+
onClick={() => scrollToSection(section.id)}
72+
className="group relative flex items-center"
73+
aria-label={`Go to ${section.label}`}
74+
aria-current={isActive ? "step" : undefined}
75+
>
76+
{/* Circle indicator */}
77+
<div
78+
className={cn(
79+
"h-4 w-4 rounded-full border-2 transition-all duration-300",
80+
isActive && "scale-125 border-blue-600 bg-blue-600",
81+
isPassed && "border-blue-600 bg-blue-600",
82+
!isActive && !isPassed && "border-gray-400 bg-transparent"
83+
)}
84+
/>
85+
86+
{/* Tooltip label on hover */}
87+
<span className="absolute left-8 whitespace-nowrap rounded bg-gray-900 px-3 py-1.5 text-sm text-white opacity-0 transition-opacity group-hover:opacity-100 dark:bg-gray-100 dark:text-gray-900">
88+
{section.label}
89+
</span>
90+
</button>
91+
);
92+
})}
93+
</div>
94+
</nav>
95+
);
96+
}
Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
// app/auth/onboarding/page.tsx (patched)
2-
31
"use client";
42

53
import { useRef } from "react";
@@ -12,6 +10,7 @@ import IsAndIsntSectionWrapper from "./IsAndIsntSectionWrapper";
1210
import IsNotSectionWrapper from "./IsNotSectionWrapper";
1311
import MindsetSectionWrapper from "./MindsetSectionWrapper";
1412
import OnboardingClientWrapper from "./OnboardingClientWrapper";
13+
import OnboardingStepper from "./OnboardingStepper";
1514
import PartnersSectionWrapper from "./PartnersSectionWrapper";
1615
import RoadMapWrapper from "./RoadMapSectionWrapper";
1716
import SoftwareSectionWrapper from "./SoftwareSectionWrapper";
@@ -23,18 +22,39 @@ export default function OnboardingPage() {
2322

2423
return (
2524
<main className="relative min-h-screen">
25+
{/* Vertical stepper navigation */}
26+
<OnboardingStepper />
27+
2628
<OnboardingClientWrapper />
27-
<SoftwareSectionWrapper />
28-
{/* <MindsetSectionWrapper /> */}
29-
<ExpectSectionWrapper />
30-
{/* <IsAndIsntSectionWrapper /> */}
31-
{/* <IsNotSectionWrapper /> */}
32-
{/* <ExpectFromUsWrapper /> */}
33-
<RoadMapWrapper roadmapRef={roadmapRef} />
34-
<HouseRulesSectionWrapper />
35-
<TeamSectionWrapper />
36-
<PartnersSectionWrapper />
37-
<WellcomeSectionWrapper />
29+
30+
{/* Each section wrapped with ID for stepper tracking */}
31+
<section id="software">
32+
<SoftwareSectionWrapper />
33+
</section>
34+
35+
<section id="expect">
36+
<ExpectSectionWrapper />
37+
</section>
38+
39+
<section id="roadmap">
40+
<RoadMapWrapper roadmapRef={roadmapRef} />
41+
</section>
42+
43+
<section id="house-rules">
44+
<HouseRulesSectionWrapper />
45+
</section>
46+
47+
<section id="team">
48+
<TeamSectionWrapper />
49+
</section>
50+
51+
<section id="partners">
52+
<PartnersSectionWrapper />
53+
</section>
54+
55+
<section id="welcome">
56+
<WellcomeSectionWrapper />
57+
</section>
3858
</main>
3959
);
40-
}
60+
}

0 commit comments

Comments
 (0)