102 lines
3.3 KiB
TypeScript
102 lines
3.3 KiB
TypeScript
"use client";
|
|
|
|
import { useState } from "react";
|
|
import Link from "next/link";
|
|
import { ChevronDown } from "lucide-react";
|
|
import { cn } from "@/lib/utils";
|
|
|
|
interface DropdownItem {
|
|
label: string;
|
|
href: string;
|
|
description?: string;
|
|
}
|
|
|
|
interface DropdownSection {
|
|
title?: string;
|
|
items: DropdownItem[];
|
|
}
|
|
|
|
interface NavDropdownProps {
|
|
label: string;
|
|
triggerHref?: string;
|
|
sections: DropdownSection[];
|
|
align?: "left" | "center" | "right";
|
|
}
|
|
|
|
export function NavDropdown({ label, triggerHref, sections, align = "center" }: NavDropdownProps) {
|
|
const [isOpen, setIsOpen] = useState(false);
|
|
|
|
const handleKeyDown = (e: React.KeyboardEvent) => {
|
|
if (e.key === "Escape") {
|
|
setIsOpen(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div
|
|
className="relative group"
|
|
onMouseEnter={() => setIsOpen(true)}
|
|
onMouseLeave={() => setIsOpen(false)}
|
|
onKeyDown={handleKeyDown}
|
|
>
|
|
<div className="flex items-center gap-1 py-2 text-sm font-medium text-primary hover:text-accent cursor-pointer transition-colors focus-within:text-accent">
|
|
{triggerHref ? (
|
|
<Link href={triggerHref} className="outline-none">
|
|
{label}
|
|
</Link>
|
|
) : (
|
|
<span tabIndex={0} className="outline-none select-none">
|
|
{label}
|
|
</span>
|
|
)}
|
|
<ChevronDown className="size-4 transition-transform duration-200 group-hover:rotate-180" />
|
|
</div>
|
|
|
|
{/* Dropdown Panel */}
|
|
<div
|
|
className={cn(
|
|
"absolute top-full pt-2 z-50 w-[640px] transition-all duration-200 origin-top",
|
|
align === "left" && "left-0",
|
|
align === "center" && "left-1/2 -translate-x-1/2",
|
|
align === "right" && "right-0",
|
|
isOpen
|
|
? "opacity-100 translate-y-0 scale-100 pointer-events-auto"
|
|
: "opacity-0 -translate-y-2 scale-95 pointer-events-none"
|
|
)}
|
|
>
|
|
<div className="bg-white rounded-xl shadow-lg border border-border p-6 grid grid-cols-2 gap-8">
|
|
{sections.map((section, sIdx) => (
|
|
<div key={sIdx} className="flex flex-col gap-3">
|
|
{section.title && (
|
|
<h4 className="text-xs font-semibold uppercase tracking-wider text-text-muted mb-1 font-heading">
|
|
{section.title}
|
|
</h4>
|
|
)}
|
|
<ul className="flex flex-col gap-2">
|
|
{section.items.map((item, iIdx) => (
|
|
<li key={iIdx}>
|
|
<Link
|
|
href={item.href}
|
|
className="group/item flex flex-col gap-0.5 p-2 -mx-2 rounded-lg hover:bg-muted transition-colors"
|
|
onClick={() => setIsOpen(false)}
|
|
>
|
|
<span className="text-sm font-medium text-primary group-hover/item:text-accent transition-colors">
|
|
{item.label}
|
|
</span>
|
|
{item.description && (
|
|
<span className="text-xs text-text-muted leading-snug">
|
|
{item.description}
|
|
</span>
|
|
)}
|
|
</Link>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|