Files
novalon-website/src/components/layout/mega-dropdown.tsx
T
张翔 37296b5717 feat(website): 三轮视觉改造与页面过渡动画
改造概要(30项):
- 第一轮:Hero重构/Section差异化/SocialProof强化/CTA对比度/About架构
- 第二轮:字体优化/背景交替/Solutions差异化/Footer五列/MegaDropdown修复
- 第三轮:卡片交互/表单层级/CTA统一/时间线标记/连接线/三列布局/移动导航/Button微交互/SEO Schema
- P3-2:template.tsx+Framer Motion页面过渡/loading.tsx加载状态
- 清理:删除未用组件/hooks,修复重复移动导航,清理冗余CSS
2026-05-10 08:20:27 +08:00

91 lines
2.9 KiB
TypeScript

'use client';
import { useRef, useEffect } from 'react';
import { ChevronDown } from 'lucide-react';
import { StaticLink } from '@/components/ui/static-link';
import { AnimatePresence, motion } from 'framer-motion';
interface MegaDropdownItem {
id: string;
title: string;
description: string;
href: string;
}
interface MegaDropdownProps {
label: string;
items: MegaDropdownItem[];
isOpen: boolean;
onToggle: () => void;
onOpen: () => void;
onClose: () => void;
}
export function MegaDropdown({ label, items, isOpen, onToggle, onOpen, onClose }: MegaDropdownProps) {
const dropdownRef = useRef<HTMLDivElement>(null);
const timeoutRef = useRef<ReturnType<typeof setTimeout>>(undefined);
const handleMouseEnter = () => {
clearTimeout(timeoutRef.current);
onOpen();
};
const handleMouseLeave = () => {
timeoutRef.current = setTimeout(onClose, 150);
};
useEffect(() => {
return () => clearTimeout(timeoutRef.current);
}, []);
return (
<div ref={dropdownRef} className="relative" onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave}>
<button
onClick={onToggle}
className={`
flex items-center gap-1 px-3 py-1.5 text-sm font-medium rounded-md
transition-all duration-200
${isOpen
? 'text-[var(--color-brand-primary)] bg-[var(--color-brand-primary-bg)]'
: 'text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)] hover:bg-[var(--color-bg-hover)]'
}
`}
aria-expanded={isOpen}
aria-haspopup="true"
>
{label}
<ChevronDown
className={`w-4 h-4 transition-transform duration-200 ${isOpen ? 'rotate-180' : ''}`}
/>
</button>
<AnimatePresence>
{isOpen && (
<motion.div
initial={{ opacity: 0, y: -8 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -8 }}
transition={{ duration: 0.15 }}
className="absolute top-full left-1/2 -translate-x-1/2 pt-2 w-[520px] z-50"
>
<div className="bg-[var(--color-bg-primary)] rounded-xl border border-[var(--color-border-primary)] shadow-lg p-5">
<div className="grid grid-cols-2 gap-3">
{items.map((item) => (
<StaticLink
key={item.id}
href={item.href}
className="block p-4 rounded-lg border-l-[3px] border-l-[var(--color-brand-primary)] hover:bg-[var(--color-bg-secondary)] transition-colors duration-200"
>
<div className="text-sm font-semibold text-[var(--color-text-primary)]">{item.title}</div>
<div className="text-xs text-[var(--color-text-placeholder)] mt-1.5 leading-relaxed">{item.description}</div>
</StaticLink>
))}
</div>
</div>
</motion.div>
)}
</AnimatePresence>
</div>
);
}