Files
asset-homepage/components/Navbar.tsx

295 lines
11 KiB
TypeScript
Raw Permalink Normal View History

2026-01-27 17:26:30 +08:00
'use client';
import { useState, useEffect } from 'react';
import Image from 'next/image';
2026-01-29 16:23:10 +08:00
import {
Navbar as HeroNavbar,
NavbarBrand,
NavbarContent,
NavbarItem,
Button,
Dropdown,
DropdownTrigger,
DropdownMenu,
DropdownItem
} from '@heroui/react';
2026-01-27 17:26:30 +08:00
import ProductMenu from './ProductMenu';
import ResourceMenu from './ResourceMenu';
2026-01-28 17:55:01 +08:00
import { useLanguage } from '@/contexts/LanguageContext';
2026-01-29 16:23:10 +08:00
import { useTheme } from '@/contexts/ThemeContext';
2026-01-27 17:26:30 +08:00
export default function Navbar() {
const [scrolled, setScrolled] = useState(false);
2026-01-28 17:55:01 +08:00
const { language, setLanguage, t } = useLanguage();
2026-01-29 16:23:10 +08:00
const { theme, toggleTheme } = useTheme();
2026-01-27 17:26:30 +08:00
const [showProductMenu, setShowProductMenu] = useState(false);
const [showResourceMenu, setShowResourceMenu] = useState(false);
2026-01-28 17:55:01 +08:00
const [animate, setAnimate] = useState(false);
2026-01-27 17:26:30 +08:00
useEffect(() => {
const handleScroll = () => {
setScrolled(window.scrollY > 50);
};
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, []);
2026-01-28 17:55:01 +08:00
useEffect(() => {
requestAnimationFrame(() => {
requestAnimationFrame(() => {
setAnimate(true);
});
});
}, []);
2026-01-27 17:26:30 +08:00
useEffect(() => {
const handleClickOutside = (e: MouseEvent) => {
const target = e.target as Element;
if (showProductMenu && !target.closest('.product-menu-container')) {
setShowProductMenu(false);
}
if (showResourceMenu && !target.closest('.resource-menu-container')) {
setShowResourceMenu(false);
}
};
document.addEventListener('click', handleClickOutside);
return () => document.removeEventListener('click', handleClickOutside);
2026-01-29 16:23:10 +08:00
}, [showProductMenu, showResourceMenu]);
2026-01-27 17:26:30 +08:00
2026-01-29 16:23:10 +08:00
const isDark = theme === 'dark';
2026-01-27 17:26:30 +08:00
return (
<>
2026-01-29 16:23:10 +08:00
<HeroNavbar
maxWidth="full"
2026-01-28 17:55:01 +08:00
style={{
2026-01-29 16:23:10 +08:00
backgroundColor: isDark
? scrolled ? 'rgba(10, 10, 10, 0.7)' : 'rgba(10, 10, 10, 0.9)'
: scrolled ? 'rgba(255, 255, 255, 0.7)' : 'rgba(255, 255, 255, 0.9)',
backdropFilter: 'blur(50px)',
borderBottom: isDark
? scrolled ? '1px solid #27272a' : '1px solid #18181b'
: '1px solid transparent',
transition: 'background-color 0.3s ease-out, border-color 0.3s ease-out'
}}
classNames={{
wrapper: "px-10 py-2"
2026-01-28 17:55:01 +08:00
}}
>
2026-01-29 16:23:10 +08:00
{/* Logo */}
<NavbarBrand>
<div
style={{
transform: animate ? 'translateY(0)' : 'translateY(-3rem)',
opacity: animate ? 1 : 0,
transition: 'transform 1s ease-out, opacity 1s ease-out'
}}
>
<Image
src="/logo0.svg"
alt="Logo"
width={160}
height={40}
priority
style={{
filter: isDark ? 'invert(1) brightness(1.2)' : 'none',
transition: 'filter 0.3s ease-out'
}}
/>
</div>
</NavbarBrand>
{/* Center Menu */}
<NavbarContent className="hidden sm:flex gap-6" justify="center">
{/* Product */}
<NavbarItem className="product-menu-container">
<Button
variant="light"
className={`font-bold text-sm select-none ${
showProductMenu
? isDark ? 'text-[#fafafa] bg-[#27272a]' : 'text-[#111827] bg-[#f3f4f6]'
: isDark ? 'text-[#a1a1aa]' : 'text-[#4b5563]'
2026-01-27 17:26:30 +08:00
}`}
2026-01-29 16:23:10 +08:00
onPress={() => {
2026-01-27 17:26:30 +08:00
setShowProductMenu(!showProductMenu);
setShowResourceMenu(false);
}}
>
2026-01-29 16:23:10 +08:00
{t('nav.product')}
</Button>
</NavbarItem>
2026-01-27 17:26:30 +08:00
2026-01-28 17:55:01 +08:00
{/* Community */}
2026-01-29 16:23:10 +08:00
<NavbarItem>
<Button
variant="light"
className={`font-bold text-sm select-none ${
isDark ? 'text-[#a1a1aa]' : 'text-[#4b5563]'
}`}
>
2026-01-28 17:55:01 +08:00
{t('nav.community')}
2026-01-29 16:23:10 +08:00
</Button>
</NavbarItem>
2026-01-27 17:26:30 +08:00
{/* Resource */}
2026-01-29 16:23:10 +08:00
<NavbarItem className="resource-menu-container">
<Button
variant="light"
className={`font-bold text-sm select-none ${
showResourceMenu
? isDark ? 'text-[#fafafa] bg-[#27272a]' : 'text-[#111827] bg-[#f3f4f6]'
: isDark ? 'text-[#a1a1aa]' : 'text-[#4b5563]'
2026-01-27 17:26:30 +08:00
}`}
2026-01-29 16:23:10 +08:00
onPress={() => {
2026-01-27 17:26:30 +08:00
setShowResourceMenu(!showResourceMenu);
setShowProductMenu(false);
}}
>
2026-01-29 16:23:10 +08:00
{t('nav.resource')}
</Button>
</NavbarItem>
</NavbarContent>
2026-01-27 17:26:30 +08:00
2026-01-29 16:23:10 +08:00
{/* Right Content */}
<NavbarContent justify="end" className="gap-4">
{/* Launch App Button */}
<NavbarItem>
<div
style={{
transform: animate ? 'translateY(0)' : 'translateY(-3rem)',
opacity: animate ? 1 : 0,
transition: 'transform 1s ease-out 200ms, opacity 1s ease-out 200ms'
}}
>
<Button
className="font-bold text-sm"
style={{
backgroundColor: isDark ? '#ffffff' : '#111827',
color: isDark ? '#0a0a0a' : '#fafafa',
transition: 'background-color 0.3s ease-out, color 0.3s ease-out'
}}
onPress={() => {}}
2026-01-27 17:26:30 +08:00
>
2026-01-29 16:23:10 +08:00
{t('nav.launchApp')}
</Button>
</div>
</NavbarItem>
{/* Theme Toggle */}
<NavbarItem>
<Button
isIconOnly
variant="light"
onPress={toggleTheme}
className="transition-colors"
>
<div className="relative w-5 h-5">
{/* Moon icon */}
<svg
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className="absolute inset-0 transition-all duration-300"
style={{
opacity: isDark ? 1 : 0,
transform: isDark ? 'rotate(0deg) scale(1)' : 'rotate(-90deg) scale(0.5)'
}}
>
<path d="M17.5 10.8333C17.3833 12.2083 16.875 13.4792 16.0625 14.5208C15.25 15.5625 14.1875 16.3333 12.9792 16.7292C11.7708 17.125 10.4792 17.125 9.27083 16.7292C8.0625 16.3333 7 15.5625 6.1875 14.5208C5.375 13.4792 4.86667 12.2083 4.75 10.8333C4.63333 9.45833 4.91667 8.08333 5.5625 6.875C6.20833 5.66667 7.1875 4.6875 8.375 4.04167C9.5625 3.39583 10.9167 3.125 12.2917 3.25C11.2083 4.45833 10.625 6.04167 10.625 7.70833C10.625 9.375 11.2083 10.9583 12.2917 12.1667C13.375 13.375 14.8333 14.0417 16.375 14.0417C16.7917 14.0417 17.2083 14 17.5 10.8333Z" stroke={isDark ? '#a1a1aa' : '#4B5563'} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
{/* Sun icon */}
<svg
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className="absolute inset-0 transition-all duration-300"
style={{
opacity: isDark ? 0 : 1,
transform: isDark ? 'rotate(90deg) scale(0.5)' : 'rotate(0deg) scale(1)'
}}
>
<path d="M10 2.5V4.16667M10 15.8333V17.5M17.5 10H15.8333M4.16667 10H2.5M15.3033 4.69667L14.1667 5.83333M5.83333 14.1667L4.69667 15.3033M15.3033 15.3033L14.1667 14.1667M5.83333 5.83333L4.69667 4.69667M13.3333 10C13.3333 11.8409 11.8409 13.3333 10 13.3333C8.15905 13.3333 6.66667 11.8409 6.66667 10C6.66667 8.15905 8.15905 6.66667 10 6.66667C11.8409 6.66667 13.3333 8.15905 13.3333 10Z" stroke={isDark ? '#a1a1aa' : '#4B5563'} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
2026-01-27 17:26:30 +08:00
</div>
2026-01-29 16:23:10 +08:00
</Button>
</NavbarItem>
{/* Language Selector */}
<NavbarItem>
<Dropdown
classNames={{
content: isDark ? 'bg-[#18181b] border-none shadow-lg min-w-[120px] w-[120px]' : 'bg-white border-none shadow-lg min-w-[120px] w-[120px]'
}}
>
<DropdownTrigger>
<Button
variant="light"
className={`font-bold text-sm ${
isDark ? 'text-[#a1a1aa]' : 'text-[#4b5563]'
}`}
startContent={
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10 18.3333C14.6024 18.3333 18.3333 14.6024 18.3333 10C18.3333 5.39763 14.6024 1.66667 10 1.66667C5.39763 1.66667 1.66667 5.39763 1.66667 10C1.66667 14.6024 5.39763 18.3333 10 18.3333Z" stroke={isDark ? '#a1a1aa' : '#4B5563'} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
<path d="M1.66667 10H18.3333" stroke={isDark ? '#a1a1aa' : '#4B5563'} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
<path d="M10 1.66667C12.0844 3.94863 13.269 6.91003 13.3333 10C13.269 13.09 12.0844 16.0514 10 18.3333C7.91561 16.0514 6.73104 13.09 6.66667 10C6.73104 6.91003 7.91561 3.94863 10 1.66667Z" stroke={isDark ? '#a1a1aa' : '#4B5563'} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
}
endContent={
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3 4.5L6 7.5L9 4.5" stroke={isDark ? '#a1a1aa' : '#4B5563'} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
}
>
{language === 'zh' ? '中文' : 'EN'}
</Button>
</DropdownTrigger>
<DropdownMenu
aria-label="Language selection"
onAction={(key) => setLanguage(key as 'zh' | 'en')}
selectedKeys={new Set([language])}
classNames={{
list: "py-2"
}}
itemClasses={{
base: "py-3 flex items-center justify-center"
}}
2026-01-27 17:26:30 +08:00
>
2026-01-29 16:23:10 +08:00
<DropdownItem
key="zh"
className={isDark ? 'text-[#fafafa] hover:bg-[#27272a] text-center' : 'text-[#111827] text-center'}
>
</DropdownItem>
<DropdownItem
key="en"
className={isDark ? 'text-[#fafafa] hover:bg-[#27272a] text-center' : 'text-[#111827] text-center'}
>
English
</DropdownItem>
</DropdownMenu>
</Dropdown>
</NavbarItem>
</NavbarContent>
</HeroNavbar>
{/* Custom Menus */}
<ProductMenu
isOpen={showProductMenu}
onClose={() => setShowProductMenu(false)}
language={language}
/>
<ResourceMenu
isOpen={showResourceMenu}
onClose={() => setShowResourceMenu(false)}
language={language}
/>
2026-01-27 17:26:30 +08:00
</>
);
}