Add About EHSAN dropdown nav + About page
Task #15: Make «عن إحسان» top-nav a real dropdown and build the About page. - Header.tsx: converted static «عن إحسان» span into a dropdown trigger replicating the existing services dropdown pattern (aboutOpen state, trigger/panel refs, outside-click + Escape handling). Desktop panel and mobile nav both list «من نحن» (/about) and «اللجان» (/about/committees). - New pages/about.tsx: green intro panel («نبذة عن إحسان») + tab strip (الرؤية والرسالة / الأهداف والركائز / المزايا والمجالات) for the Who-we-are view, and a committee card grid for the Committees view. Section chosen via /about/:section? route param; in-page toggle between the two. Reuses Reveal for scroll-in. - App.tsx: registered <Route path="/about/:section?" component={About} />. - translations.ts: added full bilingual `about` section (AR + EN). Fully bilingual via existing t.* system, RTL verified, no emojis. tsc passes; screenshots confirm both views render correctly.
This commit is contained in:
@@ -21,6 +21,8 @@ import {
|
||||
MessageSquare,
|
||||
TrendingUp,
|
||||
HeartHandshake,
|
||||
Info,
|
||||
Users,
|
||||
} from "lucide-react";
|
||||
import ehsanLogo from "../../assets/ehsan-logo.png";
|
||||
|
||||
@@ -30,8 +32,11 @@ export function Header() {
|
||||
const [location, setLocation] = useLocation();
|
||||
const [mobileOpen, setMobileOpen] = useState(false);
|
||||
const [servicesOpen, setServicesOpen] = useState(false);
|
||||
const [aboutOpen, setAboutOpen] = useState(false);
|
||||
const servicesTriggerRef = useRef<HTMLButtonElement>(null);
|
||||
const servicesPanelRef = useRef<HTMLDivElement>(null);
|
||||
const aboutTriggerRef = useRef<HTMLButtonElement>(null);
|
||||
const aboutPanelRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const toggleLanguage = () => setLanguage(language === "ar" ? "en" : "ar");
|
||||
|
||||
@@ -42,6 +47,11 @@ export function Header() {
|
||||
setLocation(path);
|
||||
};
|
||||
|
||||
const handleAboutClick = (path: string) => {
|
||||
setAboutOpen(false);
|
||||
setLocation(path);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!servicesOpen) return;
|
||||
const onPointerDown = (e: MouseEvent) => {
|
||||
@@ -65,6 +75,29 @@ export function Header() {
|
||||
};
|
||||
}, [servicesOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!aboutOpen) return;
|
||||
const onPointerDown = (e: MouseEvent) => {
|
||||
const target = e.target as Node;
|
||||
if (
|
||||
aboutPanelRef.current?.contains(target) ||
|
||||
aboutTriggerRef.current?.contains(target)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
setAboutOpen(false);
|
||||
};
|
||||
const onKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.key === "Escape") setAboutOpen(false);
|
||||
};
|
||||
document.addEventListener("mousedown", onPointerDown);
|
||||
document.addEventListener("keydown", onKeyDown);
|
||||
return () => {
|
||||
document.removeEventListener("mousedown", onPointerDown);
|
||||
document.removeEventListener("keydown", onKeyDown);
|
||||
};
|
||||
}, [aboutOpen]);
|
||||
|
||||
const services = [
|
||||
{ key: "ghiras", label: t.serviceItems.ghiras, icon: Sprout },
|
||||
{ key: "zakat", label: t.serviceItems.zakat, icon: HandCoins },
|
||||
@@ -133,9 +166,25 @@ export function Header() {
|
||||
/>
|
||||
</button>
|
||||
|
||||
<span className="px-3 py-2 text-sm font-medium text-foreground/70 cursor-default select-none">
|
||||
<button
|
||||
ref={aboutTriggerRef}
|
||||
type="button"
|
||||
onClick={() => setAboutOpen((v) => !v)}
|
||||
className={`px-3 py-2 text-sm font-medium inline-flex items-center gap-1 transition-colors ${
|
||||
aboutOpen || location.startsWith("/about")
|
||||
? "text-primary"
|
||||
: "text-foreground hover:text-primary"
|
||||
}`}
|
||||
data-testid="nav-about"
|
||||
aria-haspopup="menu"
|
||||
aria-controls="about-menu"
|
||||
aria-expanded={aboutOpen}
|
||||
>
|
||||
{t.nav.about}
|
||||
</span>
|
||||
<ChevronDown
|
||||
className={`w-3.5 h-3.5 transition-transform ${aboutOpen ? "rotate-180" : ""}`}
|
||||
/>
|
||||
</button>
|
||||
|
||||
<span className="px-3 py-2 text-sm font-medium text-foreground/70 cursor-default select-none">
|
||||
{t.nav.baraem}
|
||||
@@ -250,6 +299,41 @@ export function Header() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* About dropdown (desktop) */}
|
||||
{aboutOpen && (
|
||||
<div
|
||||
ref={aboutPanelRef}
|
||||
id="about-menu"
|
||||
role="menu"
|
||||
className="hidden lg:block absolute inset-x-0 top-full border-t bg-white shadow-lg"
|
||||
data-testid="about-menu"
|
||||
onMouseLeave={() => setAboutOpen(false)}
|
||||
>
|
||||
<div className="container mx-auto px-4 py-4">
|
||||
<div className="flex items-stretch gap-2 max-w-md">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleAboutClick("/about")}
|
||||
data-testid="nav-about-whoWeAre"
|
||||
className="flex-1 flex flex-col items-center justify-center gap-2 px-2 py-3 rounded-md text-center hover:bg-muted transition-colors"
|
||||
>
|
||||
<Info className="w-7 h-7 text-primary" />
|
||||
<span className="text-sm font-medium text-foreground">{t.about.whoWeAre}</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleAboutClick("/about/committees")}
|
||||
data-testid="nav-about-committees"
|
||||
className="flex-1 flex flex-col items-center justify-center gap-2 px-2 py-3 rounded-md text-center hover:bg-muted transition-colors border-s border-border"
|
||||
>
|
||||
<Users className="w-7 h-7 text-primary" />
|
||||
<span className="text-sm font-medium text-foreground">{t.about.committees}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Mobile nav panel */}
|
||||
{mobileOpen && (
|
||||
<div className="lg:hidden border-t bg-white px-4 py-3 space-y-1">
|
||||
@@ -299,6 +383,27 @@ export function Header() {
|
||||
>
|
||||
{t.nav.requestSupport}
|
||||
</Link>
|
||||
<div className="pt-1">
|
||||
<p className="px-3 py-1 text-xs font-semibold text-foreground/50">
|
||||
{t.nav.about}
|
||||
</p>
|
||||
<Link
|
||||
href="/about"
|
||||
onClick={() => setMobileOpen(false)}
|
||||
className="flex w-full items-center gap-2 text-start px-3 py-2 rounded-md text-sm font-medium text-foreground hover:bg-muted"
|
||||
>
|
||||
<Info className="w-4 h-4 text-primary" />
|
||||
<span>{t.about.whoWeAre}</span>
|
||||
</Link>
|
||||
<Link
|
||||
href="/about/committees"
|
||||
onClick={() => setMobileOpen(false)}
|
||||
className="flex w-full items-center gap-2 text-start px-3 py-2 rounded-md text-sm font-medium text-foreground hover:bg-muted"
|
||||
>
|
||||
<Users className="w-4 h-4 text-primary" />
|
||||
<span>{t.about.committees}</span>
|
||||
</Link>
|
||||
</div>
|
||||
<div className="pt-2 mt-2 border-t">
|
||||
{isAuthenticated ? (
|
||||
<>
|
||||
|
||||
Reference in New Issue
Block a user