Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions src/pages/home/components/calendar-section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ interface CalendarSectionProps {
onDateChange: (date: Date) => void;
baseWeekDate: Date;
onOpenBottomSheet: () => void;
onWeekChange: (direction: 'prev' | 'next') => void;
}

const CalendarSection = ({
Expand All @@ -19,6 +20,7 @@ const CalendarSection = ({
onDateChange,
baseWeekDate,
onOpenBottomSheet,
onWeekChange,
}: CalendarSectionProps) => {
const handleTabChange = (type: TabType) => {
onTabChange(type);
Expand All @@ -33,6 +35,7 @@ const CalendarSection = ({
onChange={(date) => {
onDateChange(date);
}}
onWeekChange={onWeekChange}
/>
<section className="mt-[2.5rem] flex justify-between">
<TabList colorMode="home" activeType={activeType} onTabChange={handleTabChange} />
Expand Down
5 changes: 5 additions & 0 deletions src/pages/home/home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ const Home = () => {
setBaseWeekDate(date);
};

const handleWeekChange = (direction: 'prev' | 'next') => {
setBaseWeekDate((prev) => addDays(prev, direction === 'next' ? 7 : -7));
};

const handleComplete = () => {
gaEvent('condition_set_start');
navigate(ROUTES.ONBOARDING);
Expand All @@ -71,6 +75,7 @@ const Home = () => {
onDateChange={setSelectedDate}
baseWeekDate={baseWeekDate}
onOpenBottomSheet={() => setIsCalendarBottomSheetOpen(true)}
onWeekChange={handleWeekChange}
/>
<MatchListSection
activeType={activeType}
Expand Down
125 changes: 92 additions & 33 deletions src/shared/components/calendar/week-calendar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { DATE_SELECT_TOAST_MESSAGE } from '@constants/error-toast';
import { cn } from '@libs/cn';
import { addDays, format, isSameDay } from 'date-fns';
import { ko } from 'date-fns/locale';
import { AnimatePresence, motion } from 'framer-motion';
import { useRef } from 'react';
import { calendarDayVariants } from '@/shared/components/calendar/styles/calendar-day-variants';
import { showErrorToast } from '@/shared/utils/show-error-toast';

Expand All @@ -11,44 +13,101 @@ interface WeekCalendarProps {
baseDate: Date;
value: Date;
onChange: (date: Date) => void;
onWeekChange?: (direction: 'prev' | 'next') => void;
}

const WeekCalendar = ({ entryDate, baseDate, value, onChange }: WeekCalendarProps) => {
const SWIPE_THRESHOLD = 50;

const slideVariants = {
enter: (direction: number) => ({
x: direction > 0 ? '100%' : '-100%',
opacity: 0,
}),
center: { x: 0, opacity: 1 },
exit: (direction: number) => ({
x: direction > 0 ? '-100%' : '100%',
opacity: 0,
}),
};

const WeekCalendar = ({
entryDate,
baseDate,
value,
onChange,
onWeekChange,
}: WeekCalendarProps) => {
const days = getWeekDays(baseDate);
const directionRef = useRef(0);
const touchStartXRef = useRef(0);

const handleTouchStart = (e: React.TouchEvent) => {
touchStartXRef.current = e.touches[0].clientX;
};

const handleTouchEnd = (e: React.TouchEvent) => {
const diff = touchStartXRef.current - e.changedTouches[0].clientX;
if (Math.abs(diff) < SWIPE_THRESHOLD || !onWeekChange) return;

if (diff > 0) {
directionRef.current = 1;
onWeekChange('next');
} else {
directionRef.current = -1;
onWeekChange('prev');
}
};

return (
<div className="w-full flex-row-between gap-[1.2rem]">
{days.map((day) => {
const isSelected = isSameDay(day, value);

const dateColor = 'text-gray-white';
const dayColor = isSelected ? 'text-main-400' : 'text-gray-500';

const handleClick = (day: Date) => {
const isBlocked = day <= addDays(entryDate, 1);

if (isBlocked) {
showErrorToast(DATE_SELECT_TOAST_MESSAGE, '7.6rem');
return;
}
onChange(day);
};

return (
<button
key={day.toISOString()}
type="button"
onClick={() => handleClick(day)}
className={calendarDayVariants({
weekSelected: isSelected,
size: 'week',
})}
>
<p className={cn('body_16_m', dateColor)}>{format(day, 'd')}</p>
<p className={cn('cap_14_m', dayColor)}>{format(day, 'E', { locale: ko })}</p>
</button>
);
})}
<div
className="w-full overflow-hidden"
onTouchStart={handleTouchStart}
onTouchEnd={handleTouchEnd}
>
<AnimatePresence mode="popLayout" initial={false} custom={directionRef.current}>
<motion.div
key={baseDate.toISOString()}
custom={directionRef.current}
variants={slideVariants}
initial="enter"
animate="center"
exit="exit"
transition={{ duration: 0.25, ease: 'easeInOut' }}
className="w-full flex-row-between gap-[1.2rem]"
>
{days.map((day) => {
const isSelected = isSameDay(day, value);

const dateColor = 'text-gray-white';
const dayColor = isSelected ? 'text-main-400' : 'text-gray-500';

const handleClick = (day: Date) => {
const isBlocked = day <= addDays(entryDate, 1);

if (isBlocked) {
showErrorToast(DATE_SELECT_TOAST_MESSAGE, '7.6rem');
return;
}
onChange(day);
};

return (
<button
key={day.toISOString()}
type="button"
onClick={() => handleClick(day)}
className={calendarDayVariants({
weekSelected: isSelected,
size: 'week',
})}
>
<p className={cn('body_16_m', dateColor)}>{format(day, 'd')}</p>
<p className={cn('cap_14_m', dayColor)}>{format(day, 'E', { locale: ko })}</p>
</button>
);
})}
</motion.div>
</AnimatePresence>
</div>
);
};
Expand Down
Loading