Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
737d855
refactor: useClickOutside 훅에서 다양한 HTML 태그에 사용 가능한 ref 처리를 위해 제네릭으로 유연…
juyesu Jun 29, 2025
3c76f69
refactor: dropdown 공통 컴포넌트를 사용하는 방식으로 변경
juyesu Jun 29, 2025
5734be5
refactor: gnb 내 html구조 개선
juyesu Jun 29, 2025
eab40e6
refactor: 컴포넌트명이 더 명확한 의미를 가지도록 UserDropdown -> UserProfileDropdown 으…
juyesu Jun 29, 2025
e559dd0
refactor: sidedrawer 레이아웃 구조 및 상태 관리 방식 변경
juyesu Jun 29, 2025
90eda7c
refactor: header(GNB), SideDrawer, Overlay의 z-index 계층 구조를 UI 표시 순서에 …
juyesu Jun 29, 2025
b7fbaa3
refactor: 닫기 버튼에 사용되는 UI를 텍스트에서 svg로 변경
juyesu Jun 29, 2025
75e6d03
refactor: sideDrawer 접근성 개선
juyesu Jun 30, 2025
34aadb9
refactor: gnb 및 프로필 드롭다운 접근성 개선
juyesu Jun 30, 2025
2739c0e
refactor: 컨벤션에 따라 GNB컴포넌트를 export default로 변경
juyesu Jun 30, 2025
87bb85d
refactor: 조건 검사에 사용되는 라우트 주소를 상수로 변경
juyesu Jun 30, 2025
7152586
refactor: 사용되지 않는 스타일 제거, drawer내부의 hover 스타일을 active 스타일로 변경
juyesu Jun 30, 2025
b22862e
refactor: gnb관련 컴포넌트의 파일 import를 절대 경로로 변경
juyesu Jun 30, 2025
e35e679
Merge branch 'develop' of https://github.com/we-write/fontend into re…
juyesu Jul 2, 2025
9ec8298
refactor: esc 키를 통한 상태관리 로직을 커스텀 훅으로 분리
juyesu Jul 3, 2025
d35cf7f
refactor: 중복되는 Escape 키 감지 로직 제거
juyesu Jul 3, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ import { Hanuman } from 'next/font/google';
import type { Metadata, Viewport } from 'next';
import '@/styles/globals.css';
import QueryProviders from '@/providers/queryProviders';
import { GNB } from '@/components/layout/GNB/GNB';
import GNB from '@/components/layout/GNB/GNB';
import LayoutWrapper from '@/components/layout/LayoutWrapper';
import AuthProvider from '@/providers/auth-provider/AuthProvider';
import { ReactNode } from 'react';
import { ToastProvider } from '@/providers/ToastProvider';
import SideDrawer from '@/components/layout/SideDrawer/SideDrawer';

const pretendard = localFont({
src: '../fonts/PretendardVariable.woff2',
Expand Down Expand Up @@ -45,13 +46,14 @@ const RootLayout = ({
<ToastProvider>
<QueryProviders>
<AuthProvider>
<header>
<header className="fixed z-40 h-15 w-full">
<GNB />
</header>

<main>
<LayoutWrapper>{children}</LayoutWrapper>
</main>
<SideDrawer />
</AuthProvider>
</QueryProviders>
</ToastProvider>
Expand Down
9 changes: 7 additions & 2 deletions src/components/common/Dropdown/Dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,14 @@ export const DropdownContent = ({
export const DropdownContainer = ({
children,
className = '',
...rest
}: DropdownContainerProps) => {
const defaultOptionContainerStyle = `overflow-hidden rounded-md bg-white mt-2 ${className}`;
return <ul className={defaultOptionContainerStyle}>{children}</ul>;
return (
<ul className={defaultOptionContainerStyle} {...rest}>
{children}
</ul>
);
};

const Dropdown = ({
Expand All @@ -34,7 +39,7 @@ const Dropdown = ({
className = '',
onClose,
}: DropdownProps) => {
const outSideRef = useClickOutside(() => {
const outSideRef = useClickOutside<HTMLDivElement>(() => {
if (isOpen && onClose) {
onClose();
}
Expand Down
7 changes: 3 additions & 4 deletions src/components/common/Dropdown/type.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { MouseEventHandler, ReactNode } from 'react';
import { MouseEventHandler, ReactNode, HTMLAttributes } from 'react';

export interface DropdownProps {
trigger?: ReactNode;
Expand All @@ -14,7 +14,6 @@ export interface DropdownContentProps {
className?: string;
}

export interface DropdownContainerProps {
className?: string;
export type DropdownContainerProps = HTMLAttributes<HTMLUListElement> & {
children: ReactNode;
}
};
68 changes: 9 additions & 59 deletions src/components/layout/GNB/GNB.tsx
Original file line number Diff line number Diff line change
@@ -1,65 +1,15 @@
'use client';

import LoginSection from './LoginSection';
import SideDrawer from '../SideDrawer/SideDrawer';
import LogoButton from '@/components/layout/GNB/LogoButton';
import MenuGroups from '@/components/layout/GNB/MenuGroups';
import { APP_ROUTES, APP_ROUTES_LABEL } from '@/constants/appRoutes';
import useBoolean from '@/hooks/useBoolean';
import { Hamburger } from '@public/assets/icons';

// 메뉴 항목
const MENU_ITEMS = [
{ label: APP_ROUTES_LABEL.mypage, href: APP_ROUTES.mypage },
{ label: APP_ROUTES_LABEL.social, href: APP_ROUTES.social },
{ label: APP_ROUTES_LABEL.library, href: APP_ROUTES.library },
];

export const GNB = () => {
const {
value: isDrawerOpen,
setTrue: setIsDrawerOpen,
setFalse: setIsDrawerClose,
} = useBoolean();

const GNB = () => {
return (
<>
<nav className="fixed top-0 z-50 h-15 w-full border-b border-gray-200 bg-white">
<div className="flex-center mx-auto h-full w-full max-w-300 px-4 md:justify-between md:pr-6 md:pl-7 lg:px-1">
{/* Logo */}
<div className="flex items-center gap-5 truncate lg:gap-10">
<LogoButton />
{/* 데스크탑 메뉴 */}
<MenuGroups />
</div>
{/* 데스크탑 로그인 영역*/}
<LoginSection />
{/* 모바일 햄버거 */}
<button
className="absolute top-5 right-5 md:hidden"
onClick={() => setIsDrawerOpen()}
>
<Hamburger
className="h-6 w-6 text-gray-500"
fill="currentColor"
aria-hidden="true"
/>
</button>
</div>
</nav>
{/* 오버레이 */}
{isDrawerOpen && (
<div
className="fixed inset-0 z-50 h-screen bg-black/50 md:hidden"
onClick={() => setIsDrawerClose()}
/>
)}
{/* 시아드 드로어 (모바일 화면에 표시) */}
<SideDrawer
isOpen={isDrawerOpen}
closeDrawer={() => setIsDrawerClose()}
menuItems={MENU_ITEMS}
/>
</>
<nav className="flex-center h-full w-full border-b border-gray-200 bg-white">
<div className="flex w-full max-w-300 gap-6 px-4 md:pr-6 md:pl-7 lg:gap-10 xl:px-1">
<LogoButton />
<MenuGroups />
</div>
</nav>
);
};

export default GNB;
68 changes: 0 additions & 68 deletions src/components/layout/GNB/LoginSection.tsx

This file was deleted.

12 changes: 8 additions & 4 deletions src/components/layout/GNB/LogoButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,15 @@ import Link from 'next/link';

const LogoButton = ({ onClick }: LogoButtonProps) => {
return (
<h1 className="text-write-main font-hanuman pt-1.5 text-lg font-black lg:text-2xl lg:font-bold">
<Link href={APP_ROUTES.home} onClick={onClick}>
<Link
href={APP_ROUTES.home}
onClick={onClick}
className="mx-auto flex items-center"
>
<div className="text-write-main font-hanuman pt-1.5 text-lg font-black lg:text-2xl lg:font-bold">
WeWrite
</Link>
</h1>
</div>
</Link>
);
};
export default LogoButton;
114 changes: 84 additions & 30 deletions src/components/layout/GNB/MenuGroups.tsx
Original file line number Diff line number Diff line change
@@ -1,44 +1,98 @@
'use client';

import { APP_ROUTES, APP_ROUTES_LABEL } from '@/constants/appRoutes';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
import { Users, BookOpen } from 'lucide-react';
import useClickOutside from '@/hooks/useClickOutside';
import useBoolean from '@/hooks/useBoolean';
import { useAuth } from '@/providers/auth-provider/AuthProvider.client';
import useReferer from '@/hooks/useReferer';
import UserProfileDropdown from '@/components/layout/GNB/UserProfileDropdown';
import { Hamburger } from '@public/assets/icons';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lucide 아이콘으로 통일하는건 어떨까요?

Copy link
Collaborator Author

@juyesu juyesu Jul 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저도 lucide 아이콘으로 변경할지 생각해보았는데 아직 기존에 사용된 SVGR을 lucide로 대체해자는 논의가 나오지 않은 상황에서 lucide 로 변경하는건 리팩토링보단 취향적인 부분이 될 것 같아서 이번 작업에는 제외됐습니다.

lucide 로 대체 가능한 svg는 lucide로 변경하는 것도 괜찮고, 이미 프로젝트에 사용된 svgr은 변경하지 않는 방법도 있을 것 같아서 위클리 스크럼 때 논의해볼까요?

import { useSideDrawerStore } from '@/lib/store/useSideDrawerStore';

const MenuGroups = () => {
const pathname = usePathname();
const { refererParam } = useReferer();
const { isSignIn, myInfo } = useAuth();
const { openDrawer } = useSideDrawerStore();

const {
value: isDropdownOpen,
toggle: toggleDropDown,
setFalse: closeDropdown,
} = useBoolean();

const ref = useClickOutside<HTMLLIElement>(closeDropdown);

return (
<ul className="hidden gap-6 font-semibold md:flex">
<li
className={`${
pathname === '/social'
? 'text-write-main bg-gray-100'
: 'text-gray-500'
} hover:text-write-main inline-flex rounded-xl px-3 py-1.5 hover:bg-gray-100`}
>
<Link
href={APP_ROUTES.social}
className="inline-flex items-center gap-1.5"
<>
<ul className="hidden gap-6 font-semibold md:flex md:w-full md:items-center">
<li
className={`${
pathname === `${APP_ROUTES.social}`
? 'text-write-main bg-gray-100'
: 'text-gray-500'
} hover:text-write-main inline-flex rounded-xl px-3 py-1.5 hover:bg-gray-100`}
>
<Users className="h-5 w-5" aria-hidden="true" />
{APP_ROUTES_LABEL.social}
</Link>
</li>
<li
className={`${
pathname === '/library'
? 'text-write-main bg-gray-100'
: 'text-gray-500'
} hover:text-write-main inline-flex rounded-xl px-2 py-1.5 hover:bg-gray-100`}
>
<Link
href={APP_ROUTES.library}
className="inline-flex items-center gap-1.5"
<Link
href={APP_ROUTES.social}
className="inline-flex items-center gap-1.5"
>
<Users className="h-5 w-5" aria-hidden="true" />
{APP_ROUTES_LABEL.social}
</Link>
</li>

<li
className={`${
pathname === `${APP_ROUTES.library}`
? 'text-write-main bg-gray-100'
: 'text-gray-500'
} hover:text-write-main inline-flex rounded-xl px-2 py-1.5 hover:bg-gray-100`}
>
<BookOpen className="h-5 w-5" aria-hidden="true" />
{APP_ROUTES_LABEL.library}
</Link>
</li>
</ul>
<Link
href={APP_ROUTES.library}
className="inline-flex items-center gap-1.5"
>
<BookOpen className="h-5 w-5" aria-hidden="true" />
{APP_ROUTES_LABEL.library}
</Link>
</li>

<li ref={ref} className="relative ml-auto hidden md:flex">
{myInfo && isSignIn ? (
<UserProfileDropdown
isDropdownOpen={isDropdownOpen}
toggleDropDown={toggleDropDown}
closeDropdown={closeDropdown}
userName={myInfo.name ?? null}
profileImage={myInfo.image ?? null}
/>
) : (
<Link
href={`${APP_ROUTES.signin}?${refererParam}`}
className="text-write-main text-base font-semibold"
>
로그인
</Link>
)}
</li>
</ul>
<button
type="button"
className="absolute top-5 right-5 md:hidden"
onClick={() => openDrawer()}
aria-label="사이드 드로어 열기"
>
<Hamburger
className="h-6 w-6 text-gray-500"
fill="currentColor"
aria-hidden="true"
/>
</button>
</>
);
};

Expand Down
Loading