diff --git a/LOG.md b/LOG.md index 753d52c..1fa2b73 100644 --- a/LOG.md +++ b/LOG.md @@ -2,6 +2,13 @@ ## 2026-05-28 +- 상단 우측의 테마 토글, 로그인/회원가입, 관리자/새 글/로그아웃 액션을 작은 메뉴 버튼 뒤로 숨기고, 클릭 시 왼쪽으로 펼쳐지는 패널 애니메이션으로 노출되도록 수정했다. +- 메뉴 바깥 클릭과 Escape 키로 닫히게 하고, 링크 이동/로그아웃 시 메뉴가 닫히도록 정리했다. +- 검증: `npm run lint` 통과, `npm run build` 통과. build 중 sitemap fetch는 로컬 네트워크 제한으로 기존처럼 경고를 출력했지만 실패하지 않았다. Browser로 데스크톱/모바일 홈에서 닫힘 상태와 펼침 상태를 확인했다. +- 다음 추천 작업: 실제 로그인/관리자 상태에서 펼침 패널의 버튼 수가 늘어났을 때 모바일 폭에서 자연스럽게 보이는지 production Origin에서 확인한다. + +## 2026-05-28 + - Pretendard Variable WOFF2를 `src/app/fonts`에 self-host로 추가하고 `next/font/local`로 전역 적용했다. - 홈을 Apple editorial 톤으로 정리해 히어로, 공지, 대표 글, 최신 글 중심으로 줄이고 최근 업데이트/카테고리 선반/인기 글/하단 아카이브 CTA를 제거했다. - TopHeader와 ThemeToggle을 아이콘 중심의 조용한 컨트롤로 낮추고, Sidebar는 모바일 기본 닫힘 + 오버레이 drawer로 바꿨다. diff --git a/src/components/layout/TopHeader.tsx b/src/components/layout/TopHeader.tsx index 9eb1be0..725c73b 100644 --- a/src/components/layout/TopHeader.tsx +++ b/src/components/layout/TopHeader.tsx @@ -1,80 +1,141 @@ // src/components/layout/TopHeader.tsx 'use client'; +import { useEffect, useRef, useState } from 'react'; import Link from 'next/link'; import { useRouter } from 'next/navigation'; import { useAuthStore } from '@/store/authStore'; -import { LogOut, PenLine, Settings, User, UserPlus } from 'lucide-react'; +import { LogOut, Menu, PenLine, Settings, User, UserPlus, X } from 'lucide-react'; import ThemeToggle from '@/components/theme/ThemeToggle'; export default function TopHeader() { const router = useRouter(); const { isLoggedIn, role, logout, _hasHydrated } = useAuthStore(); + const [isMenuOpen, setIsMenuOpen] = useState(false); + const menuRef = useRef(null); const isAdmin = _hasHydrated && role?.includes('ADMIN'); + useEffect(() => { + const handlePointerDown = (event: PointerEvent) => { + if (!menuRef.current?.contains(event.target as Node)) { + setIsMenuOpen(false); + } + }; + + const handleKeyDown = (event: KeyboardEvent) => { + if (event.key === 'Escape') { + setIsMenuOpen(false); + } + }; + + document.addEventListener('pointerdown', handlePointerDown); + document.addEventListener('keydown', handleKeyDown); + + return () => { + document.removeEventListener('pointerdown', handlePointerDown); + document.removeEventListener('keydown', handleKeyDown); + }; + }, []); + + const closeMenu = () => setIsMenuOpen(false); + const handleLogout = () => { if (confirm('로그아웃 하시겠습니까?')) { + closeMenu(); logout(); alert('로그아웃 되었습니다.'); router.push('/'); } }; - return ( -
- + const quietActionClass = + 'flex h-9 shrink-0 items-center gap-2 rounded-full border border-[var(--color-line)] bg-[var(--color-control)] px-3 text-sm font-semibold text-[var(--color-text-muted)] shadow-[var(--shadow-control)] backdrop-blur-2xl transition-colors hover:bg-[var(--color-surface-strong)] hover:text-[var(--color-text)]'; - {_hasHydrated && ( - isLoggedIn ? ( - <> - {isAdmin && ( + return ( +
+
+
+ + + {_hasHydrated && ( + isLoggedIn ? ( + <> + {isAdmin && ( + <> + + + 관리자 + + + + + 새 글 + + + )} + + + + ) : ( <> - - 관리자 + + 로그인 - - 새 글 + + 회원가입 - )} + ) + )} +
+
- - - ) : ( - <> - - - 로그인 - - - - - 회원가입 - - - ) - )} +
); }