183 lines
5.1 KiB
TypeScript
183 lines
5.1 KiB
TypeScript
import {
|
|
Button as AriaButton,
|
|
Calendar as AriaCalendar,
|
|
CalendarGridHeader as AriaCalendarGridHeader,
|
|
CalendarCell,
|
|
CalendarGrid,
|
|
CalendarGridBody,
|
|
CalendarHeaderCell,
|
|
Heading,
|
|
} from 'react-aria-components';
|
|
|
|
import { getLocalTimeZone, today } from '@internationalized/date';
|
|
import { tv } from 'tailwind-variants';
|
|
|
|
import { ChevronLeftIcon, ChevronRightIcon } from '@/components/icons';
|
|
|
|
import { calendarDateToDate, dateToCalendarDate } from './utils';
|
|
|
|
const cellStyles = tv({
|
|
base: 'flex h-[30px] w-[30px] cursor-default items-center justify-center rounded-full text-xs outline-none',
|
|
variants: {
|
|
isSelected: {
|
|
true: 'bg-primary font-bold text-white',
|
|
false: '',
|
|
},
|
|
isDisabled: {
|
|
true: 'cursor-default text-kc-gray-be',
|
|
},
|
|
isUnavailable: {
|
|
true: 'cursor-default text-kc-gray-be line-through',
|
|
},
|
|
isOutsideMonth: {
|
|
true: 'text-kc-gray-be',
|
|
},
|
|
isSunday: {
|
|
true: 'text-[#e48686]',
|
|
},
|
|
isSaturday: {
|
|
true: 'text-[#7b8cc8]',
|
|
},
|
|
isToday: {
|
|
true: 'font-bold text-primary',
|
|
},
|
|
isHovered: {
|
|
true: 'bg-primary-tertiary01',
|
|
},
|
|
isFocusVisible: {
|
|
true: 'ring-2 ring-offset-2 ring-primary',
|
|
},
|
|
},
|
|
compoundVariants: [
|
|
{
|
|
isSelected: true,
|
|
isSunday: true,
|
|
className: 'text-white',
|
|
},
|
|
{
|
|
isSelected: true,
|
|
isSaturday: true,
|
|
className: 'text-white',
|
|
},
|
|
{
|
|
isSelected: true,
|
|
isToday: true,
|
|
className: 'text-white',
|
|
},
|
|
{
|
|
isSelected: true,
|
|
isHovered: true,
|
|
className: 'bg-primary',
|
|
},
|
|
],
|
|
});
|
|
|
|
export interface CalendarProps {
|
|
value?: Date | null;
|
|
defaultValue?: Date | null;
|
|
onChange?: (date: Date | null) => void;
|
|
maxValue?: Date | null;
|
|
minValue?: Date | null;
|
|
isDisabled?: boolean;
|
|
className?: string;
|
|
}
|
|
|
|
export function Calendar({ value, defaultValue, onChange, maxValue, minValue, isDisabled, className }: CalendarProps) {
|
|
const todayDate = today(getLocalTimeZone());
|
|
|
|
return (
|
|
<AriaCalendar
|
|
aria-label="날짜 선택"
|
|
value={value !== undefined ? dateToCalendarDate(value ?? null) : undefined}
|
|
defaultValue={defaultValue !== undefined ? dateToCalendarDate(defaultValue ?? null) : undefined}
|
|
onChange={(val) => onChange?.(calendarDateToDate(val))}
|
|
maxValue={maxValue !== undefined ? dateToCalendarDate(maxValue) : undefined}
|
|
minValue={minValue !== undefined ? dateToCalendarDate(minValue) : undefined}
|
|
isDisabled={isDisabled}
|
|
firstDayOfWeek="sun"
|
|
className={className}
|
|
>
|
|
<CalendarHeader />
|
|
<CalendarGrid className="border-spacing-0">
|
|
<CalendarGridHeader />
|
|
<CalendarGridBody>
|
|
{(date) => {
|
|
const dayOfWeek = date.toDate('UTC').getDay();
|
|
const isSunday = dayOfWeek === 0;
|
|
const isSaturday = dayOfWeek === 6;
|
|
const isToday = date.compare(todayDate) === 0;
|
|
|
|
return (
|
|
<CalendarCell
|
|
date={date}
|
|
className={({
|
|
isSelected,
|
|
isDisabled: isCellDisabled,
|
|
isUnavailable,
|
|
isOutsideMonth,
|
|
isHovered,
|
|
isFocusVisible,
|
|
}) =>
|
|
cellStyles({
|
|
isSelected,
|
|
isDisabled: isCellDisabled,
|
|
isUnavailable,
|
|
isOutsideMonth,
|
|
isSunday: !isSelected && !isDisabled && !isOutsideMonth ? isSunday : false,
|
|
isSaturday: !isSelected && !isDisabled && !isOutsideMonth ? isSaturday : false,
|
|
isToday: !isSelected ? isToday : false,
|
|
isHovered: !isSelected ? isHovered : false,
|
|
isFocusVisible,
|
|
})
|
|
}
|
|
/>
|
|
);
|
|
}}
|
|
</CalendarGridBody>
|
|
</CalendarGrid>
|
|
</AriaCalendar>
|
|
);
|
|
}
|
|
|
|
const navButton = tv({
|
|
base: 'flex h-7 w-7 items-center justify-center rounded-sm border-none bg-transparent outline-none',
|
|
variants: {
|
|
isDisabled: {
|
|
true: 'cursor-default text-kc-gray-be',
|
|
false: 'cursor-pointer text-kc-black-34 hover:bg-kc-gray-eb',
|
|
},
|
|
isFocusVisible: {
|
|
true: 'ring-2 ring-offset-2 ring-primary',
|
|
},
|
|
},
|
|
defaultVariants: {
|
|
isDisabled: false,
|
|
},
|
|
});
|
|
|
|
export function CalendarHeader() {
|
|
return (
|
|
<header className="flex items-center gap-1 pb-3">
|
|
<AriaButton slot="previous" className={(renderProps) => navButton(renderProps)}>
|
|
<ChevronLeftIcon className="h-4 w-4" />
|
|
</AriaButton>
|
|
<Heading className="mx-2 flex-1 text-center text-sm font-semibold text-kc-black-34" />
|
|
<AriaButton slot="next" className={(renderProps) => navButton(renderProps)}>
|
|
<ChevronRightIcon className="h-4 w-4" />
|
|
</AriaButton>
|
|
</header>
|
|
);
|
|
}
|
|
|
|
export function CalendarGridHeader() {
|
|
return (
|
|
<AriaCalendarGridHeader>
|
|
{(day) => (
|
|
<CalendarHeaderCell className="h-[30px] w-[30px] text-xs font-semibold text-kc-gray-99 first:text-[#e48686] last:text-[#7b8cc8]">
|
|
{day}
|
|
</CalendarHeaderCell>
|
|
)}
|
|
</AriaCalendarGridHeader>
|
|
);
|
|
}
|