83 lines
2.6 KiB
TypeScript
83 lines
2.6 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useMemo, useState } from "react";
|
|
import { cn } from "@/lib/utils";
|
|
|
|
type CourseLevelTabItem = {
|
|
id: string;
|
|
label: string;
|
|
anchorId: string;
|
|
count: number;
|
|
};
|
|
|
|
type CourseLevelTabsProps = {
|
|
items: CourseLevelTabItem[];
|
|
};
|
|
|
|
export default function CourseLevelTabs({ items }: CourseLevelTabsProps) {
|
|
const [activeId, setActiveId] = useState(items[0]?.id ?? "");
|
|
|
|
const sectionIds = useMemo(() => items.map((item) => item.anchorId), [items]);
|
|
|
|
useEffect(() => {
|
|
if (items.length === 0) return;
|
|
|
|
const observer = new IntersectionObserver(
|
|
(entries) => {
|
|
const visible = entries
|
|
.filter((entry) => entry.isIntersecting)
|
|
.sort((a, b) => b.intersectionRatio - a.intersectionRatio);
|
|
|
|
if (visible.length === 0) return;
|
|
const matched = items.find((item) => item.anchorId === visible[0].target.id);
|
|
if (matched) setActiveId(matched.id);
|
|
},
|
|
{
|
|
rootMargin: "-30% 0px -55% 0px",
|
|
threshold: [0.2, 0.35, 0.5, 0.7],
|
|
},
|
|
);
|
|
|
|
sectionIds.forEach((sectionId) => {
|
|
const element = document.getElementById(sectionId);
|
|
if (element) observer.observe(element);
|
|
});
|
|
|
|
return () => observer.disconnect();
|
|
}, [items, sectionIds]);
|
|
|
|
const scrollToSection = (anchorId: string, id: string) => {
|
|
const section = document.getElementById(anchorId);
|
|
if (!section) return;
|
|
section.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
setActiveId(id);
|
|
};
|
|
|
|
return (
|
|
<section className="acve-panel acve-section-tight sticky top-[8.4rem] z-30 border-border/80 bg-card/90 backdrop-blur">
|
|
<p className="mb-3 text-xs font-semibold uppercase tracking-[0.14em] text-muted-foreground">Nivel académico</p>
|
|
<div className="grid gap-2 sm:grid-cols-3">
|
|
{items.map((item) => {
|
|
const isActive = item.id === activeId;
|
|
return (
|
|
<button
|
|
key={item.id}
|
|
className={cn(
|
|
"rounded-xl border px-3 py-2 text-left transition-colors",
|
|
isActive
|
|
? "border-primary/45 bg-primary/10 text-primary"
|
|
: "border-border/80 bg-card/70 text-foreground hover:border-primary/30 hover:bg-accent/60",
|
|
)}
|
|
type="button"
|
|
onClick={() => scrollToSection(item.anchorId, item.id)}
|
|
>
|
|
<p className="text-sm font-semibold">{item.label}</p>
|
|
<p className="text-xs text-muted-foreground">{item.count} programas</p>
|
|
</button>
|
|
);
|
|
})}
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|