This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
"use client";
|
||||
|
||||
import { Menu, X } from "lucide-react";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { useLocale, useTranslations } from "next-intl";
|
||||
import { useState } from "react";
|
||||
|
||||
import { Container } from "@/components/layout/container";
|
||||
import { LocaleToggle } from "@/components/layout/locale-toggle";
|
||||
import { ThemeToggle } from "@/components/theme-toggle";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { getLocalizedPath } from "@/lib/locale";
|
||||
|
||||
const navItems = [
|
||||
{ key: "home", path: "" },
|
||||
{ key: "portfolio", path: "/portfolio" },
|
||||
{ key: "products", path: "/products" },
|
||||
{ key: "about", path: "/about" },
|
||||
{ key: "contact", path: "/contact" },
|
||||
];
|
||||
|
||||
type SiteHeaderProps = {
|
||||
isAdmin?: boolean;
|
||||
};
|
||||
|
||||
function NavLinks({
|
||||
isAdmin,
|
||||
onNavigate,
|
||||
}: {
|
||||
isAdmin: boolean;
|
||||
onNavigate?: () => void;
|
||||
}) {
|
||||
const locale = useLocale();
|
||||
const t = useTranslations("navigation");
|
||||
|
||||
return (
|
||||
<>
|
||||
{navItems.map((item) => (
|
||||
<Link
|
||||
key={item.key}
|
||||
href={getLocalizedPath(locale, item.path || "/")}
|
||||
className="text-sm text-muted-foreground hover:text-foreground"
|
||||
onClick={onNavigate}
|
||||
>
|
||||
{t(item.key)}
|
||||
</Link>
|
||||
))}
|
||||
{isAdmin ? (
|
||||
<a
|
||||
href="/root"
|
||||
className="text-sm text-muted-foreground hover:text-foreground"
|
||||
onClick={onNavigate}
|
||||
>
|
||||
{t("root")}
|
||||
</a>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function SiteHeader({ isAdmin = false }: SiteHeaderProps) {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const locale = useLocale();
|
||||
const t = useTranslations("navigation");
|
||||
|
||||
return (
|
||||
<header className="sticky top-0 z-40 border-b border-border/80 bg-background/88 backdrop-blur-chrome">
|
||||
<Container>
|
||||
<div className="flex min-h-header items-center justify-between gap-4">
|
||||
<div className="flex items-center gap-4">
|
||||
<Link
|
||||
href={getLocalizedPath(locale)}
|
||||
className="inline-flex items-center rounded-nested border border-border bg-surface-1 px-3 py-2 shadow-xs"
|
||||
>
|
||||
<Image
|
||||
src="/logos/light-primary.svg"
|
||||
alt="mohfarawati"
|
||||
width={140}
|
||||
height={24}
|
||||
className="block h-6 w-auto dark:hidden"
|
||||
priority
|
||||
/>
|
||||
<Image
|
||||
src="/logos/dark-primary.svg"
|
||||
alt="mohfarawati"
|
||||
width={140}
|
||||
height={24}
|
||||
className="hidden h-6 w-auto dark:block"
|
||||
priority
|
||||
/>
|
||||
</Link>
|
||||
|
||||
<nav className="hidden items-center gap-5 md:flex">
|
||||
<NavLinks isAdmin={isAdmin} />
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div className="hidden items-center gap-2 md:flex">
|
||||
<ThemeToggle ariaLabel={t("themeToggle")} />
|
||||
<LocaleToggle locale={locale} />
|
||||
</div>
|
||||
|
||||
<Button
|
||||
type="button"
|
||||
onClick={() => setIsOpen((open) => !open)}
|
||||
variant="outline"
|
||||
size="icon"
|
||||
className="md:hidden"
|
||||
aria-label={isOpen ? t("closeMenu") : t("openMenu")}
|
||||
>
|
||||
{isOpen ? <X className="h-4 w-4" /> : <Menu className="h-4 w-4" />}
|
||||
</Button>
|
||||
</div>
|
||||
</Container>
|
||||
|
||||
{isOpen ? (
|
||||
<div className="border-t border-border/80 bg-surface-1 md:hidden">
|
||||
<Container className="py-4">
|
||||
<nav className="flex flex-col gap-3">
|
||||
<NavLinks isAdmin={isAdmin} onNavigate={() => setIsOpen(false)} />
|
||||
</nav>
|
||||
<div className="mt-4 flex items-center gap-2">
|
||||
<ThemeToggle ariaLabel={t("themeToggle")} />
|
||||
<LocaleToggle locale={locale} />
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
) : null}
|
||||
</header>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user