Claude Code · claude-opus-5-5 · React (TSX)
드로어가 열려 있는 동안 document.body 의 overflow 를 잠시 hidden 으로 바꿉니다.
결과 코드펼치기
import { useEffect, useRef, useState } from "react";
type NavItem = { label: string; href: string; children?: { label: string; href: string }[] };
const NAV: NavItem[] = [
{
label: "학교소개",
href: "/about",
children: [
{ label: "인사말", href: "/about" },
{ label: "공간 둘러보기", href: "/space" },
],
},
{ label: "오시는길", href: "/location" },
{ label: "클래스", href: "/classes" },
{
label: "커뮤니티",
href: "/notice",
children: [
{ label: "공지사항", href: "/notice" },
{ label: "수강생 갤러리", href: "/gallery" },
{ label: "자료실", href: "/resources" },
],
},
{ label: "문의하기", href: "/contact" },
];
const TEL = "02-1234-5678";
const CHAT = "https://example.com/chat";
const FONT = '"Pretendard Variable", Pretendard, -apple-system, BlinkMacSystemFont, "Apple SD Gothic Neo", "Malgun Gothic", sans-serif';
function ExternalIcon() {
return (
<svg viewBox="0 0 24 24" className="ml-1 inline-block h-3 w-3 opacity-60" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
<path d="M7 7h10v10" />
<path d="M7 17 17 7" />
</svg>
);
}
export default function SiteHeader() {
const [open, setOpen] = useState(false);
const openBtn = useRef<HTMLButtonElement>(null);
const closeBtn = useRef<HTMLButtonElement>(null);
useEffect(() => {
if (!open) return;
const prev = document.body.style.overflow;
document.body.style.overflow = "hidden";
closeBtn.current?.focus();
const onKey = (e: KeyboardEvent) => {
if (e.key === "Escape") setOpen(false);
};
document.addEventListener("keydown", onKey);
return () => {
document.body.style.overflow = prev;
document.removeEventListener("keydown", onKey);
openBtn.current?.focus();
};
}, [open]);
return (
<header className="sticky top-0 z-50 w-full border-b border-[#e4e7ed] bg-white/95 backdrop-blur" style={{ fontFamily: FONT }}>
<div className="mx-auto flex h-16 w-full max-w-[1200px] items-center gap-10 px-4 sm:px-6 lg:h-20">
<a href="/" className="shrink-0">
<img src="https://nnuer.site/media/ZpeGHXZbZ4IJ" alt="루멘 사진학교" className="h-8 w-auto lg:h-10" />
</a>
<nav aria-label="주 메뉴" className="ml-auto hidden h-full lg:block">
<ul className="flex h-full">
{NAV.map((item) => (
<li key={item.label} className="group relative h-full">
<a
href={item.href}
className="relative flex h-full items-center px-[14px] text-[17px] font-semibold tracking-[-0.01em] text-[#172031] transition-colors hover:text-[#1B263B] xl:px-[22px]"
>
{item.label}
<span className="absolute inset-x-[14px] -bottom-px h-[3px] origin-center scale-x-0 rounded-t-[3px] bg-[#F5C842] transition-transform duration-200 group-hover:scale-x-100 group-focus-within:scale-x-100 xl:inset-x-[22px]" />
</a>
{item.children && (
<ul className="invisible absolute left-1/2 top-[calc(100%+8px)] min-w-[160px] -translate-x-1/2 rounded-[10px] bg-white p-2 opacity-0 shadow-[0_18px_40px_rgba(18,26,42,0.12)] transition-all duration-150 group-hover:visible group-hover:top-full group-hover:opacity-100 group-focus-within:visible group-focus-within:top-full group-focus-within:opacity-100">
{item.children.map((c) => (
<li key={c.label}>
<a href={c.href} className="block whitespace-nowrap rounded-lg px-[14px] py-[9px] text-[15px] text-[#4a5466] hover:bg-[#fff7dc] hover:text-[#1B263B]">
{c.label}
</a>
</li>
))}
</ul>
)}
</li>
))}
</ul>
</nav>
<a
href={`tel:${TEL}`}
className="hidden rounded-full bg-[#1B263B] px-[18px] py-[9px] text-[15px] font-bold tabular-nums text-white transition-colors hover:bg-[#3D64B1] lg:inline-flex"
>
{TEL}
</a>
<button
ref={openBtn}
type="button"
onClick={() => setOpen(true)}
aria-label="전체 메뉴 열기"
aria-expanded={open}
className="ml-auto flex h-11 w-11 flex-col items-center justify-center gap-[4px] rounded-xl bg-[#f4f6f9] text-[#172031] lg:hidden"
>
<span className="block h-[2px] w-5 rounded bg-current" />
<span className="block h-[2px] w-5 rounded bg-current" />
<span className="block h-[2px] w-5 rounded bg-current" />
</button>
</div>
{open && (
<div className="fixed inset-0 z-[200] lg:hidden">
<div className="absolute inset-0 bg-[rgba(18,26,42,0.55)]" onClick={() => setOpen(false)} />
<div role="dialog" aria-modal="true" aria-label="전체 메뉴" className="absolute inset-y-0 right-0 flex w-[min(86vw,340px)] flex-col overflow-y-auto bg-white">
<div className="flex h-16 items-center justify-between bg-[#1B263B] px-5 text-white">
<span className="font-bold">메뉴</span>
<button ref={closeBtn} type="button" onClick={() => setOpen(false)} aria-label="전체 메뉴 닫기" className="flex h-10 w-10 items-center justify-center">
<svg viewBox="0 0 24 24" className="h-5 w-5" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" aria-hidden="true">
<path d="M18 6 6 18M6 6l12 12" />
</svg>
</button>
</div>
<ul className="flex-1 px-5 py-2">
{NAV.map((item) => (
<li key={item.label} className="border-b border-[#e4e7ed]">
<a href={item.href} className="block py-4 text-[17px] font-bold text-[#172031]">
{item.label}
</a>
{item.children && (
<div className="flex flex-wrap gap-1.5 pb-4">
{item.children.map((c) => (
<a key={c.label} href={c.href} className="rounded-full bg-[#f4f6f9] px-3 py-1.5 text-sm text-[#4a5466]">
{c.label}
</a>
))}
</div>
)}
</li>
))}
</ul>
<div className="grid gap-2 p-5">
<a href={`tel:${TEL}`} className="flex h-[52px] items-center justify-center rounded-xl bg-[#1B263B] font-bold text-white">
전화 상담 {TEL}
</a>
<a href={CHAT} target="_blank" rel="noopener noreferrer" title="새 창으로 열림" className="flex h-[52px] items-center justify-center rounded-xl bg-[#FEE500] font-bold text-[#191919]">
채팅 상담
<ExternalIcon />
<span className="sr-only">(새 창)</span>
</a>
</div>
</div>
</div>
)}
</header>
);
}
적용 사이트 0
이 블록이 실제로 쓰인 사이트입니다.
아직 등록된 사이트가 없습니다. 이 블록을 쓴 사이트가 있다면 알려 주세요.
재현 후기
v1 · 0개아직 이 버전의 프롬프트로 직접 만들어 본 사람이 없습니다. 첫 번째로 재현해 보세요.