Update Navbar header to match reference design with authentic owl logo, cyan links, dividers, search, and login button

This commit is contained in:
alireza 2026-08-24 13:21:00 +03:30
parent 41d4d2af80
commit 4a68f54c33
1 changed files with 344 additions and 163 deletions

View File

@ -1,23 +1,21 @@
import React, { useState, useEffect } from 'react';
import { Link, useLocation } from 'react-router-dom';
import React, { useState, useEffect, useRef } from 'react';
import { Link, useLocation, useNavigate } from 'react-router-dom';
import {
Sparkles,
Search,
Menu,
X,
Layers,
Cpu,
CreditCard,
Building2,
GraduationCap,
BookOpen,
Users,
Phone,
Download,
LogIn,
Bot,
MessageSquare
Cpu,
Layers,
Building2,
CreditCard,
Sparkles,
ArrowLeft,
GraduationCap
} from 'lucide-react';
import siteInfo from '../../data/siteInfo.json';
import postsData from '../../data/posts.json';
import toolsData from '../../data/tools.json';
import modelsData from '../../data/models.json';
interface NavbarProps {
onOpenAuth: () => void;
@ -25,12 +23,16 @@ interface NavbarProps {
export const Navbar: React.FC<NavbarProps> = ({ onOpenAuth }) => {
const [isOpen, setIsOpen] = useState(false);
const [isSearchOpen, setIsSearchOpen] = useState(false);
const [searchQuery, setSearchQuery] = useState('');
const [scrolled, setScrolled] = useState(false);
const location = useLocation();
const navigate = useNavigate();
const searchInputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
const handleScroll = () => {
if (window.scrollY > 20) {
if (window.scrollY > 15) {
setScrolled(true);
} else {
setScrolled(false);
@ -40,174 +42,353 @@ export const Navbar: React.FC<NavbarProps> = ({ onOpenAuth }) => {
return () => window.removeEventListener('scroll', handleScroll);
}, []);
// Close mobile menu on route change
// Close menus on route change
useEffect(() => {
setIsOpen(false);
setIsSearchOpen(false);
}, [location.pathname]);
// Focus search input when modal opens
useEffect(() => {
if (isSearchOpen) {
setTimeout(() => {
searchInputRef.current?.focus();
}, 100);
}
}, [isSearchOpen]);
// Handle escape key to close search modal
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
setIsSearchOpen(false);
}
};
window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}, []);
const navLinks = [
{ name: 'صفحه اصلی', path: '/' },
{ name: 'ابزارها', path: '/tools', icon: Layers },
{ name: 'مدل‌های زبانی', path: '/models', icon: Cpu },
{ name: 'تعرفه‌ها', path: '/pricing', icon: CreditCard },
{ name: 'راهکارهای سازمانی', path: '/enterprise', icon: Building2 },
{ name: 'آموزش هوش مصنوعی', path: '/academy', icon: GraduationCap },
{ name: 'رسانه و مقالات', path: '/blog', icon: BookOpen },
{ name: 'دستیار چت', path: '/chat', icon: MessageSquare, highlight: true },
{ name: 'درباره ما', path: '/about', icon: Users },
{ name: 'تماس با ما', path: '/contact', icon: Phone },
{ name: 'خانه', path: '/' },
{ name: 'رسانه', path: '/blog' },
{ name: 'مدل‌های زبانی', path: '/models' },
{ name: 'ابزارها', path: '/tools' },
{ name: 'راهکارهای سازمانی', path: '/enterprise' },
{ name: 'تعرفه', path: '/pricing' },
{ name: '+ هوشان', path: '/academy' },
];
return (
<header className={`fixed top-0 left-0 right-0 z-50 transition-all duration-300 ${
scrolled
? 'bg-slate-950/85 backdrop-blur-md border-b border-slate-800/80 shadow-lg shadow-black/20 py-3'
: 'bg-transparent py-5'
}`}>
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex items-center justify-between">
// Quick search filter
const filteredPosts = searchQuery.trim()
? postsData.filter(p => p.title.toLowerCase().includes(searchQuery.toLowerCase())).slice(0, 4)
: [];
{/* Right: Logo & Brand */}
<Link to="/" className="flex items-center gap-3 group">
<div className="relative flex items-center justify-center w-10 h-10 rounded-xl bg-gradient-to-tr from-indigo-600 via-indigo-500 to-cyan-400 p-[1.5px] shadow-lg shadow-indigo-500/25 group-hover:scale-105 transition-transform overflow-hidden">
<div className="w-full h-full bg-slate-950 rounded-[10px] flex items-center justify-center p-1">
const filteredTools = searchQuery.trim()
? toolsData.filter(t => t.title.toLowerCase().includes(searchQuery.toLowerCase()) || t.description.toLowerCase().includes(searchQuery.toLowerCase())).slice(0, 3)
: [];
const filteredModels = searchQuery.trim()
? modelsData.filter(m => m.name.toLowerCase().includes(searchQuery.toLowerCase()) || m.provider.toLowerCase().includes(searchQuery.toLowerCase())).slice(0, 3)
: [];
const hasSearchResults = filteredPosts.length > 0 || filteredTools.length > 0 || filteredModels.length > 0;
return (
<>
<header
className={`fixed top-0 left-0 right-0 z-50 bg-white transition-all duration-200 border-b border-gray-100 ${
scrolled ? 'shadow-md py-2.5 sm:py-3' : 'shadow-xs py-3 sm:py-3.5'
}`}
>
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex items-center justify-between">
{/* Right Side: Logo & Navigation Links */}
<div className="flex items-center gap-4 lg:gap-6">
{/* Authentic Houshan Owl Logo */}
<Link to="/" className="flex items-center flex-shrink-0 group">
<img
src="/storage/banner/owl_logo.png"
alt="Houshan AI Logo"
className="w-full h-full object-contain"
alt="هوشان"
className="h-10 sm:h-11 w-auto object-contain transition-transform group-hover:scale-105"
/>
</div>
<span className="absolute -top-1 -right-1 flex h-2.5 w-2.5">
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-cyan-400 opacity-75"></span>
<span className="relative inline-flex rounded-full h-2.5 w-2.5 bg-cyan-500"></span>
</span>
</Link>
{/* Desktop Navigation Links separated by | */}
<nav className="hidden lg:flex items-center">
{navLinks.map((link, index) => {
const isActive = location.pathname === link.path;
return (
<React.Fragment key={link.path}>
<Link
to={link.path}
className={`text-sm xl:text-[15px] font-bold transition-colors duration-150 px-2.5 py-1 ${
isActive
? 'text-sky-600 font-extrabold'
: 'text-[#38b6ff] hover:text-sky-600'
}`}
>
{link.name}
</Link>
{index < navLinks.length - 1 && (
<span className="text-gray-800/90 font-light text-sm select-none px-1">
|
</span>
)}
</React.Fragment>
);
})}
</nav>
</div>
<div className="flex flex-col">
<div className="flex items-center gap-1.5">
<span className="text-xl font-extrabold tracking-tight text-white font-vazir">
{siteInfo.name}
</span>
<span className="px-1.5 py-0.5 text-[10px] font-bold rounded bg-indigo-500/20 text-indigo-300 border border-indigo-500/30">
AI
</span>
</div>
<span className="text-[10px] text-slate-400 font-medium hidden sm:inline">
دستیار هوش مصنوعی مدیران
</span>
{/* Left Side: Search Button & Login/Register Button */}
<div className="flex items-center gap-3 sm:gap-4">
{/* Search Icon Button */}
<button
type="button"
onClick={() => setIsSearchOpen(true)}
className="w-9 h-9 sm:w-10 sm:h-10 rounded-lg bg-gray-100 hover:bg-gray-200/80 border border-gray-200/70 flex items-center justify-center text-[#38b6ff] hover:text-sky-600 transition-all focus:outline-none"
aria-label="جستجو در سایت"
title="جستجو"
>
<Search className="w-4 h-4 sm:w-5 sm:h-5 stroke-[2.4]" />
</button>
{/* Login / Register Button */}
<button
type="button"
onClick={onOpenAuth}
className="bg-[#54b4d3] hover:bg-[#44a5c4] active:scale-[0.98] text-white font-bold text-xs sm:text-sm px-4 sm:px-6 py-2 sm:py-2.5 rounded-xl shadow-xs transition-all duration-150 flex items-center justify-center whitespace-nowrap cursor-pointer"
>
ورود / ثبت نام
</button>
{/* Mobile Menu Toggle Button */}
<button
type="button"
onClick={() => setIsOpen(!isOpen)}
className="lg:hidden p-2 rounded-lg bg-gray-100 text-gray-700 hover:text-sky-600 border border-gray-200 focus:outline-none"
aria-label="منو"
>
{isOpen ? <X className="w-5 h-5" /> : <Menu className="w-5 h-5" />}
</button>
</div>
</Link>
{/* Center: Desktop Navigation Links */}
<nav className="hidden xl:flex items-center gap-1 bg-slate-900/60 p-1.5 rounded-full border border-slate-800/80 backdrop-blur-md">
{navLinks.slice(0, 8).map((link) => {
const isActive = location.pathname === link.path;
return (
<Link
key={link.path}
to={link.path}
className={`px-3 py-1.5 rounded-full text-xs font-medium transition-all duration-200 flex items-center gap-1.5 ${
link.highlight
? 'bg-gradient-to-r from-indigo-600 to-cyan-600 text-white shadow-sm shadow-indigo-500/20 hover:opacity-95'
: isActive
? 'bg-indigo-600/20 text-indigo-300 border border-indigo-500/30 shadow-inner'
: 'text-slate-300 hover:text-white hover:bg-slate-800/60'
}`}
>
{link.name}
</Link>
);
})}
</nav>
{/* Left: Actions & Auth Button */}
<div className="hidden sm:flex items-center gap-2.5">
<a
href={siteInfo.appDownloadUrl}
target="_blank"
rel="noopener noreferrer"
className="px-3.5 py-2 rounded-xl text-xs font-medium text-slate-300 hover:text-white bg-slate-900/80 hover:bg-slate-800 border border-slate-800 transition-all flex items-center gap-1.5"
>
<Download className="w-3.5 h-3.5 text-cyan-400" />
<span>دانلود اپلیکیشن</span>
</a>
<button
onClick={onOpenAuth}
className="relative group overflow-hidden px-4 py-2 rounded-xl text-xs font-semibold text-white bg-gradient-to-r from-indigo-600 to-indigo-500 hover:from-indigo-500 hover:to-cyan-500 shadow-md shadow-indigo-600/20 hover:shadow-indigo-600/30 transition-all flex items-center gap-1.5"
>
<LogIn className="w-3.5 h-3.5" />
<span>ورود / ثبتنام</span>
</button>
</div>
{/* Mobile Menu Toggle Button */}
<div className="flex xl:hidden items-center gap-2">
<button
onClick={onOpenAuth}
className="p-2 rounded-lg bg-indigo-600/20 text-indigo-300 border border-indigo-500/30 text-xs sm:hidden"
>
<LogIn className="w-4 h-4" />
</button>
<button
onClick={() => setIsOpen(!isOpen)}
className="p-2 rounded-xl bg-slate-900 border border-slate-800 text-slate-300 hover:text-white focus:outline-none"
aria-label="منو"
>
{isOpen ? <X className="w-5 h-5" /> : <Menu className="w-5 h-5" />}
</button>
</div>
</div>
</div>
{/* Mobile Drawer Navigation */}
{isOpen && (
<div className="xl:hidden bg-slate-950/95 border-b border-slate-800 backdrop-blur-xl px-4 pt-3 pb-6 space-y-2 animate-in slide-in-from-top duration-200">
<div className="grid grid-cols-2 gap-2 pt-2">
{navLinks.map((link) => {
const Icon = link.icon;
const isActive = location.pathname === link.path;
return (
<Link
key={link.path}
to={link.path}
className={`flex items-center gap-2 p-2.5 rounded-xl text-xs font-medium transition-colors ${
isActive
? 'bg-indigo-600/20 text-indigo-300 border border-indigo-500/30'
: 'text-slate-300 hover:bg-slate-900 hover:text-white border border-transparent'
}`}
>
{Icon && <Icon className="w-4 h-4 text-indigo-400" />}
<span>{link.name}</span>
</Link>
);
})}
{/* Mobile Navigation Drawer */}
{isOpen && (
<div className="lg:hidden bg-white border-t border-gray-100 px-4 pt-3 pb-6 space-y-2 shadow-lg animate-in slide-in-from-top-2 duration-150">
<div className="grid grid-cols-2 gap-2 py-2">
{navLinks.map((link) => {
const isActive = location.pathname === link.path;
return (
<Link
key={link.path}
to={link.path}
onClick={() => setIsOpen(false)}
className={`flex items-center justify-between p-2.5 rounded-xl text-sm font-bold transition-colors ${
isActive
? 'bg-sky-50 text-sky-600 border border-sky-200'
: 'text-[#38b6ff] hover:bg-gray-50 hover:text-sky-600'
}`}
>
<span>{link.name}</span>
</Link>
);
})}
</div>
<div className="pt-3 border-t border-gray-100 flex flex-col gap-2">
<button
onClick={() => {
setIsOpen(false);
setIsSearchOpen(true);
}}
className="w-full py-2.5 rounded-xl text-center text-sm font-medium text-gray-700 bg-gray-100 hover:bg-gray-200/80 border border-gray-200 flex items-center justify-center gap-2"
>
<Search className="w-4 h-4 text-[#38b6ff]" />
<span>جستجو در مقالات و ابزارها</span>
</button>
<button
onClick={() => {
setIsOpen(false);
onOpenAuth();
}}
className="w-full py-2.5 rounded-xl text-center text-sm font-bold text-white bg-[#54b4d3] hover:bg-[#44a5c4] shadow-xs flex items-center justify-center gap-2"
>
<span>ورود / ثبت نام در هوشان</span>
</button>
</div>
</div>
)}
</header>
<div className="pt-3 border-t border-slate-800/80 flex flex-col gap-2">
<a
href={siteInfo.appDownloadUrl}
target="_blank"
rel="noopener noreferrer"
className="w-full py-2.5 rounded-xl text-center text-xs font-medium text-slate-300 bg-slate-900 border border-slate-800 flex items-center justify-center gap-2"
>
<Download className="w-4 h-4 text-cyan-400" />
<span>دانلود مستقیم اپلیکیشن هوشان</span>
</a>
<button
onClick={() => {
setIsOpen(false);
onOpenAuth();
}}
className="w-full py-2.5 rounded-xl text-center text-xs font-semibold text-white bg-gradient-to-r from-indigo-600 to-cyan-600 shadow-md flex items-center justify-center gap-2"
>
<LogIn className="w-4 h-4" />
<span>ورود / ثبتنام در حساب کاربری</span>
</button>
{/* Global Interactive Search Modal */}
{isSearchOpen && (
<div className="fixed inset-0 z-50 flex items-start justify-center pt-20 sm:pt-28 px-4 bg-black/50 backdrop-blur-xs animate-in fade-in duration-150">
<div
className="w-full max-w-2xl bg-white rounded-2xl shadow-2xl border border-gray-200 overflow-hidden text-slate-800 animate-in zoom-in-95 duration-150"
onClick={(e) => e.stopPropagation()}
>
{/* Search Input Bar */}
<div className="relative flex items-center px-4 py-3.5 border-b border-gray-100 bg-gray-50/50">
<Search className="w-5 h-5 text-[#38b6ff] ml-3 flex-shrink-0" />
<input
ref={searchInputRef}
type="text"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
placeholder="جستجو در مقالات، ابزارها، مدل‌ها و دوره‌ها..."
className="w-full bg-transparent text-slate-800 placeholder-gray-400 text-sm sm:text-base focus:outline-none"
/>
{searchQuery && (
<button
type="button"
onClick={() => setSearchQuery('')}
className="p-1 rounded-md text-gray-400 hover:text-gray-600 mr-2"
>
<X className="w-4 h-4" />
</button>
)}
<button
type="button"
onClick={() => setIsSearchOpen(false)}
className="p-1.5 rounded-lg text-gray-400 hover:text-gray-700 hover:bg-gray-200/50 transition-colors"
title="بستن (Esc)"
>
<X className="w-5 h-5" />
</button>
</div>
{/* Live Search Results */}
<div className="max-h-[60vh] overflow-y-auto p-4 space-y-4">
{!searchQuery.trim() ? (
<div className="py-6 text-center text-gray-500 space-y-3">
<p className="text-sm">عبارت مورد نظر خود را برای جستجو وارد کنید</p>
<div className="flex flex-wrap items-center justify-center gap-2 pt-2">
<span className="text-xs text-gray-400">پیشنهادها:</span>
{['هوش مصنوعی', 'چت‌بات', 'Claude 3.5', 'ChatGPT', 'تولید تصویر', 'سئو'].map((tag) => (
<button
key={tag}
type="button"
onClick={() => setSearchQuery(tag)}
className="px-2.5 py-1 rounded-lg bg-gray-100 hover:bg-sky-50 hover:text-sky-600 text-xs text-gray-600 transition-colors"
>
{tag}
</button>
))}
</div>
</div>
) : !hasSearchResults ? (
<div className="py-8 text-center text-gray-500">
<p className="text-sm">نتیجهای برای «{searchQuery}» یافت نشد.</p>
</div>
) : (
<div className="space-y-4">
{/* Blog Articles */}
{filteredPosts.length > 0 && (
<div>
<h4 className="text-xs font-bold text-gray-400 mb-2 flex items-center gap-1.5">
<BookOpen className="w-3.5 h-3.5 text-[#38b6ff]" />
<span>مقالات و آموزشها</span>
</h4>
<div className="space-y-1.5">
{filteredPosts.map((post) => (
<Link
key={post.id}
to={`/blog/${post.slug}`}
onClick={() => setIsSearchOpen(false)}
className="flex items-center justify-between p-2.5 rounded-xl hover:bg-gray-50 border border-transparent hover:border-gray-200 transition-all text-right group"
>
<span className="text-xs sm:text-sm font-medium text-gray-800 group-hover:text-sky-600 line-clamp-1">
{post.title}
</span>
<ArrowLeft className="w-3.5 h-3.5 text-gray-400 group-hover:text-sky-600 -translate-x-1 group-hover:translate-x-0 transition-transform flex-shrink-0 mr-2" />
</Link>
))}
</div>
</div>
)}
{/* AI Tools */}
{filteredTools.length > 0 && (
<div>
<h4 className="text-xs font-bold text-gray-400 mb-2 flex items-center gap-1.5">
<Layers className="w-3.5 h-3.5 text-[#38b6ff]" />
<span>ابزارهای هوش مصنوعی</span>
</h4>
<div className="space-y-1.5">
{filteredTools.map((tool) => (
<Link
key={tool.id}
to="/tools"
onClick={() => setIsSearchOpen(false)}
className="flex items-center justify-between p-2.5 rounded-xl hover:bg-gray-50 border border-transparent hover:border-gray-200 transition-all text-right group"
>
<div>
<span className="text-xs sm:text-sm font-medium text-gray-800 group-hover:text-sky-600 block">
{tool.title}
</span>
<span className="text-[11px] text-gray-500 line-clamp-1">
{tool.description}
</span>
</div>
<ArrowLeft className="w-3.5 h-3.5 text-gray-400 group-hover:text-sky-600 flex-shrink-0 mr-2" />
</Link>
))}
</div>
</div>
)}
{/* AI Models */}
{filteredModels.length > 0 && (
<div>
<h4 className="text-xs font-bold text-gray-400 mb-2 flex items-center gap-1.5">
<Cpu className="w-3.5 h-3.5 text-[#38b6ff]" />
<span>مدلهای زبانی</span>
</h4>
<div className="space-y-1.5">
{filteredModels.map((model) => (
<Link
key={model.id}
to="/models"
onClick={() => setIsSearchOpen(false)}
className="flex items-center justify-between p-2.5 rounded-xl hover:bg-gray-50 border border-transparent hover:border-gray-200 transition-all text-right group"
>
<div>
<span className="text-xs sm:text-sm font-medium text-gray-800 group-hover:text-sky-600 block">
{model.name}
</span>
<span className="text-[11px] text-gray-500">
ارائه دهنده: {model.provider}
</span>
</div>
<ArrowLeft className="w-3.5 h-3.5 text-gray-400 group-hover:text-sky-600 flex-shrink-0 mr-2" />
</Link>
))}
</div>
</div>
)}
</div>
)}
</div>
{/* Modal Footer */}
<div className="px-4 py-2.5 bg-gray-50 border-t border-gray-100 flex items-center justify-between text-[11px] text-gray-500">
<span>هوشان پلتفرم جامع هوش مصنوعی</span>
<div className="flex items-center gap-1.5">
<kbd className="px-1.5 py-0.5 rounded bg-gray-200 text-gray-600 text-[10px] font-mono">ESC</kbd>
<span>جهت بستن</span>
</div>
</div>
</div>
</div>
)}
</header>
</>
);
};