-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* chore: global button color 스타일 삭제 * feat(useCalendarInfo): 날짜 정보를 알려주는 커스텀 훅 구현 - year, month를 상태로 관리하여 값이 변경될 때마다 날짜 정보를 계산해서 반환 - 달(Month)를 이동할 수 있는 함수 구현 - 12월에서 다음을 클릭할 경우 해(Year)가 변경되도록 구현 * feat(Calendar): 달력 컴포넌트 구현 - 오늘의 경우 어떻게 보여줘야할 것인지 스타일 논의 필요 - 일요일이 아닌, 월요일부터 테이블을 채울 수 있도록 구현 * chore: 스토리북 preview 설정 수정 - theme. global 스타일이 적용되도록 설정 * test(Calendar): 달력 컴포넌트 UI 테스트 구현 * chore(useTimePick): 함수 시그니쳐를 표현식으로 통일 * chore: px단위를 rem단위로 수정 * refactor: 빈 칸으로 표시되어야 하는 날짜인 경우, div 태그로 감싸도록 수정 - cursor : pointer 속성이 적용되지 않기 위함 * refactor: getDayInfo 함수의 인자를 객체 형태로 변경 * chore(useCalendarInfo): 의미있는 변수명을 사용하도록 수정
- Loading branch information
Showing
9 changed files
with
340 additions
and
17 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import { Global, ThemeProvider } from '@emotion/react'; | ||
import type { Preview } from '@storybook/react'; | ||
import React from 'react'; | ||
|
||
import globalStyles from '../src/styles/global'; | ||
import theme from '../src/styles/theme'; | ||
|
||
const preview: Preview = { | ||
parameters: { | ||
controls: { | ||
matchers: { | ||
color: /(background|color)$/i, | ||
date: /Date$/i, | ||
}, | ||
}, | ||
}, | ||
}; | ||
|
||
export default preview; | ||
|
||
export const decorators = [ | ||
(Story) => ( | ||
<ThemeProvider theme={theme}> | ||
<Global styles={globalStyles} /> | ||
<Story /> | ||
</ThemeProvider> | ||
), | ||
]; |
64 changes: 64 additions & 0 deletions
64
frontend/src/components/_common/Calendar/Calendar.stories.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
import type { Meta, StoryObj } from '@storybook/react'; | ||
import { useState } from 'react'; | ||
|
||
import Button from './index'; | ||
import Calendar from './index'; | ||
|
||
const meta = { | ||
title: 'Components/Calendar', | ||
component: Calendar, | ||
tags: ['autodocs'], | ||
|
||
parameters: { | ||
layout: 'centered', | ||
}, | ||
argTypes: { | ||
hasDate: { | ||
description: '선택된 날짜들', | ||
type: 'function', | ||
control: { | ||
disable: true, | ||
}, | ||
}, | ||
onDateClick: { | ||
description: '선택된 날짜 리스트에 특정 날짜를 추가하거나 제거할 수 있는 함수', | ||
}, | ||
}, | ||
decorators: [ | ||
(Story, context) => { | ||
const [selectedDates, setSelectedDates] = useState<string[]>([]); | ||
|
||
const hasDate = (date: string) => selectedDates.includes(date); | ||
|
||
const handleDateClick = (date: string) => { | ||
setSelectedDates((prevDates) => | ||
hasDate(date) ? prevDates.filter((d) => d !== date) : [...prevDates, date], | ||
); | ||
}; | ||
|
||
return ( | ||
<Story | ||
args={{ | ||
...context.args, | ||
hasDate, | ||
onDateClick: handleDateClick, | ||
}} | ||
/> | ||
); | ||
}, | ||
], | ||
} satisfies Meta<typeof Button>; | ||
|
||
export default meta; | ||
|
||
type Story = StoryObj<typeof meta>; | ||
|
||
export const Playground: Story = { | ||
args: { | ||
hasDate: () => false, | ||
onDateClick: () => {}, | ||
}, | ||
render: (args) => { | ||
return <Calendar hasDate={args.hasDate} onDateClick={args.onDateClick} />; | ||
}, | ||
}; |
86 changes: 86 additions & 0 deletions
86
frontend/src/components/_common/Calendar/Calendar.styles.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
import { css } from '@emotion/react'; | ||
|
||
export const s_calendarContainer = css` | ||
display: flex; | ||
flex-direction: column; | ||
align-items: center; | ||
`; | ||
|
||
export const s_calendarContent = css` | ||
display: grid; | ||
grid-auto-rows: 4rem; | ||
grid-template-columns: repeat(7, 1fr); | ||
width: 100%; | ||
`; | ||
|
||
export const s_dayOfWeekContainer = css` | ||
margin-bottom: 2rem; | ||
`; | ||
|
||
export const s_dayOfWeek = css` | ||
display: flex; | ||
align-items: center; | ||
justify-content: center; | ||
width: 100%; | ||
min-width: 4rem; | ||
height: 4rem; | ||
min-height: 4rem; | ||
font-size: 1.2rem; | ||
font-weight: normal; | ||
color: gray; | ||
`; | ||
|
||
export const s_monthHeader = css` | ||
display: flex; | ||
align-items: center; | ||
justify-content: space-between; | ||
width: 100%; | ||
margin-bottom: 2rem; | ||
padding: 0 1rem; | ||
font-size: 1.5rem; | ||
font-weight: bold; | ||
`; | ||
|
||
export const s_monthNavigation = css` | ||
cursor: pointer; | ||
`; | ||
|
||
export const s_daySlotButton = css` | ||
display: flex; | ||
align-items: center; | ||
justify-content: center; | ||
width: 100%; | ||
min-width: 3.6rem; | ||
height: 3.6rem; | ||
background-color: transparent; | ||
border: none; | ||
`; | ||
|
||
// TODO : 공휴일 색 변경 논의 필요(@해리) | ||
// TODO : s_todayDaySlot 추가 예정(@해리) | ||
export const s_daySlot = (isHoliday: boolean) => css` | ||
cursor: pointer; | ||
font-size: 1.5rem; | ||
color: ${isHoliday ? 'red' : '#000'}; | ||
`; | ||
|
||
export const s_selectedDaySlot = (isSelected: boolean) => css` | ||
display: flex; | ||
align-items: center; | ||
justify-content: center; | ||
width: 3.6rem; | ||
height: 3.6rem; | ||
${isSelected && | ||
css` | ||
background-color: #fcc; | ||
border-radius: 50%; | ||
`} | ||
`; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
import useCalendarInfo from '@hooks/useCalendarInfo/useCalendarInfo'; | ||
|
||
import { | ||
s_calendarContainer, | ||
s_calendarContent, | ||
s_dayOfWeek, | ||
s_dayOfWeekContainer, | ||
s_daySlot, | ||
s_daySlotButton, | ||
s_monthHeader, | ||
s_monthNavigation, | ||
s_selectedDaySlot, | ||
} from './Calendar.styles'; | ||
|
||
const DAY_OF_WEEK = ['월', '화', '수', '목', '금', '토', '일'] as const; | ||
|
||
// TODO : 선택된 날짜에 대한 강조 색을 외부에서 주입받을 수 있도록 props 수정 예정 (@해리) | ||
interface CalendarProps { | ||
hasDate: (date: string) => boolean; | ||
onDateClick: (date: string) => void; | ||
} | ||
|
||
export default function Calendar({ hasDate, onDateClick }: CalendarProps) { | ||
const { yearMonthInfo, handleGetDayInfo, handlePrevMonth, handleNextMonth } = useCalendarInfo(); | ||
const { year, month, daySlotCount } = yearMonthInfo; | ||
|
||
return ( | ||
<div css={s_calendarContainer} aria-label={`${year}년 ${month}월 달력`}> | ||
<header css={s_monthHeader}> | ||
{/* TODO : 캘린더 헤더 버튼 스타일 수정 예정(@해리) */} | ||
<button css={s_monthNavigation} onClick={handlePrevMonth}> | ||
{'<'} | ||
</button> | ||
<span> | ||
{year}년 {month}월 | ||
</span> | ||
<button css={s_monthNavigation} onClick={handleNextMonth}> | ||
{'>'} | ||
</button> | ||
</header> | ||
<section css={[s_calendarContent, s_dayOfWeekContainer]}> | ||
{DAY_OF_WEEK.map((day) => ( | ||
<div key={day} css={s_dayOfWeek}> | ||
{day} | ||
</div> | ||
))} | ||
</section> | ||
<section css={s_calendarContent}> | ||
{Array.from({ length: daySlotCount }, (_, index) => { | ||
// TODO : isToday 변수를 활용한 스타일 변경 논의 필요 (@해리) | ||
const { date, dateString, isDate, isToday, isHoliday } = handleGetDayInfo(index); | ||
const isSelectedDate = hasDate(dateString); | ||
|
||
return isDate ? ( | ||
<button key={dateString} onClick={() => onDateClick(dateString)} css={s_daySlotButton}> | ||
<span css={[s_daySlot(isHoliday), s_selectedDaySlot(isSelectedDate)]}>{date}</span> | ||
</button> | ||
) : ( | ||
<div key={dateString} css={s_daySlotButton}></div> | ||
); | ||
})} | ||
</section> | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import { useState } from 'react'; | ||
|
||
import { getDayInfo, getYearMonthInfo } from './useCalendarInfo.utils'; | ||
|
||
export default function useCalendarInfo() { | ||
// TODO : L7 ~ L9 getCurrentDate 함수로 추상화 예정(@해리) | ||
const currentDate = new Date(); | ||
const currentYear = currentDate.getFullYear(); | ||
const currentMonth = currentDate.getMonth() + 1; | ||
|
||
const [year, setYear] = useState(currentYear); | ||
const [month, setMonth] = useState(currentMonth); | ||
|
||
const { firstDayIndex, daySlotCount } = getYearMonthInfo(year, month); | ||
|
||
const handleGetDayInfo = (index: number) => { | ||
return getDayInfo({ year, month, firstDayIndex, index, currentDate }); | ||
}; | ||
|
||
const handlePrevMonth = () => { | ||
// TODO : isCurrentDate 함수로 추상화(@해리) | ||
if (year === currentYear && month === currentMonth) return; | ||
|
||
if (month === 1) { | ||
setYear(year - 1); | ||
setMonth(12); // TODO : 상수화(@해리) | ||
} else { | ||
setMonth(month - 1); | ||
} | ||
}; | ||
|
||
const handleNextMonth = () => { | ||
if (month === 12) { | ||
setYear(year + 1); | ||
setMonth(1); | ||
} else { | ||
setMonth(month + 1); | ||
} | ||
}; | ||
|
||
return { | ||
yearMonthInfo: { | ||
year, | ||
month, | ||
daySlotCount, | ||
}, | ||
handleGetDayInfo, | ||
handlePrevMonth, | ||
handleNextMonth, | ||
} as const; | ||
} |
44 changes: 44 additions & 0 deletions
44
frontend/src/hooks/useCalendarInfo/useCalendarInfo.utils.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
export function getYearMonthInfo(year: number, month: number) { | ||
const startDate = new Date(year, month - 1, 1); | ||
|
||
/* | ||
로직 설명(@hwinkr) | ||
- 월요일을 index 0으로 변경하기 위해서 나머지 연산자를 활용한다. | ||
- 자바스크립트 Date 객체는 기본적으로 일요일이 인덱스가 0인데, 모모 달력은 월요일을 인덱스를 0으로 만들어줘야 한다. | ||
- 따라서, 특정 달의 시작 날짜에 대한 인덱스에 6을 더해주고 7로 나눈 나머지를 사용하는 것으로 구현했다. | ||
*/ | ||
const firstDayIndex = (startDate.getDay() + 6) % 7; | ||
|
||
const lastDayOfMonthDate = new Date(year, month, 0); | ||
const lastDayNumber = lastDayOfMonthDate.getDate(); | ||
|
||
const daySlotCount = firstDayIndex + lastDayNumber; | ||
|
||
return { year, month, firstDayIndex, daySlotCount } as const; | ||
} | ||
|
||
export function getDayInfo({ | ||
year, | ||
month, | ||
firstDayIndex, | ||
index, | ||
currentDate, | ||
}: { | ||
year: number; | ||
month: number; | ||
firstDayIndex: number; | ||
index: number; | ||
currentDate: Date; | ||
}) { | ||
const date = index - firstDayIndex + 1; | ||
const isDate = index >= firstDayIndex; | ||
|
||
const dateString = `${year}-${month}-${date}`; | ||
const todayString = `${year}-${month}-${currentDate.getDate()}`; | ||
const isToday = dateString === todayString; | ||
// TODO : 일단은 일요일만 isHolday로 설정되도록 구현, 추후에 진짜 공휴일도 포함할 것인지 논의 필요, 찾아보니 라이브러리가 있더라구요.(@해리) | ||
// TODO : 매직넘버 의미있는 상수화 필요(@해리) | ||
const isHoliday = index % 7 === 6; | ||
|
||
return { date, dateString, isDate, isToday, isHoliday } as const; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -162,7 +162,6 @@ const globalStyles = css` | |
button { | ||
cursor: pointer; | ||
border: none; | ||
} | ||
`; | ||
|
||
|