-
Notifications
You must be signed in to change notification settings - Fork 2
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(client): 캘린더 컴포넌트 구현 #118
base: develop
Are you sure you want to change the base?
Changes from all commits
ab1aaed
560050f
c4ea1d7
52d5c9f
9deae98
aef1d60
63b6067
0214c6f
adca901
0af590c
0ebcaa3
efd2e04
4252fa5
f652f95
dbaaf8c
86e53a1
fa8f1ef
a1cbe4f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
import { style } from '@vanilla-extract/css'; | ||
import { recipe } from '@vanilla-extract/recipes'; | ||
import { themeVars } from '@confeti/design-system/styles'; | ||
|
||
export const container = style({ | ||
padding: '2rem', | ||
}); | ||
|
||
export const yearSection = style({ | ||
...themeVars.fontStyles.title5_b_15, | ||
color: themeVars.color.black, | ||
}); | ||
|
||
export const dateSection = style({ | ||
display: 'grid', | ||
gridTemplateColumns: 'repeat(7,1fr)', | ||
padding: '2.1rem 0rem', | ||
gap: '2rem', | ||
}); | ||
|
||
export const dateItems = style({ | ||
...themeVars.display.flexColumnCenter, | ||
width: '3rem', | ||
height: '5.5rem', | ||
gap: '0.6rem', | ||
}); | ||
|
||
export const dayNum = recipe({ | ||
base: { | ||
...themeVars.display.flexCenter, | ||
width: '3rem', | ||
height: '3rem', | ||
borderRadius: '1.5rem', | ||
cursor: 'pointer', | ||
background: 'transparent', | ||
}, | ||
variants: { | ||
isSelected: { | ||
true: { | ||
background: themeVars.color.confeti_lime, | ||
transition: 'background 0.3s ease', | ||
}, | ||
}, | ||
hasFestivalDate: { | ||
true: { | ||
...themeVars.fontStyles.title4_b_16, | ||
color: themeVars.color.black, | ||
}, | ||
false: { | ||
...themeVars.fontStyles.body1_r_16, | ||
color: themeVars.color.gray500, | ||
cursor: 'default', | ||
}, | ||
}, | ||
}, | ||
}); | ||
|
||
export const dayKo = recipe({ | ||
base: { | ||
...themeVars.display.flexCenter, | ||
background: 'transparent', | ||
}, | ||
variants: { | ||
hasFestivalDate: { | ||
true: { | ||
...themeVars.fontStyles.title4_b_16, | ||
color: themeVars.color.black, | ||
}, | ||
false: { | ||
...themeVars.fontStyles.body1_r_16, | ||
color: themeVars.color.gray500, | ||
}, | ||
}, | ||
}, | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import * as styles from './calender.css'; | ||
import { cn } from '@confeti/design-system/utils'; | ||
import { | ||
useFormattedYear, | ||
useFormattedWeek, | ||
useDayNumSelection, | ||
createFestivalDateMap, | ||
checkFestivalDateStatus, | ||
} from '@pages/time-table/hooks/use-data-formatted'; | ||
|
||
interface CalenderProps { | ||
festivalDates: { festivalDateId: number; festivalAt: string }[]; | ||
} | ||
|
||
const Calender = ({ festivalDates }: CalenderProps) => { | ||
const firstDate = festivalDates[0]?.festivalAt; | ||
const { weekDays } = useFormattedWeek(firstDate); | ||
const { selectedDayNumeId, handleDayNumClick } = useDayNumSelection(); | ||
const festivalDateMap = createFestivalDateMap(festivalDates); | ||
|
||
const dateItems = weekDays.map((day, id) => ({ | ||
...day, | ||
...checkFestivalDateStatus(festivalDateMap, id, selectedDayNumeId), | ||
})); | ||
|
||
return ( | ||
<section className={styles.container}> | ||
<div className={styles.yearSection}> | ||
<span>{useFormattedYear(firstDate)}</span> | ||
</div> | ||
<div className={styles.dateSection}> | ||
{dateItems.map( | ||
({ date, dayKo, festivalDateId, isSelected, hasFestivalDate }) => ( | ||
<div className={styles.dateItems} key={festivalDateId}> | ||
<p | ||
className={cn(styles.dayNum({ isSelected, hasFestivalDate }))} | ||
onClick={() => | ||
festivalDateId && handleDayNumClick(festivalDateId) | ||
} | ||
> | ||
{date} | ||
</p> | ||
<p | ||
className={cn(styles.dayKo({ hasFestivalDate }))} | ||
onClick={() => | ||
festivalDateId && handleDayNumClick(festivalDateId) | ||
} | ||
> | ||
{dayKo} | ||
</p> | ||
</div> | ||
), | ||
)} | ||
</div> | ||
</section> | ||
); | ||
}; | ||
|
||
export default Calender; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -71,6 +71,18 @@ export const ImageVariants = recipe({ | |
}, | ||
lg: {}, | ||
}, | ||
isClicked: { | ||
true: { | ||
border: '2.5px solid transparent', | ||
backgroundImage: ` | ||
radial-gradient(circle at bottom, #ffffff 100%, rgba(255, 255, 255, 0) 100%), | ||
linear-gradient(to top,rgb(234, 255, 175) 30%,rgb(174, 225, 32) 100%) | ||
`, | ||
backgroundOrigin: 'border-box', | ||
backgroundClip: 'content-box, border-box', | ||
transition: 'background-image 0.4s ease, border-color 0.4s ease', | ||
}, | ||
Comment on lines
+74
to
+84
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. InfoButton의 border 그라데이션 스타일을 위한 코드입니다 border 토큰화 해야할까요?? 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. 이건 채은이랑 마음 잘맞음 이슈 때문에 기존 파일 새롭게 그냥 추가해서 올린겁니다~ 기존 develop 에도 존재하는 파일을 그냥 복붙해서 올린거에요~ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
import { useState, useMemo } from 'react'; | ||
|
||
interface Festival { | ||
festivalId: number; | ||
title: string; | ||
logoUrl: string; | ||
festivalDates: Array<{ | ||
festivalDateId: number; | ||
festivalAt: string; | ||
}>; | ||
} | ||
|
||
const useButtonSelection = (festivals: Festival[]) => { | ||
// 초기 선택된 축제 ID 설정 | ||
const [clickedFestivalId, setClickedFestivalId] = useState<number | null>( | ||
festivals.length > 0 ? festivals[0].festivalId : null, | ||
); | ||
|
||
// festival ID와 dates를 매핑하는 Map 생성 | ||
const festivalDatesMap = useMemo( | ||
() => | ||
new Map( | ||
festivals.map((festival) => [ | ||
festival.festivalId, | ||
festival.festivalDates, | ||
]), | ||
), | ||
[festivals], | ||
); | ||
|
||
// 축제 선택/해제 핸들러 | ||
const handleFestivalClick = (festivalId: number) => { | ||
setClickedFestivalId((prevId) => | ||
prevId === festivalId ? null : festivalId, | ||
); | ||
}; | ||
|
||
// 선택된 축제의 날짜들 가져오기 | ||
const selectedFestivalDates = clickedFestivalId | ||
? (festivalDatesMap.get(clickedFestivalId) ?? []) | ||
: []; | ||
|
||
return { | ||
clickedFestivalId, | ||
selectedFestivalDates, | ||
handleFestivalClick, | ||
}; | ||
}; | ||
|
||
export default useButtonSelection; |
Original file line number | Diff line number | Diff line change | ||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,87 @@ | ||||||||||||||||
import { useState, useMemo } from 'react'; | ||||||||||||||||
import { WEEKDAYS } from '@shared/constants/day'; | ||||||||||||||||
|
||||||||||||||||
const YEAR_MESSAGE = { | ||||||||||||||||
ERR_MESSAGE: '', | ||||||||||||||||
}; | ||||||||||||||||
|
||||||||||||||||
const weekData = WEEKDAYS; | ||||||||||||||||
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. 이거 @bongtta 랑 완전히 겹치는 상수 데이터를 쓰고있어서 shared 로 옮기고, 채은님이 사용하시던 페이지에서도 제가 수정완료 했습니다! |
||||||||||||||||
|
||||||||||||||||
export const useFormattedYear = (date: string | null) => { | ||||||||||||||||
if (!date) { | ||||||||||||||||
return ``; | ||||||||||||||||
} | ||||||||||||||||
|
||||||||||||||||
const [year, month] = date.split('.') || []; | ||||||||||||||||
if (!year || !month) { | ||||||||||||||||
return `${YEAR_MESSAGE.ERR_MESSAGE}`; | ||||||||||||||||
} | ||||||||||||||||
Comment on lines
+16
to
+18
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. 에러 메세지에 대한 기준이 아직 명확하지 않으니 우선은 ' '로 구현해주신대로 유지합시다! |
||||||||||||||||
|
||||||||||||||||
return `${year}년 ${parseInt(month, 10)}월`; | ||||||||||||||||
}; | ||||||||||||||||
|
||||||||||||||||
/** | ||||||||||||||||
* 특정 날짜 기준으로 일주일 동안의 날짜(num) 값을 계산하고 반환함 | ||||||||||||||||
*/ | ||||||||||||||||
export const useFormattedWeek = (date: string | null) => { | ||||||||||||||||
return useMemo(() => { | ||||||||||||||||
if (!date) return { weekDays: [] }; | ||||||||||||||||
|
||||||||||||||||
const [year, month, day] = date | ||||||||||||||||
.split('.') | ||||||||||||||||
.map((part) => parseInt(part, 10)); | ||||||||||||||||
if (isNaN(year) || isNaN(month) || isNaN(day)) return { weekDays: [] }; | ||||||||||||||||
|
||||||||||||||||
const baseDate = new Date(year, month - 1, day); | ||||||||||||||||
const weekDays = Array.from({ length: 7 }, (_, i) => { | ||||||||||||||||
const currentDate = new Date(baseDate); | ||||||||||||||||
currentDate.setDate(baseDate.getDate() + i); | ||||||||||||||||
return { | ||||||||||||||||
date: currentDate.getDate(), | ||||||||||||||||
dayKo: weekData[currentDate.getDay()], | ||||||||||||||||
}; | ||||||||||||||||
}); | ||||||||||||||||
|
||||||||||||||||
return { weekDays }; | ||||||||||||||||
}, [date]); | ||||||||||||||||
}; | ||||||||||||||||
|
||||||||||||||||
export const useDayNumSelection = () => { | ||||||||||||||||
const [selectedDayNumeId, setSelectedDateId] = useState<number | null>(null); | ||||||||||||||||
|
||||||||||||||||
const handleDayNumClick = (festivalDateId: number) => { | ||||||||||||||||
setSelectedDateId((prev) => | ||||||||||||||||
prev === festivalDateId ? null : festivalDateId, | ||||||||||||||||
); | ||||||||||||||||
}; | ||||||||||||||||
|
||||||||||||||||
return { | ||||||||||||||||
selectedDayNumeId, | ||||||||||||||||
handleDayNumClick, | ||||||||||||||||
}; | ||||||||||||||||
}; | ||||||||||||||||
|
||||||||||||||||
// festivalDateMap 생성 함수 | ||||||||||||||||
export const createFestivalDateMap = ( | ||||||||||||||||
festivalDates: { festivalDateId: number }[], | ||||||||||||||||
) => { | ||||||||||||||||
return new Map( | ||||||||||||||||
festivalDates.map((festival, index) => [ | ||||||||||||||||
index + 1, | ||||||||||||||||
festival.festivalDateId, | ||||||||||||||||
]), | ||||||||||||||||
); | ||||||||||||||||
}; | ||||||||||||||||
// isSelected 처리 함수 | ||||||||||||||||
export const checkFestivalDateStatus = ( | ||||||||||||||||
Comment on lines
+74
to
+76
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.
Suggested change
|
||||||||||||||||
festivalDateMap: Map<number, number>, | ||||||||||||||||
id: number, | ||||||||||||||||
selectedDateId: number | null, | ||||||||||||||||
) => { | ||||||||||||||||
const festivalDateId = festivalDateMap.get(id + 1); | ||||||||||||||||
const isSelected: boolean | undefined = | ||||||||||||||||
festivalDateId && selectedDateId === festivalDateId ? true : undefined; | ||||||||||||||||
const hasFestivalDate = festivalDateId !== undefined; | ||||||||||||||||
|
||||||||||||||||
return { festivalDateId, isSelected, hasFestivalDate }; | ||||||||||||||||
}; |
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.
dateItems 배열은 기존의 날짜 정보(date, dayKo)에 **선택 여부(isSelected)**와 축제일 여부(hasFestivalDate) 같은 추가적인 정보를 포함하는 객체인데
네이밍 뭐가 적절할까요..?
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.
dateDetails
이 적절해보여요