Calendar 데모
DS Calendar는 월 이동·요일 헤더·날짜 그리드를 제공하는 공통 캘린더입니다. year, month, 월 이동 핸들러를 생략하면 로컬 타임존 기준 현재 달을 자체 관리합니다. 날짜 선택 상태는 getDayState 콜백으로 외부에서 주입합니다.
기본 calendar 컴포넌트
props 없이 `<Calendar />`만 렌더하면 로컬 타임존 기준 현재 달이 표시됩니다. 월 이동은 컴포넌트 내부에서 자체 관리되며, 기본적으로 이전·다음 달 이동에 제한이 없습니다.
<Calendar />import
named export로 가져옵니다.
import {
Calendar,
useCalendarView,
type CalendarDayState,
} from "@/shared/components/ds/Calendar";외부에서 월 이동 제어 (useCalendarView)
대부분의 경우 Calendar props만으로 충분합니다. 연·월 상태를 외부에서 읽거나 동기화해야 할 때만 `useCalendarView`를 사용합니다. (예: URL 쿼리와 연동, 열릴 때 특정 달로 강제 이동)
// 일반적인 사용 — Calendar가 월 이동을 자체 관리
<Calendar maxDate={new Date()} getDayState={getDayState} onDayClick={handleClick} />
// 고급 — 외부에서 연·월 상태를 직접 제어
const view = useCalendarView({ initialDate: new Date(2026, 2, 1) });
<Calendar
year={view.viewYear}
month={view.viewMonth}
onPrevMonth={view.handlePrevMonth}
onNextMonth={view.handleNextMonth}
/>단일 날짜 선택
`getDayState`로 `isRangeStart` / `isRangeEnd`를 같은 날짜에 설정하면 단일 선택 UI가 됩니다. 미래 날짜는 `isDisabled: true`로 비활성화하고, `maxDate` prop으로 미래 달 이동도 막을 수 있습니다.
const todayIso = getTodayIso();
function getDayState(isoDate: string): CalendarDayState {
return {
isDisabled: isoDate > todayIso,
isToday: isoDate === todayIso,
isRangeStart: isoDate === selectedDate,
isRangeEnd: isoDate === selectedDate,
};
}
<Calendar
maxDate={new Date()}
getDayState={getDayState}
onDayClick={handleDayClick}
/>선택된 날짜: 없음
기간 선택 (range)
`isRangeStart`, `isRangeEnd`, `isInRange`를 설정하면 Calendar가 시작~종료 사이 하늘색 밴드를 자동으로 연결합니다. 주 단위 줄바꿈 시 반원 라운딩도 Calendar 내부에서 처리합니다.
const todayIso = getTodayIso();
function getDayState(isoDate: string): CalendarDayState {
const isRangeComplete =
rangeStart !== undefined &&
rangeEnd !== undefined &&
rangeStart !== rangeEnd;
return {
isDisabled: isoDate > todayIso,
isToday: isoDate === todayIso,
isRangeStart: isoDate === rangeStart,
isRangeEnd: isoDate === rangeEnd,
isInRange:
isRangeComplete &&
isoDate > rangeStart &&
isoDate < rangeEnd,
};
}
<Calendar
maxDate={new Date()}
getDayState={getDayState}
onDayClick={handleRangeDayClick}
/>선택된 기간: 없음
기간 범위 Picker
`PeriodRangePicker`는 기간 설정 캘린더를 trigger/content 형태로 조합하는 bonbon 공통 UI입니다. 저장 API, 토스트, 검증 정책은 호출부에서 처리합니다.
import {
PeriodRangePicker,
type PeriodSettingCalendarValue,
} from "@/shared/components/bonbon";
const [open, setOpen] = useState(false);
const [value, setValue] = useState<PeriodSettingCalendarValue>({});
const [draftValue, setDraftValue] = useState<PeriodSettingCalendarValue>({});
<PeriodRangePicker.Root open={open} onOpenChange={setOpen}>
<PeriodRangePicker.Trigger asChild>
<button type="button">기간 설정</button>
</PeriodRangePicker.Trigger>
<PeriodRangePicker.Content
value={draftValue}
onValueChange={setDraftValue}
onConfirm={(nextValue) => {
setValue(nextValue);
setOpen(false);
}}
title="기간 설정"
confirmLabel="확인"
closeAriaLabel="기간 설정 닫기"
/>
</PeriodRangePicker.Root>설정된 기간: 없음
CalendarDayState 요약
각 날짜 셀의 표시 상태를 `getDayState(isoDate)` 반환값으로 제어합니다.
type CalendarDayState = {
isDisabled?: boolean; // 클릭 불가 (미래 날짜 등)
isToday?: boolean; // 오늘 날짜 강조
isRangeStart?: boolean; // 범위 시작일 (파란 원)
isRangeEnd?: boolean; // 범위 종료일 (파란 원)
isInRange?: boolean; // 시작~종료 사이 (하늘색 밴드는 Calendar가 자동 연결)
};관련 유틸
날짜 계산·표시는 `@/shared/lib/calendarUtils`를 함께 사용합니다. `getTodayIso`와 `toIsoDate`는 동일한 `YYYY-MM-DD` 형식을 보장하므로 문자열 비교가 안전합니다.
import {
getDaysInMonth,
getFirstDayOfWeek,
getWeekdayLabels,
toIsoDate,
getTodayIso,
addDaysToIso,
isoToLocalDate,
isoToDotDisplay,
formatCalendarMonthTitle,
formatDayAriaLabel,
} from "@/shared/lib/calendarUtils";