Pending course, rest ready for launch
This commit is contained in:
82
components/courses/CourseLevelTabs.tsx
Normal file
82
components/courses/CourseLevelTabs.tsx
Normal file
@@ -0,0 +1,82 @@
|
||||
"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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user