Files
cabrits-math/components/layout/sidebar.tsx
2026-07-05 11:12:45 -04:00

175 lines
5.5 KiB
TypeScript

"use client";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { curriculum } from "@/lib/curriculum";
import { cn } from "@/lib/utils";
import { useState } from "react";
import { sidebarAsideClass, sidebarNavClass } from "./collapsible-sidebar";
const unitColorMap = {
"unit-1": {
active: "bg-unit-1-light text-unit-1-dark border-unit-1/20",
dot: "bg-unit-1",
heading: "text-unit-1-dark",
hoverBg: "hover:bg-unit-1-light/50",
},
"unit-2": {
active: "bg-unit-2-light text-unit-2-dark border-unit-2/20",
dot: "bg-unit-2",
heading: "text-unit-2-dark",
hoverBg: "hover:bg-unit-2-light/50",
},
"unit-3": {
active: "bg-unit-3-light text-unit-3-dark border-unit-3/20",
dot: "bg-unit-3",
heading: "text-unit-3-dark",
hoverBg: "hover:bg-unit-3-light/50",
},
"unit-4": {
active: "bg-unit-4-light text-unit-4-dark border-unit-4/20",
dot: "bg-unit-4",
heading: "text-unit-4-dark",
hoverBg: "hover:bg-unit-4-light/50",
},
"unit-5": {
active: "bg-unit-5-light text-unit-5-dark border-unit-5/20",
dot: "bg-unit-5",
heading: "text-unit-5-dark",
hoverBg: "hover:bg-unit-5-light/50",
},
"unit-6": {
active: "bg-unit-6-light text-unit-6-dark border-unit-6/20",
dot: "bg-unit-6",
heading: "text-unit-6-dark",
hoverBg: "hover:bg-unit-6-light/50",
},
"unit-7": {
active: "bg-unit-7-light text-unit-7-dark border-unit-7/20",
dot: "bg-unit-7",
heading: "text-unit-7-dark",
hoverBg: "hover:bg-unit-7-light/50",
},
"unit-8": {
active: "bg-unit-8-light text-unit-8-dark border-unit-8/20",
dot: "bg-unit-8",
heading: "text-unit-8-dark",
hoverBg: "hover:bg-unit-8-light/50",
},
};
export function Sidebar({
mobileOpen = false,
desktopCollapsed = false,
onNavigate,
}: {
mobileOpen?: boolean;
desktopCollapsed?: boolean;
onNavigate?: () => void;
}) {
const pathname = usePathname();
const [openUnits, setOpenUnits] = useState<Set<number>>(() => {
const open = new Set<number>();
for (const unit of curriculum) {
if (pathname.includes(unit.slug)) {
open.add(unit.number);
}
}
if (open.size === 0) open.add(1);
return open;
});
function toggleUnit(num: number) {
setOpenUnits((prev) => {
const next = new Set(prev);
if (next.has(num)) next.delete(num);
else next.add(num);
return next;
});
}
return (
<aside className={sidebarAsideClass(mobileOpen, desktopCollapsed)}>
<nav className={sidebarNavClass}>
<Link
href="/lessons"
onClick={onNavigate}
className={cn(
"mb-4 flex items-center gap-2 rounded-xl px-3 py-2 text-sm font-medium transition-all duration-200",
pathname === "/lessons"
? "bg-foreground text-background shadow-[var(--shadow-sm)]"
: "text-muted hover:bg-background hover:text-foreground",
)}
>
All Topics
</Link>
<div className="mb-3 h-px bg-border/60" />
{curriculum.map((unit) => {
const colors = unitColorMap[unit.color];
const isOpen = openUnits.has(unit.number);
return (
<div key={unit.slug} className="mb-1">
<button
onClick={() => toggleUnit(unit.number)}
className={cn(
"flex w-full items-center justify-between rounded-xl px-3 py-2 text-left text-sm font-semibold transition-all duration-200",
colors.heading,
colors.hoverBg,
)}
>
<span className="truncate">Unit {unit.number}: {unit.title}</span>
<svg
className={cn(
"h-4 w-4 shrink-0 transition-transform duration-200",
isOpen && "rotate-90",
)}
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
strokeWidth={2}
>
<path strokeLinecap="round" strokeLinejoin="round" d="M9 5l7 7-7 7" />
</svg>
</button>
{isOpen && (
<div className="ml-2 mt-0.5 space-y-0.5 border-l-2 border-border/40 pl-2">
{unit.topics.map((topic) => {
const href = `/lessons/${unit.slug}/${topic.slug}`;
const isActive = pathname === href;
return (
<Link
key={topic.slug}
href={href}
onClick={onNavigate}
className={cn(
"flex items-center gap-2 rounded-lg px-2.5 py-1.5 text-sm transition-all duration-200",
isActive
? cn(colors.active, "font-medium shadow-[var(--shadow-sm)]")
: "text-muted hover:text-foreground",
)}
>
<span
className={cn(
"h-1.5 w-1.5 shrink-0 rounded-full transition-colors",
isActive ? colors.dot : "bg-border",
)}
/>
<span className="truncate">{topic.shortTitle}</span>
</Link>
);
})}
</div>
)}
</div>
);
})}
</nav>
</aside>
);
}