import { RangeCalendar as AriaRangeCalendar, CalendarCell, CalendarGrid, CalendarGridBody, type DateValue, } from 'react-aria-components'; import { getLocalTimeZone, today } from '@internationalized/date'; import { tv } from 'tailwind-variants'; import { CalendarGridHeader, CalendarHeader } from './Calendar'; import { calendarDateToDate, dateToCalendarDate } from './utils'; const rangeCell = tv({ base: 'flex h-full w-full items-center justify-center rounded-full text-xs', variants: { selectionState: { none: '', middle: '', cap: 'bg-primary font-bold text-white', }, isDisabled: { true: 'text-kc-gray-be', }, isOutsideMonth: { true: 'text-kc-gray-be', }, isSunday: { true: 'text-[#e48686]', }, isSaturday: { true: 'text-[#7b8cc8]', }, isToday: { true: 'font-bold text-primary', }, isHovered: { true: '', }, isFocusVisible: { true: 'ring-2 ring-offset-2 ring-primary', }, }, compoundVariants: [ { selectionState: 'cap', isSunday: true, className: 'text-white', }, { selectionState: 'cap', isSaturday: true, className: 'text-white', }, { selectionState: 'cap', isToday: true, className: 'text-white', }, { selectionState: 'none', isHovered: true, className: 'bg-primary-tertiary01 rounded-full', }, { selectionState: 'middle', isHovered: true, className: 'bg-primary-tertiary01', }, ], }); export interface RangeCalendarProps { value?: { start: Date; end: Date } | null; defaultValue?: { start: Date; end: Date } | null; onChange?: (value: { start: Date; end: Date } | null) => void; maxValue?: Date | null; minValue?: Date | null; isDisabled?: boolean; className?: string; } export function RangeCalendar({ value, defaultValue, onChange, maxValue, minValue, isDisabled, className, }: RangeCalendarProps) { const todayDate = today(getLocalTimeZone()); const ariaValue = value !== undefined ? value ? { start: dateToCalendarDate(value.start)!, end: dateToCalendarDate(value.end)! } : null : undefined; const ariaDefaultValue = defaultValue !== undefined ? defaultValue ? { start: dateToCalendarDate(defaultValue.start)!, end: dateToCalendarDate(defaultValue.end)! } : null : undefined; return ( aria-label="날짜 범위 선택" value={ariaValue} defaultValue={ariaDefaultValue} onChange={(range) => { if (!range) { onChange?.(null); } else { onChange?.({ start: calendarDateToDate(range.start)!, end: calendarDateToDate(range.end)!, }); } }} maxValue={maxValue !== undefined ? dateToCalendarDate(maxValue) : undefined} minValue={minValue !== undefined ? dateToCalendarDate(minValue) : undefined} isDisabled={isDisabled} firstDayOfWeek="sun" className={className} > {(date) => { const dayOfWeek = date.toDate('UTC').getDay(); const isSunday = dayOfWeek === 0; const isSaturday = dayOfWeek === 6; const isToday = date.compare(todayDate) === 0; return ( { const classes = ['group h-[30px] w-[30px] cursor-default text-sm outline-none']; if (isSelected) { classes.push('bg-primary/10'); if (isSelectionStart) classes.push('rounded-s-full'); if (isSelectionEnd) classes.push('rounded-e-full'); } return classes.join(' '); }} > {({ formattedDate, isSelected, isSelectionStart, isSelectionEnd, isDisabled: isCellDisabled, isOutsideMonth, isHovered, isFocusVisible, }) => { const selectionState = isSelected && (isSelectionStart || isSelectionEnd) ? ('cap' as const) : isSelected ? ('middle' as const) : ('none' as const); const showDayColor = selectionState !== 'cap' && !isCellDisabled && !isOutsideMonth; return ( {formattedDate} ); }} ); }} ); }