Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 73 additions & 27 deletions src/components/layout/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client";

import { useState, useEffect } from 'react';
import { useState, useEffect, useRef } from 'react';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
import { Button } from '@/components/ui/button';
Expand All @@ -11,8 +11,18 @@ const Header = () => {
const [isMenuOpen, setIsMenuOpen] = useState(false);
const [dropdownOpen, setDropdownOpen] = useState<string | null>(null);
const pathname = usePathname();
const closeTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);

const openDropdown = (name: string) => {
if (closeTimeoutRef.current) clearTimeout(closeTimeoutRef.current);
setDropdownOpen(name);
};

const scheduleCloseDropdown = () => {
if (closeTimeoutRef.current) clearTimeout(closeTimeoutRef.current);
closeTimeoutRef.current = setTimeout(() => setDropdownOpen(null), 150);
};

// Close dropdown when clicking outside
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (dropdownOpen && !(event.target as Element).closest('.dropdown-container')) {
Expand All @@ -28,6 +38,7 @@ const Header = () => {
{
name: 'Education',
href: '/education',
hasLandingPage: true,
dropdown: [
{ name: 'Private Bitcoin Briefing', href: '/education/private-bitcoin-briefing' },
{ name: 'Bitcoin Executive Masterclass', href: '/education/bitcoin-for-executives' },
Expand All @@ -37,6 +48,7 @@ const Header = () => {
{
name: 'Research',
href: '/research',
hasLandingPage: true,
dropdown: [
{ name: 'Bitcoin Intelligence Briefs', href: '/research/intelligence-briefs' },
{ name: 'Bitcoin Research Quarterly', href: '/research/resq-package' },
Expand Down Expand Up @@ -78,28 +90,51 @@ const Header = () => {
<div className="hidden md:flex items-center space-x-8">
{navigation.map((item) => (
'dropdown' in item ? (
<div key={item.name} className="relative dropdown-container">
<button
onClick={() => setDropdownOpen(dropdownOpen === item.name ? null : item.name)}
className={`flex items-center text-sm font-semibold transition-colors hover:text-primary-brand ${isActive(item.href) || item.dropdown?.some(sub => isActive(sub.href)) ? 'text-primary-brand' : 'text-gray-600'
}`}
>
{item.name}
<ChevronDown className="ml-1 h-4 w-4" />
</button>
<div
key={item.name}
className="relative dropdown-container"
onMouseEnter={() => openDropdown(item.name)}
onMouseLeave={scheduleCloseDropdown}
>
{'hasLandingPage' in item ? (
<Link
href={item.href}
onFocus={() => openDropdown(item.name)}
className={`flex items-center text-sm font-semibold transition-colors hover:text-primary-brand ${isActive(item.href) || item.dropdown?.some(sub => isActive(sub.href)) ? 'text-primary-brand' : 'text-gray-600'
}`}
>
{item.name}
<ChevronDown className="ml-1 h-4 w-4" />
</Link>
) : (
<button
type="button"
aria-haspopup="true"
aria-expanded={dropdownOpen === item.name}
onClick={() => setDropdownOpen(dropdownOpen === item.name ? null : item.name)}
onFocus={() => openDropdown(item.name)}
className={`flex items-center text-sm font-semibold transition-colors hover:text-primary-brand ${isActive(item.href) || item.dropdown?.some(sub => isActive(sub.href)) ? 'text-primary-brand' : 'text-gray-600'
}`}
>
{item.name}
<ChevronDown className="ml-1 h-4 w-4" />
</button>
)}
{dropdownOpen === item.name && item.dropdown && (
<div className="absolute top-full mt-2 w-48 bg-white rounded-md shadow-lg border border-gray-200 py-1 z-50">
{item.dropdown.map((subItem) => (
<Link
key={subItem.name}
href={subItem.href}
className={`block px-4 py-2 text-sm hover:bg-gray-50 ${isActive(subItem.href) ? 'text-primary-brand' : 'text-gray-600'
}`}
onClick={() => setDropdownOpen(null)}
>
{subItem.name}
</Link>
))}
<div className="absolute top-full left-0 pt-2 w-48 z-50">
<div className="bg-white rounded-md shadow-lg border border-gray-200 py-1">
{item.dropdown.map((subItem) => (
<Link
key={subItem.name}
href={subItem.href}
className={`block px-4 py-2 text-sm hover:bg-gray-50 ${isActive(subItem.href) ? 'text-primary-brand' : 'text-gray-600'
}`}
onClick={() => setDropdownOpen(null)}
>
{subItem.name}
</Link>
))}
</div>
</div>
)}
</div>
Expand Down Expand Up @@ -149,10 +184,21 @@ const Header = () => {
{navigation.map((item) => (
'dropdown' in item ? (
<div key={item.name}>
<div className={`block px-3 py-2 text-base font-semibold ${isActive(item.href) || item.dropdown?.some(sub => isActive(sub.href)) ? 'text-primary-brand' : 'text-gray-600'
}`}>
{item.name}
</div>
{'hasLandingPage' in item ? (
<Link
href={item.href}
onClick={() => setIsMenuOpen(false)}
className={`block px-3 py-2 text-base font-semibold transition-colors hover:text-primary-brand ${isActive(item.href) || item.dropdown?.some(sub => isActive(sub.href)) ? 'text-primary-brand' : 'text-gray-600'
}`}
>
{item.name}
</Link>
) : (
<div className={`block px-3 py-2 text-base font-semibold ${isActive(item.href) || item.dropdown?.some(sub => isActive(sub.href)) ? 'text-primary-brand' : 'text-gray-600'
}`}>
{item.name}
</div>
)}
<div className="pl-6 space-y-1">
{item.dropdown?.map((subItem) => (
<Link
Expand Down
Loading