پروژه کامل: فرانت متصل به API + داکرایز
- اتصال کامل فرانت به API: همه محتوای سایت (پروژهها، مقالات، خدمات، برند و...) از بکاند خوانده میشود (SiteProvider + useSite) - فرم تماس و خبرنامه متصل به بکاند و تستشده در مرورگر - ساختار مونوریپو: frontend/ + backend/ - Dockerfile برای هر دو بخش + docker-compose (PostgreSQL + FastAPI + nginx) - nginx با پروکسی /api — بدون نیاز به CORS در حالت داکر - README کامل فارسی
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Link, NavLink, useLocation } from "react-router-dom";
|
||||
import { MenuIcon, PhoneIcon, XIcon } from "lucide-react";
|
||||
import { NAV } from "../data/site";
|
||||
import { useSite } from "../api";
|
||||
import { use } from "framer-motion/m";
|
||||
|
||||
export function Header() {
|
||||
const { brand } = useSite();
|
||||
const [scrolled, setScrolled] = useState(false);
|
||||
const [open, setOpen] = useState(false);
|
||||
const { pathname } = useLocation();
|
||||
|
||||
useEffect(() => {
|
||||
const onScroll = () => setScrolled(window.scrollY > 24);
|
||||
onScroll();
|
||||
window.addEventListener("scroll", onScroll, { passive: true });
|
||||
return () => window.removeEventListener("scroll", onScroll);
|
||||
});
|
||||
useEffect(() => {
|
||||
setOpen(false);
|
||||
}, [pathname]);
|
||||
|
||||
return (
|
||||
<header
|
||||
className={`fixed inset-x-0 top-0 z-50 transition-[background-color,border-color,backdrop-filter] duration-300 ease-lux ${
|
||||
scrolled
|
||||
? "border-b border-ivory/10 bg-ink/90 backdrop-blur-md"
|
||||
: "border-b border-transparent bg-transparent"
|
||||
}`}
|
||||
>
|
||||
<div className="mx-auto flex max-w-site items-center justify-between gap-8 px-6 py-4 lg:px-10">
|
||||
<Link
|
||||
to="/"
|
||||
className="flex items-center gap-3"
|
||||
aria-label={brand.name}
|
||||
>
|
||||
<img
|
||||
src={brand.logo}
|
||||
alt={brand.name}
|
||||
className={`w-auto object-contain transition-[height] duration-300 ease-lux ${
|
||||
scrolled ? "h-14" : "h-20"
|
||||
}`}
|
||||
/>
|
||||
</Link>
|
||||
|
||||
<nav aria-label="ناوبری اصلی" className="hidden xl:block">
|
||||
<ul className="flex items-center gap-7">
|
||||
{NAV.map((item) => (
|
||||
<li key={item.to}>
|
||||
<NavLink
|
||||
to={item.to}
|
||||
className={({ isActive }) =>
|
||||
`group relative py-2 text-[0.95rem] font-light transition-colors duration-200 ease-lux hover:text-copper [text-shadow:_0_2px_4px_rgba(0,0,0,0.6)] ${
|
||||
isActive ? "text-copper" : "text-ivory/80"
|
||||
}`
|
||||
}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<>
|
||||
{item.label}
|
||||
<span
|
||||
className={`absolute -bottom-0.5 left-0 right-0 h-px w-full origin-center bg-copper transition-transform duration-300 ease-lux ${
|
||||
isActive
|
||||
? "scale-x-100"
|
||||
: "scale-x-0 group-hover:scale-x-100"
|
||||
}`}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</NavLink>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<div className="flex items-center gap-3">
|
||||
<a
|
||||
href={brand.phone_href}
|
||||
className="group relative hidden items-center gap-2 overflow-hidden border border-copper/50 px-5 py-2.5 text-sm font-medium text-copper [text-shadow:_0_1px_2px_rgba(0,0,0,0.5)] lg:flex"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="absolute inset-0 translate-y-full bg-copper transition-transform duration-300 ease-lux group-hover:translate-y-0"
|
||||
/>
|
||||
<span className="relative z-10 flex items-center gap-2 transition-colors duration-300 ease-lux group-hover:text-char">
|
||||
<PhoneIcon className="h-4 w-4" />
|
||||
<span dir="ltr">{brand.phone}</span>
|
||||
</span>
|
||||
</a>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setOpen((v) => !v)}
|
||||
className="flex h-11 w-11 items-center justify-center border border-ivory/20 text-ivory transition-colors duration-200 ease-lux hover:border-copper hover:text-copper xl:hidden"
|
||||
aria-label={open ? "بستن منو" : "باز کردن منو"}
|
||||
aria-expanded={open}
|
||||
>
|
||||
{open ? (
|
||||
<XIcon className="h-5 w-5" />
|
||||
) : (
|
||||
<MenuIcon className="h-5 w-5" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{open && (
|
||||
<nav
|
||||
aria-label="ناوبری موبایل"
|
||||
className="border-t border-ivory/10 bg-ink/98 px-6 py-4 xl:hidden"
|
||||
>
|
||||
<ul className="grid gap-1">
|
||||
{NAV.map((item) => (
|
||||
<li key={item.to}>
|
||||
<NavLink
|
||||
to={item.to}
|
||||
className={({ isActive }) =>
|
||||
`block border-b border-ivory/5 py-3 text-base font-light transition-colors duration-200 ease-lux ${
|
||||
isActive ? "text-copper" : "text-ivory/80"
|
||||
}`
|
||||
}
|
||||
>
|
||||
{item.label}
|
||||
</NavLink>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</nav>
|
||||
)}
|
||||
</header>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user