Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: 공통 컴포넌트 gnb 컴포넌트 UI 작성 #26

Merged
merged 27 commits into from
Sep 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
3fe1182
[KAN-10] style: gnb UI 추가
hakyoung12 Sep 6, 2024
16b0c09
[KAN-10] style: 주소에 pathname이 포함되어있다면 해당 메뉴 텍스트 색상이 black으로 변경
hakyoung12 Sep 6, 2024
f5f306c
[KAN-10] style: 프로필 UI 추가
hakyoung12 Sep 6, 2024
8dc165c
[KAN-10] style: gnb 반응형 디자인 설정
hakyoung12 Sep 6, 2024
c1edb78
[KAN-10] style: 반응형 사이즈 수정
hakyoung12 Sep 6, 2024
5147919
[KAN-10] style: profile 커서포인터 추가
hakyoung12 Sep 6, 2024
4342508
[KAN-10] style: gnb style 수정
hakyoung12 Sep 6, 2024
f613ce1
[KAN-10] style: gnb 반응형 폰트 사이즈 추가
hakyoung12 Sep 6, 2024
1f695be
[KAN-11] feat: BoxSelect 컴포넌트 (데스크톱 버전) 추가
BeMatthewsong Sep 6, 2024
4d0a8a5
[KAN-11] chore: tailwind 미디어쿼리 설정 추가
BeMatthewsong Sep 6, 2024
2f8013e
[KAN-11] style: BoxSelect 컴포넌트 (모바일 버전) 추가 및 trasition 추가
BeMatthewsong Sep 6, 2024
9aecc82
[KAN-11] fix: 상위 태그를 label 태그로 교체
BeMatthewsong Sep 6, 2024
336e845
[KAN-11] style: 스크린 사이즈에 따른 전체 높이 조절
BeMatthewsong Sep 6, 2024
798c302
[KAN-11] chore: 불필요한 코드 삭제
BeMatthewsong Sep 6, 2024
b98120c
[KAN-11] chore: ChangeEvent Import
BeMatthewsong Sep 6, 2024
5bcdcf2
[KAN-11] chore: 불필요한 코드 삭제
BeMatthewsong Sep 6, 2024
4be38c8
[KAN-11] fix: 변수명 변경 및 테일윈드 수정
BeMatthewsong Sep 6, 2024
e5c1124
[KAN-3] chore : tailwind 설정 font-size 추가
yunchaeney Sep 6, 2024
dea6550
[KAN-3] feat: 컴포넌트 Tab 추가 및 svg icon 코드 변경
yunchaeney Sep 6, 2024
fe7b6b1
[KAN-3] feat: 컴포넌트 Chips 추가 (chip, info, state, time)
yunchaeney Sep 6, 2024
641109c
[KAN-3] feat: 컴포넌트 Badge, Tag 추가
yunchaeney Sep 6, 2024
bac2058
[KAN-3] feat: 컴포넌트 Filter 추가 (list, sort)
yunchaeney Sep 6, 2024
6420887
[KAN-3] feat: 컴포넌트 Top Tab 추가
yunchaeney Sep 6, 2024
4ca1ebf
[KAN-3] refactor: PR 리뷰 반영 (fragment 삭제, ReactNode import)
yunchaeney Sep 6, 2024
5afe17a
[KAN-10] rename: Input 폴더 변경
hakyoung12 Sep 9, 2024
66a7390
[KAN-10] feat: TopTab 컴포넌트적용
hakyoung12 Sep 9, 2024
e1515f0
[KAN-10] feat: 찜한 모임 뱃지 추가
hakyoung12 Sep 9, 2024
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
18 changes: 9 additions & 9 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 8 additions & 8 deletions public/icons/dalaemfit.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
26 changes: 13 additions & 13 deletions public/icons/workation.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 13 additions & 0 deletions src/app/components/Badge/Badge.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
interface BadgeProps {
children: string;
}

const Badge = ({ children }: BadgeProps) => {
return (
<div className='rounded-full bg-var-gray-900 px-8 text-12 font-semibold text-white'>
{children}
</div>
);
};

export default Badge;
41 changes: 41 additions & 0 deletions src/app/components/BoxSelect/BoxSelect.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
'use client';

import { ChangeEvent, useState } from 'react';

/**
* BoxSelect component
* @param {string} title - 제목
* @param {string} subTitle - 부제목
*/

interface BoxSelectProps {
title: string;
subTitle: string;
}

const BoxSelect = ({ title = '', subTitle = '' }: BoxSelectProps) => {
const [isSelected, setIsSelected] = useState<boolean>(false);

const handleCheckboxChange = (event: ChangeEvent<HTMLInputElement>) => {
setIsSelected(event.target.checked);
};

return (
<label
className={`flex h-76 w-full items-start gap-8 rounded-lg ${isSelected ? 'bg-var-gray-900 text-var-white' : 'bg-var-gray-50 text-var-black'} md:h-70 lg:h-70 px-8 py-[6px] transition-colors duration-200 ease-in-out md:px-16 md:py-12`}
>
<input
id='checkbox'
type='checkbox'
className="h-24 w-24 appearance-none rounded-md bg-[url('/icons/checkbox-default.svg')] bg-center bg-no-repeat checked:bg-[url('/icons/checkbox-active.svg')] checked:bg-center checked:bg-no-repeat"
onChange={handleCheckboxChange}
/>
<div className='flex flex-col'>
<div className='text-14 md:text-16 font-bold'>{title}</div>
<div className='text-12 font-medium'>{subTitle}</div>
</div>
</label>
);
};

export default BoxSelect;
21 changes: 21 additions & 0 deletions src/app/components/Chip/Chip.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
interface ChipProps {

Choose a reason for hiding this comment

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

칩스 컴포넌트를 하나로 선언해두고 variant 프롭스로 InfoChip, StateChip, TimeChip 등 다양한 칩을 리터하도록 내부에서 조정해보도록 리팩토링해보시면 좋을 것 같습니다.

공통 컴포넌트 혹은 디자인 시스템을 사용할대, 아톤 컴포넌트 하나를 만들고 그 내부에서 variant props를 두고 사용목적에 맞게 리턴하도록 사용하는 패턴이 많이 응용되거든요

state?: 'default' | 'active';
children: string;
}

const stateClasses = {
default: 'bg-var-gray-200 text-var-bg-gray-900',
active: 'bg-var-gray-900 text-white',
};

const Chip = ({ state = 'default', children }: ChipProps) => {
return (
<span
className={`inline-block rounded-[12px] px-[12px] py-8 text-14 font-medium md:px-16 md:py-[10px] ${stateClasses[state]}`}
>
{children}
</span>
);
};

export default Chip;
21 changes: 21 additions & 0 deletions src/app/components/Chip/InfoChip.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
interface InfoChipProps {
type?: 'date' | 'time';
children: string;
}

const typeClasses = {
date: 'text-white',
time: 'text-var-orange-600',
};

const InfoChip = ({ type = 'date', children }: InfoChipProps) => {
return (
<span
className={`inline-block rounded-[4px] bg-var-gray-900 px-8 py-[2px] text-14 font-medium ${typeClasses[type]}`}
>
{children}
</span>
);
};

export default InfoChip;
32 changes: 32 additions & 0 deletions src/app/components/Chip/StateChip.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { IconCheck } from '@/public/icons';

interface StateChipProps {
state: 'scheduled' | 'done' | 'confirmed' | 'pending'; // 이용 예정 | 이용 완료 | 개설 확정 | 개설 대기
}

const stateClasses = {
scheduled: 'bg-var-orange-100 text-var-orange-600',
done: 'bg-var-gray-200 text-var-gray-500',
confirmed: 'bg-white text-var-orange-500 border border-var-orange-100',
pending: 'bg-white text-var-gray-500 border border-var-gray-200',
};

const stateContents = {

Choose a reason for hiding this comment

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

상수로 분리해두는게 더 좋을 것 같네요

scheduled: '이용 예정',
done: '이용 완료',
confirmed: '개설 확정',
pending: '개설 대기',
};

const StateChip = ({ state }: StateChipProps) => {
return (
<span
className={`inline-flex h-32 items-center rounded-full px-[12px] py-[6px] text-14 font-medium ${stateClasses[state]}`}
>
{state === 'confirmed' ? <IconCheck className='mr-4 h-16 w-16' /> : null}
{stateContents[state]}
</span>
);
};

export default StateChip;
23 changes: 23 additions & 0 deletions src/app/components/Chip/TimeChip.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
interface TimeChipProps {
state?: 'default' | 'active' | 'disabled';
children: string;
}

const stateClasses = {
default:
'bg-var-gray-50 border border-var-gray-200 text-var-gray-900 cursor-pointer',
active: 'bg-var-gray-900 text-white cursor-pointer',
disabled: 'bg-var-gray-200 text-var-gray-400 cursor-default',
};

const TimeChip = ({ state = 'default', children }: TimeChipProps) => {
return (
<span
className={`inline-block h-32 rounded-[8px] px-[12px] py-[6px] text-14 font-medium ${stateClasses[state]}`}
>
{children}
</span>
);
};

export default TimeChip;
24 changes: 24 additions & 0 deletions src/app/components/Filter/FilterList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { IconCaret } from '@/public/icons';

interface FilterListProps {
state?: 'default' | 'active';
children: string;
}

const stateClasses = {
default: 'border border-var-gray-100 bg-var-gray-50 text-var-gray-800',
active: 'text-var-gray-50 bg-var-gray-900',
};

const FilterList = ({ state = 'default', children }: FilterListProps) => {
return (
<div
className={`flex h-36 w-[110px] items-center justify-between rounded-[12px] py-[6px] pl-12 pr-8 text-14 font-medium md:h-40 md:w-120 md:py-8 ${stateClasses[state]}`}
>
{children}
<IconCaret className='h-24 w-24' />
</div>
);
};

export default FilterList;
24 changes: 24 additions & 0 deletions src/app/components/Filter/FilterSort.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { IconSort } from '@/public/icons';

interface FilterSortProps {
state?: 'default' | 'active';
children: string;
}

const stateClasses = {
default: 'border border-var-gray-100 bg-var-gray-50 text-var-gray-800',
active: 'text-var-gray-50 bg-var-gray-900',
};

const FilterSort = ({ state = 'default', children }: FilterSortProps) => {
return (
<div
className={`flex h-36 w-36 items-center justify-between rounded-[12px] p-[6px] text-14 font-medium md:h-40 md:w-120 md:px-12 md:py-[6px] ${stateClasses[state]}`}
>
<IconSort className='h-24 w-24' />
<span className='hidden md:mr-8 md:inline'>{children}</span>
</div>
);
};

export default FilterSort;
68 changes: 68 additions & 0 deletions src/app/components/Gnb/Gnb.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
'use client';

import { Logo } from '@/public/images';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
import UserStatus from './UserStatus';
import TopTab from '../Tab/TopTab';
import Badge from '../Badge/Badge';

/* @todo 웹 스토리지에 저장된 찜한 모임의 length를 추출하는 방식으로 변경 예정 */
const favoriteGroups = '12';

//@todo pathname 정해질 시 추가 예정
Copy link
Contributor

Choose a reason for hiding this comment

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

👍

Copy link
Contributor

Choose a reason for hiding this comment

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

👍

P4) 정말 사소한 건데 'use client' 쓰고 한 줄 띄워주셔도 좋을 것 같습니다!

const navList = [
{
name: '모임 찾기',
link: '#',
},
{
name: '찜한 모임',
link: '#',
Comment on lines +20 to +21
Copy link
Contributor

Choose a reason for hiding this comment

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

찜한 모임 옆에 갯수 Badge 달리는 경우는 어떻게 되나요 ?_?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

그부분은 제가 깜빡했습니다ㅠ 추가하겠습니다!

},
{
name: '모든 리뷰',
link: '#',
},
];

const Gnb = () => {
const pathname = usePathname();
Copy link
Contributor Author

Choose a reason for hiding this comment

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

현재 URL 경로를 가져오는 코드입니다.

Copy link
Contributor

Choose a reason for hiding this comment

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

👍


/* 찜한 모임 배지 렌더링 함수
nav.name이 '찜한 모임'이고 웹스토리지에 저장된 '찜한 모임'의 개수가 1개 이상일 시 Badge 렌더링 */
const renderBadge = (name: string) => {

Choose a reason for hiding this comment

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

목요일날 배운 뷰 컴포넌트에서 상태 로직 분리하기를 떠올려보시고,
커스텀 훅을 작성해보는게 어떨까요?

return name === '찜한 모임' && Number(favoriteGroups) > 0 ? (
<div className='py-[18px]'>
<Badge>{favoriteGroups}</Badge>
</div>
) : null;
};

return (
<header className='bg-var-orange-600 py-16'>
<div className='mx-16 flex max-w-[1200px] items-center justify-between md:mx-24 lg:mx-auto'>
<nav className='flex items-center'>
<Link href='/'>
<Logo className='mr-20 h-40 w-72' />
</Link>
<ul className='flex gap-24'>
{navList.map((nav, index) => (
<li key={index} className='flex items-center gap-[5px]'>
<Link href={nav.link}>
<TopTab isActive={pathname.includes(nav.link)}>
{nav.name}
</TopTab>
</Link>
{renderBadge(nav.name)}
</li>
))}
</ul>
Comment on lines +49 to +60
Copy link
Contributor Author

Choose a reason for hiding this comment

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

9/9 변경사항입니다!

  • TopTab 컴포넌트 적용
  • 찜한 모임 뱃지 추가

Copy link
Contributor

Choose a reason for hiding this comment

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

👍👍👍 반영 감사합니다!

</nav>
<UserStatus />
</div>
</header>
);
};

export default Gnb;
Loading
Loading