2025-12-23 17:09:40 +03:00
|
|
|
import React, { useState, useEffect } from "react";
|
|
|
|
|
import { useTranslation } from "react-i18next";
|
|
|
|
|
import { Link } from "react-scroll";
|
|
|
|
|
import "../../index.css";
|
|
|
|
|
|
|
|
|
|
const Navbar = () => {
|
|
|
|
|
const { t, i18n } = useTranslation();
|
|
|
|
|
const [menuOpen, setMenuOpen] = useState(false);
|
|
|
|
|
const [activeSection, setActiveSection] = useState("home");
|
2026-01-09 00:07:39 +03:00
|
|
|
const [isDarkMode, setIsDarkMode] = useState(false);
|
2026-01-09 00:33:54 +03:00
|
|
|
const [scrolled, setScrolled] = useState(false);
|
2025-12-23 22:00:31 +03:00
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const savedTheme = localStorage.getItem("theme");
|
|
|
|
|
const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
|
|
|
const shouldEnableDarkMode = savedTheme === "dark" || (!savedTheme && prefersDark);
|
2026-01-09 00:07:39 +03:00
|
|
|
|
2025-12-23 22:00:31 +03:00
|
|
|
setIsDarkMode(shouldEnableDarkMode);
|
|
|
|
|
if (shouldEnableDarkMode) {
|
|
|
|
|
document.documentElement.classList.add("dark");
|
|
|
|
|
}
|
|
|
|
|
}, []);
|
2025-12-23 17:09:40 +03:00
|
|
|
|
2026-01-09 00:33:54 +03:00
|
|
|
// small scroll listener to add background/shadow after scrolling a bit
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const onScroll = () => {
|
|
|
|
|
setScrolled(window.scrollY > 10);
|
|
|
|
|
};
|
|
|
|
|
onScroll();
|
|
|
|
|
window.addEventListener("scroll", onScroll, { passive: true });
|
|
|
|
|
return () => window.removeEventListener("scroll", onScroll);
|
|
|
|
|
}, []);
|
|
|
|
|
|
2025-12-23 17:09:40 +03:00
|
|
|
const toggleMenu = () => {
|
|
|
|
|
setMenuOpen(!menuOpen);
|
|
|
|
|
};
|
|
|
|
|
|
2025-12-23 22:00:31 +03:00
|
|
|
const toggleDarkMode = () => {
|
|
|
|
|
const newDarkMode = !isDarkMode;
|
|
|
|
|
setIsDarkMode(newDarkMode);
|
2026-01-09 00:07:39 +03:00
|
|
|
|
2025-12-23 22:00:31 +03:00
|
|
|
if (newDarkMode) {
|
|
|
|
|
document.documentElement.classList.add("dark");
|
|
|
|
|
localStorage.setItem("theme", "dark");
|
|
|
|
|
} else {
|
|
|
|
|
document.documentElement.classList.remove("dark");
|
|
|
|
|
localStorage.setItem("theme", "light");
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2025-12-23 17:09:40 +03:00
|
|
|
useEffect(() => {
|
|
|
|
|
const handleScroll = () => {
|
2026-01-09 00:07:39 +03:00
|
|
|
const sections = ["home", "services", "about", "contact", "sections"];
|
|
|
|
|
const scrollPosition = window.scrollY + 120; // offset to detect current section
|
2025-12-23 17:09:40 +03:00
|
|
|
|
|
|
|
|
for (const section of sections) {
|
|
|
|
|
const element =
|
2026-01-09 00:07:39 +03:00
|
|
|
document.getElementById(section) || document.querySelector(`[name="${section}"]`);
|
2025-12-23 17:09:40 +03:00
|
|
|
if (element) {
|
|
|
|
|
const offsetTop = element.offsetTop;
|
|
|
|
|
const offsetHeight = element.offsetHeight;
|
|
|
|
|
|
2026-01-09 00:07:39 +03:00
|
|
|
if (scrollPosition >= offsetTop && scrollPosition < offsetTop + offsetHeight) {
|
2025-12-23 17:09:40 +03:00
|
|
|
setActiveSection(section);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
window.addEventListener("scroll", handleScroll);
|
|
|
|
|
return () => window.removeEventListener("scroll", handleScroll);
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (i18n.language === "ar") {
|
|
|
|
|
document.documentElement.dir = "rtl";
|
|
|
|
|
document.documentElement.lang = "ar";
|
|
|
|
|
} else {
|
|
|
|
|
document.documentElement.dir = "ltr";
|
|
|
|
|
document.documentElement.lang = i18n.language;
|
|
|
|
|
}
|
|
|
|
|
}, [i18n.language]);
|
|
|
|
|
|
|
|
|
|
const navItems = [
|
2026-01-09 00:07:39 +03:00
|
|
|
{ key: "home", label: t("nav.home", "الرئيسية") },
|
|
|
|
|
{ key: "services", label: t("nav.services", "الخدمات") },
|
|
|
|
|
{ key: "about", label: t("nav.about", "من نحن") },
|
|
|
|
|
{ key: "sections", label: t("nav.sections", "الأقسام") },
|
|
|
|
|
{ key: "contact", label: t("nav.contact", "تواصل معنا") },
|
2025-12-23 17:09:40 +03:00
|
|
|
];
|
|
|
|
|
|
2026-01-09 00:07:39 +03:00
|
|
|
const toggleLang = () => {
|
|
|
|
|
const newLang = i18n.language === "ar" ? "en" : "ar";
|
|
|
|
|
i18n.changeLanguage(newLang);
|
|
|
|
|
};
|
|
|
|
|
|
2025-12-23 17:09:40 +03:00
|
|
|
return (
|
2026-01-09 00:33:54 +03:00
|
|
|
<nav
|
|
|
|
|
className={`fixed top-0 left-0 right-0 w-full z-50 pointer-events-auto transition-all duration-300 ${
|
|
|
|
|
scrolled ? "backdrop-blur-sm bg-white/70 dark:bg-gray-900/70 shadow-md" : "bg-transparent"
|
|
|
|
|
}`}
|
|
|
|
|
aria-label="Primary"
|
|
|
|
|
>
|
|
|
|
|
{/* container with comfortable vertical padding to avoid overlapping page content */}
|
|
|
|
|
<div className="max-w-screen-xl mx-auto px-4 py-3">
|
|
|
|
|
{/* grid: left spacer | center nav | right controls */}
|
2026-01-09 00:07:39 +03:00
|
|
|
<div className="grid grid-cols-3 items-center">
|
2026-01-09 00:33:54 +03:00
|
|
|
{/* left spacer */}
|
2026-01-09 00:07:39 +03:00
|
|
|
<div className="flex items-center">
|
|
|
|
|
<div style={{ width: 44 }} />
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-01-09 00:33:54 +03:00
|
|
|
{/* center: stretch nav */}
|
2026-01-09 00:07:39 +03:00
|
|
|
<div className="flex justify-center">
|
2026-01-09 00:33:54 +03:00
|
|
|
<div className="w-full">
|
|
|
|
|
<div className="hidden md:flex items-center w-full">
|
|
|
|
|
<ul className="flex items-center justify-between w-full px-3">
|
2026-01-09 00:07:39 +03:00
|
|
|
{navItems.map((item) => (
|
2026-01-09 00:33:54 +03:00
|
|
|
<li key={item.key} className="flex-1">
|
2026-01-09 00:07:39 +03:00
|
|
|
<Link
|
|
|
|
|
to={item.key}
|
|
|
|
|
smooth
|
|
|
|
|
duration={500}
|
|
|
|
|
spy={true}
|
2026-01-09 00:33:54 +03:00
|
|
|
offset={-80}
|
2026-01-09 00:07:39 +03:00
|
|
|
onSetActive={() => setActiveSection(item.key)}
|
|
|
|
|
onClick={() => {
|
|
|
|
|
setActiveSection(item.key);
|
|
|
|
|
setMenuOpen(false);
|
|
|
|
|
}}
|
2026-01-09 00:33:54 +03:00
|
|
|
className={`nav-item relative inline-flex w-full justify-center text-base md:text-lg font-semibold py-3 rounded-full transition-all duration-300 focus:outline-none select-none
|
2026-01-09 00:07:39 +03:00
|
|
|
${
|
|
|
|
|
activeSection === item.key
|
|
|
|
|
? "text-yellow-500"
|
|
|
|
|
: "text-gray-800 dark:text-white"
|
|
|
|
|
}
|
|
|
|
|
`}
|
|
|
|
|
role="link"
|
|
|
|
|
aria-current={activeSection === item.key ? "page" : undefined}
|
|
|
|
|
>
|
|
|
|
|
<span className="nav-item-glow" aria-hidden="true" />
|
|
|
|
|
<span className="relative z-10">{item.label}</span>
|
|
|
|
|
</Link>
|
|
|
|
|
</li>
|
|
|
|
|
))}
|
|
|
|
|
</ul>
|
|
|
|
|
</div>
|
2026-01-09 00:33:54 +03:00
|
|
|
|
|
|
|
|
{/* mobile compact center (if needed) */}
|
|
|
|
|
<div className="md:hidden flex justify-center">
|
|
|
|
|
<div className="flex items-center gap-3 rtl:gap-x-reverse">
|
|
|
|
|
{navItems.slice(0, 3).map((item) => (
|
|
|
|
|
<Link
|
|
|
|
|
key={item.key}
|
|
|
|
|
to={item.key}
|
|
|
|
|
smooth
|
|
|
|
|
duration={500}
|
|
|
|
|
spy={true}
|
|
|
|
|
offset={-70}
|
|
|
|
|
onSetActive={() => setActiveSection(item.key)}
|
|
|
|
|
onClick={() => {
|
|
|
|
|
setActiveSection(item.key);
|
|
|
|
|
setMenuOpen(false);
|
|
|
|
|
}}
|
|
|
|
|
className={`nav-item relative inline-flex items-center text-sm font-semibold px-4 py-2 rounded-full transition-all duration-300
|
|
|
|
|
${
|
|
|
|
|
activeSection === item.key
|
|
|
|
|
? "text-yellow-500"
|
|
|
|
|
: "text-gray-800 dark:text-white"
|
|
|
|
|
}
|
|
|
|
|
`}
|
|
|
|
|
>
|
|
|
|
|
<span className="nav-item-glow" aria-hidden="true" />
|
|
|
|
|
<span className="relative z-10">{item.label}</span>
|
|
|
|
|
</Link>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2026-01-09 00:07:39 +03:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-01-09 00:33:54 +03:00
|
|
|
{/* right: controls */}
|
2026-01-09 00:07:39 +03:00
|
|
|
<div className="flex justify-end items-center space-x-2 rtl:space-x-reverse">
|
2026-01-09 00:33:54 +03:00
|
|
|
{/* Dark mode - refined icon with subtle motion */}
|
2025-12-23 22:00:31 +03:00
|
|
|
<button
|
2026-01-09 00:07:39 +03:00
|
|
|
onClick={toggleDarkMode}
|
|
|
|
|
type="button"
|
2026-01-09 00:33:54 +03:00
|
|
|
className="group inline-flex items-center p-2 w-12 h-12 justify-center rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 transition-all duration-300 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2"
|
|
|
|
|
aria-pressed={isDarkMode}
|
2026-01-09 00:07:39 +03:00
|
|
|
aria-label={isDarkMode ? "Light Mode" : "Dark Mode"}
|
|
|
|
|
title={isDarkMode ? "Light Mode" : "Dark Mode"}
|
|
|
|
|
>
|
2026-01-09 00:33:54 +03:00
|
|
|
<svg
|
|
|
|
|
className="w-6 h-6 transform transition-transform duration-350"
|
|
|
|
|
viewBox="0 0 24 24"
|
|
|
|
|
fill="none"
|
|
|
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
|
|
|
aria-hidden="true"
|
|
|
|
|
>
|
|
|
|
|
{/* Moon shape (when light) */}
|
|
|
|
|
<path
|
|
|
|
|
d="M21 12.79A9 9 0 1111.21 3a7 7 0 109.79 9.79z"
|
|
|
|
|
fill="currentColor"
|
|
|
|
|
style={{ opacity: isDarkMode ? 0 : 1, transition: "opacity 220ms" }}
|
|
|
|
|
/>
|
|
|
|
|
{/* Sun: center circle + rays (when dark) */}
|
|
|
|
|
<g style={{ opacity: isDarkMode ? 1 : 0, transition: "opacity 220ms" }} fill="currentColor">
|
|
|
|
|
<circle cx="12" cy="12" r="3" />
|
|
|
|
|
<path d="M12 2v1M12 21v1M4.2 4.2l.7.7M19.1 19.1l.7.7M1 12h1M22 12h1M4.2 19.8l.7-.7M19.1 4.9l.7-.7" stroke="currentColor" strokeWidth="1.1" strokeLinecap="round" strokeLinejoin="round" />
|
|
|
|
|
</g>
|
|
|
|
|
</svg>
|
2026-01-09 00:07:39 +03:00
|
|
|
</button>
|
|
|
|
|
|
2026-01-09 00:33:54 +03:00
|
|
|
{/* Language toggle - professional globe + pill */}
|
2026-01-09 00:07:39 +03:00
|
|
|
<button
|
|
|
|
|
onClick={toggleLang}
|
|
|
|
|
type="button"
|
2026-01-09 00:33:54 +03:00
|
|
|
className="inline-flex items-center px-3 py-2 rounded-lg border border-transparent hover:border-gray-200 dark:hover:border-gray-700 transition-all duration-300 text-sm font-medium focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2"
|
2026-01-09 00:07:39 +03:00
|
|
|
aria-label="Toggle language"
|
|
|
|
|
title={i18n.language === "ar" ? "العربية" : "English"}
|
|
|
|
|
>
|
2026-01-09 00:33:54 +03:00
|
|
|
<svg className="w-5 h-5 mr-2" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
|
|
|
|
<circle cx="12" cy="12" r="9" stroke="currentColor" strokeWidth="1.5" />
|
|
|
|
|
<path d="M2 12h20M12 2v20" stroke="currentColor" strokeWidth="1" strokeLinecap="round" strokeLinejoin="round" />
|
|
|
|
|
</svg>
|
|
|
|
|
|
|
|
|
|
<span className="inline-flex items-center justify-center w-9 h-6 text-xs font-semibold rounded-full bg-gray-100 dark:bg-gray-800 text-gray-800 dark:text-white">
|
|
|
|
|
{i18n.language === "ar" ? "ع" : "EN"}
|
2026-01-09 00:07:39 +03:00
|
|
|
</span>
|
|
|
|
|
</button>
|
|
|
|
|
|
|
|
|
|
{/* mobile menu toggle */}
|
|
|
|
|
<button
|
|
|
|
|
onClick={toggleMenu}
|
|
|
|
|
type="button"
|
2026-01-09 00:33:54 +03:00
|
|
|
className="inline-flex items-center p-2 w-11 h-11 justify-center text-sm text-gray-500 rounded-lg md:hidden hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-700 transition-all duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2"
|
2026-01-09 00:07:39 +03:00
|
|
|
aria-controls="navbar-menu"
|
|
|
|
|
aria-expanded={menuOpen}
|
|
|
|
|
>
|
|
|
|
|
<span className="sr-only">Open main menu</span>
|
2026-01-09 00:33:54 +03:00
|
|
|
<svg className="w-6 h-6" viewBox="0 0 17 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
2026-01-09 00:07:39 +03:00
|
|
|
<path d="M1 1h15M1 7h15M1 13h15" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
|
2025-12-23 22:00:31 +03:00
|
|
|
</svg>
|
2026-01-09 00:07:39 +03:00
|
|
|
</button>
|
|
|
|
|
</div>
|
2025-12-23 17:09:40 +03:00
|
|
|
</div>
|
|
|
|
|
|
2026-01-09 00:33:54 +03:00
|
|
|
{/* Mobile menu */}
|
|
|
|
|
<div className={`${menuOpen ? "block" : "hidden"} md:hidden mt-3 bg-white/95 dark:bg-gray-900/85 backdrop-blur-sm rounded-lg p-3`}>
|
2026-01-09 00:07:39 +03:00
|
|
|
<ul className="flex flex-col items-center space-y-3">
|
2025-12-23 17:09:40 +03:00
|
|
|
{navItems.map((item) => (
|
2026-01-09 00:07:39 +03:00
|
|
|
<li key={item.key} className="w-full">
|
2025-12-23 17:09:40 +03:00
|
|
|
<Link
|
|
|
|
|
to={item.key}
|
|
|
|
|
smooth
|
|
|
|
|
duration={500}
|
|
|
|
|
spy={true}
|
2026-01-09 00:33:54 +03:00
|
|
|
offset={-80}
|
2025-12-23 17:09:40 +03:00
|
|
|
onSetActive={() => setActiveSection(item.key)}
|
2026-01-09 00:07:39 +03:00
|
|
|
onClick={() => {
|
|
|
|
|
setActiveSection(item.key);
|
|
|
|
|
setMenuOpen(false);
|
|
|
|
|
}}
|
|
|
|
|
className={`block w-full text-center px-6 py-3 rounded-lg font-semibold transition-all duration-250
|
|
|
|
|
${
|
|
|
|
|
activeSection === item.key
|
|
|
|
|
? "text-yellow-500 ring-4 ring-yellow-300/30 shadow-lg"
|
|
|
|
|
: "text-gray-800 dark:text-white hover:text-yellow-500"
|
|
|
|
|
}
|
|
|
|
|
`}
|
2025-12-23 17:09:40 +03:00
|
|
|
>
|
|
|
|
|
{item.label}
|
|
|
|
|
</Link>
|
|
|
|
|
</li>
|
|
|
|
|
))}
|
|
|
|
|
</ul>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2026-01-09 00:07:39 +03:00
|
|
|
|
2026-01-09 00:33:54 +03:00
|
|
|
{/* inline CSS for polish */}
|
2026-01-09 00:07:39 +03:00
|
|
|
<style>{`
|
2026-01-09 00:33:54 +03:00
|
|
|
/* core nav item polish */
|
|
|
|
|
.nav-item { transform-style: preserve-3d; transition: transform 0.28s, box-shadow 0.28s, color 0.2s; position: relative; display: inline-flex; }
|
|
|
|
|
.nav-item:hover { transform: translateY(-6px) scale(1.03); box-shadow: 0 18px 30px rgba(2,6,23,0.10); }
|
|
|
|
|
.nav-item:active { transform: translateY(-3px) scale(1.02); }
|
|
|
|
|
.nav-item-glow { position: absolute; inset: -6px; border-radius: 9999px; filter: blur(12px); opacity:0; transition: opacity 0.28s, transform 0.28s; pointer-events:none; z-index:0; background: radial-gradient(circle at 50% 40%, rgba(250,204,21,0.12), transparent 30%); transform: scale(0.98); }
|
|
|
|
|
.nav-item:hover .nav-item-glow { opacity:0.9; transform: scale(1.02); }
|
2026-01-09 00:07:39 +03:00
|
|
|
.nav-item[aria-current="page"]::after { content: ""; position: absolute; height: 3px; left: 18%; right: 18%; bottom: -6px; border-radius: 9999px; background: linear-gradient(90deg, rgba(250,204,21,0.95), rgba(250,200,40,0.9)); opacity: 1; }
|
|
|
|
|
.nav-item span { white-space: nowrap; }
|
|
|
|
|
|
2026-01-09 00:33:54 +03:00
|
|
|
/* focus for accessibility */
|
2026-01-09 00:07:39 +03:00
|
|
|
.nav-item:focus-visible { outline: 3px solid rgba(99,102,241,0.12); outline-offset: 4px; }
|
|
|
|
|
|
2026-01-09 00:33:54 +03:00
|
|
|
/* reduce nav height overlap: ensure page content doesn't hide under nav (optional note) */
|
|
|
|
|
/* If you want, add: body { padding-top: 68px; } in global css so page content starts below navbar. */
|
2026-01-09 00:07:39 +03:00
|
|
|
`}</style>
|
2025-12-23 17:09:40 +03:00
|
|
|
</nav>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export default Navbar;
|