Files
mstfyldzandClaude Sonnet 4.6 d369c9e258 Redesign nav: sticky horizontal scrollable category strip
19 categories can't fit in a fixed bottom tab bar. Replace with a
sticky horizontal pill strip that sits below the header, auto-scrolls
active tab into view via Intersection Observer, and shows emoji + label
at a readable size with teal active highlight.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 03:21:35 +03:00

135 lines
4.0 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client'
import { useEffect, useRef, useState } from 'react'
import type { Category } from '@/data/menu'
type Props = {
categories: Category[]
lang: string
}
export default function BottomTabBar({ categories, lang }: Props) {
const l = lang as 'tr' | 'en'
const [activeId, setActiveId] = useState<string>(categories[0]?.id ?? '')
const scrollRef = useRef<HTMLDivElement>(null)
/* ── Intersection Observer: aktif kategoriyi takip et ── */
useEffect(() => {
const observers: IntersectionObserver[] = []
categories.forEach((cat) => {
const el = document.getElementById(cat.id)
if (!el) return
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) setActiveId(cat.id)
})
},
{ rootMargin: '-15% 0px -80% 0px', threshold: 0 }
)
observer.observe(el)
observers.push(observer)
})
return () => observers.forEach((o) => o.disconnect())
}, [categories])
/* ── Aktif tab görünüme kaydır ── */
useEffect(() => {
if (!scrollRef.current) return
const activeEl = scrollRef.current.querySelector<HTMLElement>(
`[data-id="${activeId}"]`
)
if (!activeEl) return
activeEl.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center' })
}, [activeId])
return (
<nav
className="sticky z-30 bg-coffee-dark border-b border-white/[0.06]"
style={{ top: 'var(--header-h)' }}
aria-label="Kategori navigasyonu"
>
{/* Scrollable pill strip */}
<div
ref={scrollRef}
className="flex gap-2 overflow-x-auto px-3 py-2.5"
style={{
scrollbarWidth: 'none',
msOverflowStyle: 'none',
WebkitOverflowScrolling: 'touch',
}}
>
{categories.map((cat) => {
const isActive = activeId === cat.id
return (
<a
key={cat.id}
href={`#${cat.id}`}
data-id={cat.id}
className="flex-shrink-0 flex items-center gap-1.5 px-3 py-1.5 rounded-full transition-all duration-200 select-none"
style={
isActive
? {
background:
'linear-gradient(135deg, rgba(74,175,168,0.28), rgba(92,138,92,0.20))',
border: '1px solid rgba(74,175,168,0.50)',
boxShadow: '0 0 10px rgba(74,175,168,0.18)',
}
: {
background: 'rgba(255,255,255,0.05)',
border: '1px solid rgba(255,255,255,0.08)',
}
}
>
{/* Emoji */}
<span
className="text-base leading-none"
style={{
filter: isActive
? 'drop-shadow(0 0 4px rgba(74,175,168,0.55))'
: 'none',
transition: 'filter 0.2s',
}}
>
{cat.emoji}
</span>
{/* Label */}
<span
className="font-body font-medium whitespace-nowrap"
style={{
fontSize: '0.72rem',
letterSpacing: '0.01em',
color: isActive ? '#4AAFA8' : 'rgba(245,240,232,0.45)',
transition: 'color 0.2s',
}}
>
{cat.label[l]}
</span>
</a>
)
})}
</div>
{/* Sağ ve sol fade maskesi */}
<div
className="pointer-events-none absolute inset-y-0 left-0 w-4"
style={{
background:
'linear-gradient(to right, rgba(31,20,10,0.9), transparent)',
}}
/>
<div
className="pointer-events-none absolute inset-y-0 right-0 w-6"
style={{
background:
'linear-gradient(to left, rgba(31,20,10,0.9), transparent)',
}}
/>
</nav>
)
}