refactor: SidebarNav uses usePathname and Link instead of IntersectionObserver
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { CONTACT_LINKS, cn } from '$shared/lib';
|
||||
import type { NavItem } from '../model/types';
|
||||
|
||||
@@ -13,41 +14,23 @@ interface Props {
|
||||
|
||||
/**
|
||||
* Fixed sidebar navigation, visible on lg+ screens.
|
||||
* Active section determined by current URL pathname.
|
||||
*/
|
||||
export function SidebarNav({ items }: Props) {
|
||||
const [activeSection, setActiveSection] = useState('bio');
|
||||
|
||||
useEffect(() => {
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
entries.forEach((entry) => {
|
||||
if (entry.isIntersecting) {
|
||||
setActiveSection(entry.target.id);
|
||||
}
|
||||
});
|
||||
},
|
||||
{ rootMargin: '-20% 0px -70% 0px', threshold: 0 },
|
||||
);
|
||||
|
||||
items.forEach((item) => {
|
||||
const el = document.getElementById(item.id);
|
||||
if (el) {
|
||||
observer.observe(el);
|
||||
}
|
||||
});
|
||||
|
||||
return () => observer.disconnect();
|
||||
}, [items]);
|
||||
const pathname = usePathname();
|
||||
|
||||
/**
|
||||
* Scrolls to the section by id with a 40px offset.
|
||||
* An item is active when its slug matches the current pathname,
|
||||
* or when the pathname is root and it is the first item.
|
||||
*/
|
||||
function scrollToSection(id: string) {
|
||||
const el = document.getElementById(id);
|
||||
if (el) {
|
||||
const top = el.getBoundingClientRect().top + window.scrollY - 40;
|
||||
window.scrollTo({ top, behavior: 'smooth' });
|
||||
function isActive(item: NavItem): boolean {
|
||||
if (pathname === `/${item.id}`) {
|
||||
return true;
|
||||
}
|
||||
if (pathname === '/' && items[0]?.id === item.id) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -60,27 +43,23 @@ export function SidebarNav({ items }: Props) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{items.map((item) => {
|
||||
const isActive = activeSection === item.id;
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
key={item.id}
|
||||
onClick={() => scrollToSection(item.id)}
|
||||
className={cn(
|
||||
'w-full text-left brutal-border bg-ochre-clay px-6 py-4 transition-all duration-300',
|
||||
isActive
|
||||
? 'shadow-[12px_12px_0_var(--carbon-black)] opacity-100 translate-x-0'
|
||||
: 'opacity-40 shadow-none hover:opacity-60',
|
||||
)}
|
||||
>
|
||||
<div className="flex items-baseline gap-4">
|
||||
<span className="text-sm opacity-60">{item.number}</span>
|
||||
<span className="font-heading text-xl font-black">{item.label}</span>
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
{items.map((item) => (
|
||||
<Link
|
||||
key={item.id}
|
||||
href={`/${item.id}`}
|
||||
className={cn(
|
||||
'block w-full text-left brutal-border bg-ochre-clay px-6 py-4 transition-all duration-300',
|
||||
isActive(item)
|
||||
? 'shadow-[12px_12px_0_var(--carbon-black)] opacity-100 translate-x-0'
|
||||
: 'opacity-40 shadow-none hover:opacity-60',
|
||||
)}
|
||||
>
|
||||
<div className="flex items-baseline gap-4">
|
||||
<span className="text-sm opacity-60">{item.number}</span>
|
||||
<span className="font-heading text-xl font-black">{item.label}</span>
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
|
||||
<div className="mt-12 pt-12 brutal-border-top">
|
||||
<p className="text-sm uppercase tracking-wider mb-4 opacity-60">Quick Links</p>
|
||||
|
||||
Reference in New Issue
Block a user