73 lines
2.7 KiB
TypeScript
73 lines
2.7 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useState } from "react";
|
|
|
|
const SECTIONS = [
|
|
{ id: "map", emoji: "🗺️", label: "地图" },
|
|
{ id: "destinations", emoji: "🌍", label: "目的地" },
|
|
{ id: "spin", emoji: "🎲", label: "转盘" },
|
|
{ id: "timezone", emoji: "🕐", label: "时区" },
|
|
{ id: "jetlag", emoji: "😴", label: "时差" },
|
|
{ id: "trip", emoji: "🗓️", label: "行程" },
|
|
{ id: "calculator", emoji: "🧮", label: "计算器" },
|
|
{ id: "cost-compare", emoji: "💰", label: "省钱" },
|
|
{ id: "savings", emoji: "🎯", label: "存款" },
|
|
{ id: "expenses", emoji: "📒", label: "账本" },
|
|
{ id: "flight", emoji: "✈️", label: "机票" },
|
|
{ id: "wifi", emoji: "📶", label: "网速" },
|
|
{ id: "lifestyle", emoji: "💻", label: "生活" },
|
|
{ id: "visa", emoji: "📋", label: "签证" },
|
|
{ id: "insurance", emoji: "🏥", label: "保险" },
|
|
{ id: "coworking", emoji: "💻", label: "办公" },
|
|
{ id: "housing", emoji: "🏡", label: "住宿" },
|
|
{ id: "sim", emoji: "📱", label: "上网" },
|
|
{ id: "season", emoji: "🌤️", label: "季节" },
|
|
{ id: "packing", emoji: "🧳", label: "行李" },
|
|
{ id: "phrases", emoji: "🗣️", label: "短语" },
|
|
{ id: "events", emoji: "📅", label: "活动" },
|
|
{ id: "blog", emoji: "📝", label: "博客" },
|
|
];
|
|
|
|
export default function SectionNav() {
|
|
const [active, setActive] = useState("");
|
|
const [visible, setVisible] = useState(false);
|
|
|
|
useEffect(() => {
|
|
const onScroll = () => setVisible(window.scrollY > 400);
|
|
window.addEventListener("scroll", onScroll, { passive: true });
|
|
onScroll();
|
|
return () => window.removeEventListener("scroll", onScroll);
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
const sections = SECTIONS.map((s) => document.getElementById(s.id)).filter(Boolean);
|
|
const observer = new IntersectionObserver(
|
|
(entries) => entries.forEach((e) => { if (e.isIntersecting) setActive(e.target.id); }),
|
|
{ threshold: 0.25, rootMargin: "-80px 0px -55% 0px" }
|
|
);
|
|
sections.forEach((s) => observer.observe(s!));
|
|
return () => observer.disconnect();
|
|
}, []);
|
|
|
|
const scrollTo = (id: string) => {
|
|
document.getElementById(id)?.scrollIntoView({ behavior: "smooth" });
|
|
};
|
|
|
|
return (
|
|
<nav className={`section-nav${visible ? " visible" : ""}`} aria-label="章节导航">
|
|
{SECTIONS.map((s) => (
|
|
<button
|
|
key={s.id}
|
|
className={`section-nav-dot${active === s.id ? " active" : ""}`}
|
|
onClick={() => scrollTo(s.id)}
|
|
aria-label={s.label}
|
|
title={s.label}
|
|
>
|
|
<span className="section-nav-emoji">{s.emoji}</span>
|
|
<span className="section-nav-label">{s.label}</span>
|
|
</button>
|
|
))}
|
|
</nav>
|
|
);
|
|
}
|