From b6f1181d82eaf7dc8d72899ad8131e0050dd970c Mon Sep 17 00:00:00 2001 From: Jack Zheng Date: Mon, 4 May 2026 17:12:53 -0700 Subject: [PATCH 1/3] feat: highlight active section in sidebar on scroll --- .../_components/StarterKit/StarterKit.tsx | 87 +++++++++++-------- 1 file changed, 52 insertions(+), 35 deletions(-) diff --git a/app/(pages)/(hackers)/_components/StarterKit/StarterKit.tsx b/app/(pages)/(hackers)/_components/StarterKit/StarterKit.tsx index 66b39450..d636439d 100644 --- a/app/(pages)/(hackers)/_components/StarterKit/StarterKit.tsx +++ b/app/(pages)/(hackers)/_components/StarterKit/StarterKit.tsx @@ -1,5 +1,6 @@ 'use client'; +import { useEffect, useState } from 'react'; import DesignDevResources from './Resources/DesignDevResources'; import Ideate from './Ideate/Ideate'; import Introduction from './Introduction'; @@ -7,53 +8,69 @@ import MoreTips from './MoreTips'; import TeamBuilding from './TeamBuilding'; const sections = [ - { - title: 'Introduction', - id: 'introduction', - Component: Introduction, - }, - { - title: 'Team Building', - id: 'team-building', - Component: TeamBuilding, - }, - { - title: 'Ideate', - id: 'ideate', - Component: Ideate, - }, - // Resources section has two subsections (Design and Dev), so it's handled differently in the sidebar and component rendering + { title: 'Introduction', id: 'introduction', Component: Introduction }, + { title: 'Team Building', id: 'team-building', Component: TeamBuilding }, + { title: 'Ideate', id: 'ideate', Component: Ideate }, { title: 'Design Resources', id: 'design-resources', Component: DesignDevResources, }, - { - title: 'More Tips', - id: 'more-tips', - Component: MoreTips, - }, + { title: 'More Tips', id: 'more-tips', Component: MoreTips }, ]; -// Separate links for the Design Resources section in the sidebar const designResourceLinks = [ - { - title: 'Design Resources', - id: 'design-resources', - }, - { - title: 'Dev Resources', - id: 'dev-resources', - }, + { title: 'Design Resources', id: 'design-resources' }, + { title: 'Dev Resources', id: 'dev-resources' }, ]; function scrollToSection(id: string) { - const element = document.getElementById(id); - if (!element) return; - element.scrollIntoView({ behavior: 'smooth', block: 'start' }); + document + .getElementById(id) + ?.scrollIntoView({ behavior: 'smooth', block: 'start' }); } export default function StarterKit() { + const [activeId, setActiveId] = useState('introduction'); + + useEffect(() => { + const handleScroll = () => { + const sectionIds = [...sections.map((s) => s.id)]; + + // If we're at the bottom of the page, highlight the last section + if ( + window.innerHeight + window.scrollY >= + document.body.scrollHeight - 10 + ) { + setActiveId('more-tips'); + return; + } + + for (const id of sectionIds) { + const el = document.getElementById(id); + if (!el) continue; + + const { top } = el.getBoundingClientRect(); + if (top <= 150) setActiveId(id); + } + }; + + window.addEventListener('scroll', handleScroll); + return () => window.removeEventListener('scroll', handleScroll); + }, []); + + const isActive = (id: string) => activeId === id; + + const buttonClass = (id: string) => + `font-dm-mono text-[16px] uppercase text-left transition-colors duration-200 relative pl-4 + before:absolute before:left-0 before:top-1/2 before:-translate-y-1/2 before:w-1.5 before:h-1.5 before:rounded-full + before:transition-all before:duration-200 + ${ + isActive(id) + ? 'text-black before:bg-[#3F3F3F] before:opacity-100' + : 'text-[#ACACB9] before:opacity-0' + }`; + return (
@@ -65,7 +82,7 @@ export default function StarterKit() { key={link.id} type="button" onClick={() => scrollToSection(link.id)} - className="font-dm-mono text-[16px] text-[#ACACB9] uppercase text-left" + className={buttonClass(link.id)} > {link.title} @@ -75,7 +92,7 @@ export default function StarterKit() { key={section.id} type="button" onClick={() => scrollToSection(section.id)} - className="font-dm-mono text-[16px] text-[#ACACB9] uppercase text-left" + className={buttonClass(section.id)} > {section.title} From 2c02a9483e96deaf16bd62cb2aa5ebb160506a37 Mon Sep 17 00:00:00 2001 From: Jack Zheng Date: Wed, 6 May 2026 01:10:23 -0700 Subject: [PATCH 2/3] fix: add highlight to dev resources and remove design + dev resources --- .../_components/StarterKit/StarterKit.tsx | 54 +++++++------------ 1 file changed, 19 insertions(+), 35 deletions(-) diff --git a/app/(pages)/(hackers)/_components/StarterKit/StarterKit.tsx b/app/(pages)/(hackers)/_components/StarterKit/StarterKit.tsx index d636439d..9dc8b085 100644 --- a/app/(pages)/(hackers)/_components/StarterKit/StarterKit.tsx +++ b/app/(pages)/(hackers)/_components/StarterKit/StarterKit.tsx @@ -16,14 +16,10 @@ const sections = [ id: 'design-resources', Component: DesignDevResources, }, + { title: 'Dev Resources', id: 'dev-resources', Component: null }, { title: 'More Tips', id: 'more-tips', Component: MoreTips }, ]; -const designResourceLinks = [ - { title: 'Design Resources', id: 'design-resources' }, - { title: 'Dev Resources', id: 'dev-resources' }, -]; - function scrollToSection(id: string) { document .getElementById(id) @@ -35,7 +31,7 @@ export default function StarterKit() { useEffect(() => { const handleScroll = () => { - const sectionIds = [...sections.map((s) => s.id)]; + const sectionIds = sections.map((s) => s.id); // If we're at the bottom of the page, highlight the last section if ( @@ -74,37 +70,25 @@ export default function StarterKit() { return (
- {sections.map((section) => - // Render separate link for Design and Dev Resources in sidebar - section.id === 'design-resources' ? ( - designResourceLinks.map((link) => ( - - )) - ) : ( - - ) - )} + {sections.map((section) => ( + + ))}
- {sections.map(({ id, Component }) => ( -
- -
- ))} + {sections + .filter(({ Component }) => Component !== null) + .map(({ id, Component }) => ( +
+ +
+ ))}
); From ce8e0790a8a3d9f35d46fa04cf8a5657f498b5b4 Mon Sep 17 00:00:00 2001 From: Jack Zheng Date: Wed, 6 May 2026 17:02:44 -0700 Subject: [PATCH 3/3] fix: assert type recognition --- .../_components/StarterKit/StarterKit.tsx | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/app/(pages)/(hackers)/_components/StarterKit/StarterKit.tsx b/app/(pages)/(hackers)/_components/StarterKit/StarterKit.tsx index 9dc8b085..49eed590 100644 --- a/app/(pages)/(hackers)/_components/StarterKit/StarterKit.tsx +++ b/app/(pages)/(hackers)/_components/StarterKit/StarterKit.tsx @@ -7,7 +7,11 @@ import Introduction from './Introduction'; import MoreTips from './MoreTips'; import TeamBuilding from './TeamBuilding'; -const sections = [ +const sections: { + title: string; + id: string; + Component: React.ComponentType | null; +}[] = [ { title: 'Introduction', id: 'introduction', Component: Introduction }, { title: 'Team Building', id: 'team-building', Component: TeamBuilding }, { title: 'Ideate', id: 'ideate', Component: Ideate }, @@ -33,7 +37,6 @@ export default function StarterKit() { const handleScroll = () => { const sectionIds = sections.map((s) => s.id); - // If we're at the bottom of the page, highlight the last section if ( window.innerHeight + window.scrollY >= document.body.scrollHeight - 10 @@ -84,11 +87,14 @@ export default function StarterKit() {
{sections .filter(({ Component }) => Component !== null) - .map(({ id, Component }) => ( -
- -
- ))} + .map(({ id, Component }) => { + const C = Component as React.ComponentType; + return ( +
+ +
+ ); + })}
);