feat: add MobileNav, SidebarNav, and UtilityBar widgets

TDD implementation of three navigation widgets: mobile overlay toggle,
fixed sidebar with IntersectionObserver-driven active section tracking,
and utility bar with contact info and CV download action.
This commit is contained in:
Ilia Mashkov
2026-04-19 08:40:08 +03:00
parent 590147adb1
commit cfe50069b7
7 changed files with 346 additions and 0 deletions
+66
View File
@@ -0,0 +1,66 @@
'use client'
import { useState } from 'react'
import { cn } from '$shared/lib'
import type { NavItem } from '../model/types'
interface Props {
/**
* Navigation items to render
*/
items: NavItem[]
}
/**
* Mobile navigation overlay, hidden on lg+ screens.
*/
export function MobileNav({ items }: Props) {
const [isOpen, setIsOpen] = useState(false)
/**
* Scrolls to the section by id with a 100px offset, then closes the menu.
*/
function scrollToSection(id: string) {
const el = document.getElementById(id)
if (el) {
const top = el.getBoundingClientRect().top + window.scrollY - 100
window.scrollTo({ top, behavior: 'smooth' })
}
setIsOpen(false)
}
return (
<div className="lg:hidden fixed top-0 left-0 right-0 bg-ochre-clay brutal-border-bottom z-50">
<div className="px-6 py-4 flex items-center justify-between">
<h4>allmy.work</h4>
<button
onClick={() => setIsOpen(prev => !prev)}
className="brutal-border px-4 py-2 bg-carbon-black text-ochre-clay"
>
{isOpen ? 'Close' : 'Menu'}
</button>
</div>
{isOpen && (
<div className="px-6 py-6 brutal-border-top space-y-2 max-h-[80vh] overflow-y-auto">
{items.map(item => (
<button
key={item.id}
onClick={() => scrollToSection(item.id)}
className="w-full text-left brutal-border bg-ochre-clay px-4 py-3"
>
<div className={cn('flex items-baseline gap-3')}>
<span className="text-sm opacity-60 font-body">{item.number}</span>
<span
className="font-heading text-lg font-black"
style={{ fontVariationSettings: '"WONK" 1, "SOFT" 0' }}
>
{item.label}
</span>
</div>
</button>
))}
</div>
)}
</div>
)
}