parent
56af4599a2
commit
f64e9d2e4c
22 changed files with 757 additions and 366 deletions
117
src/components/Header.astro
Normal file
117
src/components/Header.astro
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
---
|
||||
const navigationItems = [
|
||||
{ label: "Home", href: "#home", nodeId: "43:29" },
|
||||
{ label: "About", href: "#about", nodeId: "43:30" },
|
||||
{ label: "Projects", href: "#projects", nodeId: "43:32" },
|
||||
{ label: "Timeline", href: "#timeline", nodeId: "43:36" },
|
||||
] as const;
|
||||
|
||||
const desktopLinkClass =
|
||||
"text-foreground no-underline transition-opacity duration-150 hover:opacity-55 focus-visible:opacity-55";
|
||||
const mobileLinkClass =
|
||||
"block rounded-xl px-3 py-2 text-sm font-semibold text-foreground no-underline transition-colors hover:bg-foreground/5";
|
||||
---
|
||||
|
||||
<header
|
||||
data-node-id="43:26"
|
||||
class="fixed inset-x-0 top-0 z-50 flex h-[64px] items-center justify-between overflow-clip border-b border-foreground/[0.06] bg-background/90 px-[clamp(26px,2.5vw,48px)] backdrop-blur-xl transition-colors duration-300 max-md:overflow-visible max-md:px-6"
|
||||
>
|
||||
<a
|
||||
class="shrink-0 text-2xl font-bold leading-[normal] text-foreground no-underline max-md:text-xl"
|
||||
href="#home"
|
||||
data-node-id="43:28">@imnya.ng</a
|
||||
>
|
||||
|
||||
<div id="navigation" class="relative flex shrink-0 items-center">
|
||||
<nav
|
||||
class="flex shrink-0 items-center gap-4 whitespace-nowrap text-base font-semibold leading-[1.5] tracking-[-0.02em] max-md:hidden"
|
||||
aria-label="주요 메뉴"
|
||||
data-node-id="43:40"
|
||||
>
|
||||
{
|
||||
navigationItems.map((item) => (
|
||||
<a
|
||||
class={desktopLinkClass}
|
||||
href={item.href}
|
||||
data-node-id={item.nodeId}
|
||||
>
|
||||
{item.label}
|
||||
</a>
|
||||
))
|
||||
}
|
||||
</nav>
|
||||
|
||||
<button
|
||||
id="menu-toggle"
|
||||
type="button"
|
||||
class="relative hidden h-10 w-10 items-center justify-center rounded-full text-foreground transition-colors hover:bg-foreground/5 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-foreground max-md:flex"
|
||||
aria-label="메뉴 열기"
|
||||
aria-controls="mobile-nav"
|
||||
aria-expanded="false"
|
||||
>
|
||||
<span class="sr-only">메뉴</span>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="absolute h-px w-5 -translate-y-1.5 bg-foreground"
|
||||
></span>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="absolute h-px w-5 bg-foreground"></span>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="absolute h-px w-5 translate-y-1.5 bg-foreground"
|
||||
></span>
|
||||
</button>
|
||||
|
||||
<nav
|
||||
id="mobile-nav"
|
||||
class="absolute right-0 top-[calc(100%+12px)] z-50 hidden min-w-40 flex-col gap-1 rounded-2xl border border-border bg-background p-2 shadow-[0_12px_30px_rgba(0,0,0,0.18)] md:hidden"
|
||||
aria-label="모바일 메뉴"
|
||||
>
|
||||
{
|
||||
navigationItems.map((item) => (
|
||||
<a class={mobileLinkClass} href={item.href}>
|
||||
{item.label}
|
||||
</a>
|
||||
))
|
||||
}
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<script>
|
||||
const menuToggle =
|
||||
document.querySelector<HTMLButtonElement>("#menu-toggle");
|
||||
const mobileNav = document.querySelector<HTMLElement>("#mobile-nav");
|
||||
const navigation = document.querySelector<HTMLElement>("#navigation");
|
||||
|
||||
if (menuToggle && mobileNav && navigation) {
|
||||
const setMenuOpen = (isOpen: boolean) => {
|
||||
mobileNav.classList.toggle("hidden", !isOpen);
|
||||
mobileNav.classList.toggle("flex", isOpen);
|
||||
menuToggle.setAttribute("aria-expanded", String(isOpen));
|
||||
menuToggle.setAttribute(
|
||||
"aria-label",
|
||||
isOpen ? "메뉴 닫기" : "메뉴 열기",
|
||||
);
|
||||
};
|
||||
|
||||
menuToggle.addEventListener("click", () => {
|
||||
setMenuOpen(menuToggle.getAttribute("aria-expanded") !== "true");
|
||||
});
|
||||
|
||||
mobileNav.addEventListener("click", (event) => {
|
||||
if (event.target instanceof HTMLAnchorElement) setMenuOpen(false);
|
||||
});
|
||||
|
||||
document.addEventListener("keydown", (event) => {
|
||||
if (event.key === "Escape") setMenuOpen(false);
|
||||
});
|
||||
|
||||
document.addEventListener("click", (event) => {
|
||||
if (event.target instanceof Node && !navigation.contains(event.target)) {
|
||||
setMenuOpen(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
Loading…
Reference in a new issue