nnuer
강희태@gmlxo9917

사진학교 상단 헤더 (드롭다운 메뉴 + 모바일 드로어)

내비게이션 블록

로고, 2단 드롭다운 메뉴, 전화번호 버튼이 있는 흰색 고정 헤더입니다. 1024px 미만에서는 햄버거 버튼과 오른쪽 슬라이드 드로어로 바뀝니다.

#sticky#dropdown#drawer#white#korean2026. 10. 16. 업데이트

블록 신고

운영자가 확인한 뒤 필요하면 블록을 숨깁니다.

사유
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개

아직 이 버전의 프롬프트로 직접 만들어 본 사람이 없습니다. 첫 번째로 재현해 보세요.