Skip to content

Commit

Permalink
feat: add DateRangePicker Component
Browse files Browse the repository at this point in the history
  • Loading branch information
khj0426 committed Jun 14, 2024
1 parent 17c4ca7 commit 5468f31
Show file tree
Hide file tree
Showing 6 changed files with 169 additions and 12 deletions.
22 changes: 15 additions & 7 deletions src/Component/Common/Calendar/Calendar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@ import MonthNavigation from '@/Component/Common/Calendar/MonthNavigation';
import Flex from '@/Component/Common/Flex/Flex';
import useCalendar from '@/hooks/useCalendar';

const Calendar = () => {
const Calendar = ({
onSelectDate,
isDateInRange,
}: {
isDateInRange?: (_newDate: Date) => boolean;
onSelectDate?: (_newDate: Date) => void;
}) => {
//가능적인 로직은 모두 캘린더 훅으로 분리하고
const {
currentDate,
Expand All @@ -22,18 +28,20 @@ const Calendar = () => {
prevMonth={prevMonth}
date={currentDate}
setCurrentDate={(_newYear: string) => {
setCurrentDate(
new Date(
Number(_newYear),
currentDate.getMonth(),
currentDate.getDate()
)
const selectDate = new Date(
Number(_newYear),
currentDate.getMonth(),
currentDate.getDate()
);
setCurrentDate(selectDate);
onSelectDate?.(selectDate);
}}
/>
<DateGrid
currentMonthDates={currentMonthDates()}
prevMonthDates={prevMonthDates()}
onSelectDate={onSelectDate}
isDateWithRange={isDateInRange}
/>
</Flex>
);
Expand Down
24 changes: 19 additions & 5 deletions src/Component/Common/Calendar/DateGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@ import { useState } from 'react';
import { getDate, isSaturday, isSunday, isToday } from 'date-fns';
import styled from 'styled-components';

import { buttonTheme } from '@/style/theme/button';

interface DateGridProps {
prevMonthDates: Date[];
currentMonthDates: Date[];
onSelectDate?: (_newDate: Date) => void;
isDateWithRange?: (_newDate: Date) => boolean;
}

const DAYS = ['일', '월', '화', '수', '목', '금', '토'];
Expand All @@ -15,6 +18,7 @@ const DateGrid = ({
prevMonthDates,
currentMonthDates,
onSelectDate,
isDateWithRange,
}: DateGridProps) => {
const [currentCell, setCurrentCell] = useState<null | Date>(null);
return (
Expand All @@ -23,21 +27,27 @@ const DateGrid = ({
<DayHeader key={day}>{day}</DayHeader>
))}
{prevMonthDates.map((prevDate) => (
<DateCell key={prevDate.toString()} isPrevMonth>
<DateCell
key={prevDate.toString()}
isPrevMonth
isSelected={currentCell === prevDate || isDateWithRange?.(prevDate)}
>
{getDate(prevDate)}
</DateCell>
))}
{currentMonthDates.map((currentDate) => (
<DateCell
onClick={() => {
onSelectDate && onSelectDate(currentDate);
onSelectDate?.(currentDate);
setCurrentCell(currentDate);
}}
key={currentDate.toString()}
isToday={isToday(currentDate)}
isSaturDay={isSaturday(currentDate)}
isWeekend={isSaturday(currentDate) || isSunday(currentDate)}
isSelected={currentCell === currentDate}
isSelected={
currentCell === currentDate || isDateWithRange?.(currentDate)
}
isSunday={isSunday(currentDate)}
>
{getDate(currentDate)}
Expand Down Expand Up @@ -75,7 +85,12 @@ const DateCell = styled.div<DateCellProps>`
padding: 10px;
text-align: center;
cursor: pointer;
background-color: ${(props) => (props.isPrevMonth ? '#e0e0e0' : 'none')};
background-color: ${(props) =>
props.isSelected
? buttonTheme.variant_backgroundColor.light_blue
: props.isPrevMonth
? '#e0e0e0'
: 'none'};
color: ${(props) =>
props.isToday
? 'red'
Expand All @@ -85,5 +100,4 @@ const DateCell = styled.div<DateCellProps>`
? 'red'
: 'black'};
font-weight: ${(props) => (props.isToday ? 'bold' : 'normal')};
box-shadow: ${(props) => (props.isSelected ? '0 0 0 1px black' : '')};
`;
17 changes: 17 additions & 0 deletions src/Component/Common/DateRangePicker/DateRangePicker.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type { Meta, StoryObj } from '@storybook/react';

import DateRangePicker from '@/Component/Common/DateRangePicker/DateRangePicker';

const meta: Meta<typeof DateRangePicker> = {
title: '날짜 구간 선택 컴포넌트',
component: DateRangePicker,
tags: ['autodocs'],
parameters: {
layout: 'fullscreen',
},
};

export default meta;
type Story = StoryObj<typeof DateRangePicker>;

export const BaseDateRangePickerCalendar: Story = {};
38 changes: 38 additions & 0 deletions src/Component/Common/DateRangePicker/DateRangePicker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { useEffect } from 'react';

import Calendar from '@/Component/Common/Calendar/Calendar';
import Flex from '@/Component/Common/Flex/Flex';
import { ToastContainer, ToastManager } from '@/Component/Common/Toast';
import useDateRangeCalendar from '@/hooks/useDateRangeCalendar';
interface DateRangePickerProps {
readonly isFutureDaysRestricted?: boolean;
}

const DateRangePicker = ({
isFutureDaysRestricted = true,
}: DateRangePickerProps) => {
const { selectCurrentDate, selectEndDate, hasError, isDateWithRange } =
useDateRangeCalendar({ isFutureDaysRestricted });

useEffect(() => {
if (hasError) {
ToastManager.error('올바른 날짜를 선택해주세요!');
}
}, [hasError]);

return (
<Flex flexWrap="wrap">
<Calendar
onSelectDate={(newDate) => selectCurrentDate(newDate)}
isDateInRange={isDateWithRange}
/>
<Calendar
onSelectDate={(newDate) => selectEndDate(newDate)}
isDateInRange={isDateWithRange}
/>
<ToastContainer enterTimeout={500} leaveTimeout={1000} />
</Flex>
);
};

export default DateRangePicker;
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,14 @@ export const NextNavigationButton: Story = {
args: {
type: 'next',
children: '반갑습니다',
link: 'https://github.com/khj0426/HJ_Devlog',
},
};

export const PrevNavigationButton: Story = {
args: {
type: 'prev',
children: '반갑습니다.',
link: 'https://github.com/khj0426/HJ_Devlog',
},
};
78 changes: 78 additions & 0 deletions src/hooks/useDateRangeCalendar.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { useState } from 'react';

import { compareAsc, isWithinInterval } from 'date-fns';

interface UseDateRangeCalenDarProps {
onStartDateSelect?: () => void;
onEndDateSelect?: () => void;
isFutureDaysRestricted?: boolean;
}

export default function useDateRangeCalendar({
onStartDateSelect,
onEndDateSelect,
isFutureDaysRestricted,
}: UseDateRangeCalenDarProps) {
const currentDate = new Date();
const [hasError, setError] = useState(false);
const [startDate, setStartDate] = useState<null | Date>(null);
const [endDate, setEndDate] = useState<null | Date>(null);

const clearBothStartDateEndDate = () => {
setStartDate(null);
setEndDate(null);
};

const selectCurrentDate = (_newDate: Date) => {
if (startDate) {
clearBothStartDateEndDate();
return;
}
if (onStartDateSelect) {
onStartDateSelect();
}
setStartDate(_newDate);
setError(false);
};

const selectEndDate = (_newEndDate: Date) => {
if (endDate) {
setEndDate(null);
return;
}
if (compareAsc(_newEndDate, currentDate) !== -1) {
clearBothStartDateEndDate();
setError(true);

return;
}

if (onEndDateSelect) {
onEndDateSelect();
}

if (startDate) {
setEndDate(_newEndDate);
setError(false);
}
};

const isDateWithRange = (date: Date) => {
if (startDate && endDate && compareAsc(startDate, endDate) === -1)
return isWithinInterval(date, {
start: startDate,
end: endDate,
});

return false;
};

return {
isDateWithRange,
selectCurrentDate,
selectEndDate,
endDate,
hasError,
startDate,
};
}

0 comments on commit 5468f31

Please sign in to comment.