Skip to content

Commit

Permalink
FE-50 ✨공용컴포넌트 헤더 구현 (#19)
Browse files Browse the repository at this point in the history
* FE-5050✨ feat:  헤더 부분 기능 초안

* FE-50 ✨styles: 주석 추가

* FE-50 ✨styles: 주석 추추가

* FE-5050 ✨test: 테스트 코드

* FE-50 ✨fix: 테스트 코드 삭제

* FE-50 ✨feat: 공유 이미지 추가 및 현재 URL 복사 기능 추가

* FE-50 ✨styles: U셋 중 하나가 빠지더라도 안무너지게 UI 수정

* FE-50 ✨comment:  주석 수정 및 추가

* FE-50 ✨fix: 테스트 코드 삭제

* FE-50 ✨fix:  함수명 컨벤션에 맞게 변경

* FE-50 ✨fix: types 폴더에 interface 정의

* FE-50 fix: build 오류 수정
  • Loading branch information
imsoohyeok authored Jul 12, 2024
1 parent c374a27 commit 09d3cb2
Show file tree
Hide file tree
Showing 9 changed files with 685 additions and 6 deletions.
536 changes: 535 additions & 1 deletion package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@
"react": "^18",
"react-dom": "^18",
"react-hook-form": "^7.52.1",
"react-toastify": "^10.0.5",
"sharp": "^0.33.4",
"tailwind-merge": "^2.4.0",
"tailwindcss-animate": "^1.0.7",
"zod": "^3.23.8"
Expand Down
5 changes: 5 additions & 0 deletions public/icon/arrow-left-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions public/icon/profile-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions public/icon/search-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions public/icon/share-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
107 changes: 107 additions & 0 deletions src/components/Header/Header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import React from 'react';
import { useRouter } from 'next/router';
import Image from 'next/image';
import { HeaderProps } from '../../types/Header';
import { useToast } from '../ui/use-toast';
import LOGO_ICON from '../../../public/epigram-icon.png';
import ARROW_LEFT_ICON from '../../../public/icon/arrow-left-icon.svg';
import PROFILE_ICON from '../../../public/icon/profile-icon.svg';
import SEARCH_ICON from '../../../public/icon/search-icon.svg';
import SHARE_ICON from '../../../public/icon/share-icon.svg';

// TODO 네비게이션 바를 나타내는 컴포넌트 입니다.
// TODO 상위 컴포넌트에서 Props를 받아 원하는 스타일을 보여줍니다.
// TODO 사용 예시
// TODO <Header icon='back' routerPage='원하는 페이지 주소' isLogo={false} insteadOfLogo='센터 텍스트' isProfileIcon={false} isShareIcon={false} isButton textInButton='버튼 텍스트' disabled={false} onClick={동작할 함수} />
// TODO <Header icon='search' routerPage='/search' isLogo insteadOfLogo='' isProfileIcon isShareIcon={false} isButton={false} textInButton='' disabled={false} onClick={() => {}} />;
// TODO icon: 'back'을 사용할 경우 routerPage의 값을 무조건 지정해줘야 합니다.
// TODO isLogo={false}일 경우 insteadOfLogo의 값을 무조건 지정해줘야 합니다.
// TODO isButton 일 경우 textInButton의 값을 무조건 지정해줘야 합니다.
// TODO SHARE_ICON 추가 시 토스트 기능도 사용하려면 해당 컴포넌트 아래 <Toaster /> 를 추가해주세요.

function Header({ isLogo, icon, insteadOfLogo, isButton, isProfileIcon, isShareIcon, textInButton, routerPage, disabled, onClick }: HeaderProps) {
const router = useRouter();
const { toast } = useToast();

// 페이지 이동 함수
const handleNavigateTo = (path: string) => {
router.push(path);
};

// 현재 링크 복사 함수
const handleCopyToClipboard = async () => {
try {
// 현재 URL 가져오기
const currentURL = window.location.href;
// 클립보드에 복사하기
await navigator.clipboard.writeText(currentURL);
toast({
title: '성공',
description: '링크가 클립보드에 복사되었습니다!',
});
} catch (err) {
toast({
title: '실패',
description: '링크 복사에 실패했습니다. 다시 시도해 주세요.',
variant: 'destructive',
});
}
};

return (
<nav className='bg-white h-13 px-6 py-4 md:px-28 md:py-5 lg:px-30 lg:py-6'>
<div className='container flex justify-between items-center'>
<div className='flex items-center space-x-4'>
{icon === 'back' && (
<button className='w-5 h-5 lg:w-9 lg:h-9' type='button' onClick={() => handleNavigateTo(routerPage)} aria-label='뒤로가기 버튼'>
<Image src={ARROW_LEFT_ICON} alt='뒤로가기 버튼 이미지' />
</button>
)}
{icon === 'search' && (
<button className='w-5 h-5 lg:w-9 lg:h-9' type='button' onClick={() => handleNavigateTo('/search')} aria-label='검색 버튼'>
<Image src={SEARCH_ICON} alt='검색 버튼 이미지' />
</button>
)}
</div>
<div className='flex-grow flex justify-center'>
{isLogo ? (
<button className='flex items-center gap-2' type='button' onClick={() => handleNavigateTo('/')} aria-label='홈으로 이동'>
<Image className='w-5 h-5 lg:w-9 lg:h-9' src={LOGO_ICON} alt='logo' />
<span className='text-black-700 text-6 lg:text-[26px] leading-6 font-bold'>Epigram</span>
</button>
) : (
<span className='text-black-700 text-6 lg:text-[26px] leading-6 font-bold'>{insteadOfLogo}</span>
)}
</div>
<div className='flex items-center space-x-4'>
{isProfileIcon && (
<button className='w-5 h-5 lg:w-9 lg:h-9' type='button' onClick={() => handleNavigateTo('/mypage')} aria-label='프로필 페이지로 이동'>
<Image src={PROFILE_ICON} alt='프로필 이미지' />
</button>
)}
{isShareIcon && (
<button className='w-5 h-5 lg:w-9 lg:h-9' type='button' onClick={handleCopyToClipboard} aria-label='링크 복사'>
<Image src={SHARE_ICON} alt='프로필 이미지' />
</button>
)}
{isButton && (
<button
className='flex justify-center items-center h-8 lg:h-11 px-4 rounded-lg bg-black-500
hover:bg-black-600
active:bg-black-700
disabled:bg-blue-400 border-blue-300
disabled:cursor-not-allowed'
type='button'
disabled={disabled}
onClick={onClick}
>
<span className='text-blue-100 text-xs lg:text-base font-pretendard font-semibold leading-5'>{textInButton}</span>
</button>
)}
</div>
</div>
</nav>
);
}

export default Header;
12 changes: 12 additions & 0 deletions src/types/Header.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export interface HeaderProps {
icon: 'back' | 'search' | '';
routerPage: string;
isLogo: boolean;
insteadOfLogo: string;
isProfileIcon: boolean;
isShareIcon: boolean;
isButton: boolean;
textInButton: string;
disabled: boolean;
onClick: (e: React.MouseEvent<HTMLButtonElement>) => void;
}
10 changes: 5 additions & 5 deletions tailwind.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,14 @@ module.exports = {
'sub-gray_3': '#EFF3F8',
},
screens: {
'sm': '640px',
'md': '768px',
'lg': '1024px',
'xl': '1280px',
sm: '640px',
md: '768px',
lg: '1024px',
xl: '1280px',
'2xl': '1536px',
},
backgroundImage: {
'stripes': 'repeating-linear-gradient(to bottom, #ffffff, #ffffff 23px, #e5e7eb 23px, #e5e7eb 24px)',
stripes: 'repeating-linear-gradient(to bottom, #ffffff, #ffffff 23px, #e5e7eb 23px, #e5e7eb 24px)',
},
},
},
Expand Down

0 comments on commit 09d3cb2

Please sign in to comment.