-
Notifications
You must be signed in to change notification settings - Fork 0
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
Changes from all commits
3fe1182
16b0c09
f5f306c
8dc165c
c1edb78
5147919
4342508
f613ce1
1f695be
4d0a8a5
2f8013e
9aecc82
336e845
798c302
b98120c
5bcdcf2
4be38c8
e5c1124
dea6550
fe7b6b1
641109c
bac2058
6420887
4ca1ebf
5afe17a
66a7390
e1515f0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
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; |
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; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
interface ChipProps { | ||
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; |
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; |
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 = { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; |
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; |
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; |
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; |
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 정해질 시 추가 예정 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 찜한 모임 옆에 갯수 Badge 달리는 경우는 어떻게 되나요 ?_? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 그부분은 제가 깜빡했습니다ㅠ 추가하겠습니다! |
||
}, | ||
{ | ||
name: '모든 리뷰', | ||
link: '#', | ||
}, | ||
]; | ||
|
||
const Gnb = () => { | ||
const pathname = usePathname(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 현재 URL 경로를 가져오는 코드입니다. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
|
||
/* 찜한 모임 배지 렌더링 함수 | ||
nav.name이 '찜한 모임'이고 웹스토리지에 저장된 '찜한 모임'의 개수가 1개 이상일 시 Badge 렌더링 */ | ||
const renderBadge = (name: string) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 9/9 변경사항입니다!
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍👍👍 반영 감사합니다! |
||
</nav> | ||
<UserStatus /> | ||
</div> | ||
</header> | ||
); | ||
}; | ||
|
||
export default Gnb; |
There was a problem hiding this comment.
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를 두고 사용목적에 맞게 리턴하도록 사용하는 패턴이 많이 응용되거든요