Calendar | CoUI

Calendar

캘린더 컴포넌트

Calendar#

날짜를 시각적으로 탐색하고 선택할 수 있는 캘린더 컴포넌트입니다.

Live Preview#

사용 시기 (When to Use)#

이 컴포넌트를 사용하세요:

  • 날짜를 시각적으로 탐색하고 선택해야 할 때
  • 범위(체크인/체크아웃) 또는 다중 날짜 선택이 필요할 때

대신 다른 컴포넌트를 사용하세요:

  • DatePicker: 입력 필드와 결합된 날짜 선택 폼 요소일 때
  • TextField: 단순 날짜 텍스트 입력만 필요할 때

기본 사용법 (Basic Usage)#

// 단일 선택
Calendar.single(
  onChanged: (value) => print('Selected: $value'),
)

// 범위 선택
Calendar.range(
  onChanged: (value) => print('Range: $value'),
)

// 다중 선택
Calendar.multi(
  onChanged: (value) => print('Multi: $value'),
)
// 단일 선택
Calendar.single(
  onChanged: (value) => print('Selected: $value'),
)

// 범위 선택
Calendar.range(
  onChanged: (value) => print('Range: $value'),
)

// 다중 선택
Calendar.multi(
  onChanged: (value) => print('Multi: $value'),
)

Props / Parameters#

속성타입기본값설명
selectionMode CoreCalendarSelectionMode single 선택 모드
value CoreCalendarValue? null 현재 선택 값
onChanged Function(CoreCalendarValue?)? null 선택 변경 콜백
initialView CoreCalendarView? null 초기 표시 월/년
stateBuilder CoreCalendarDateStateBuilder? null 날짜별 활성/비활성 결정
minDate DateTime? null 최소 선택 가능 날짜
maxDate DateTime? null 최대 선택 가능 날짜
calendarStyle CoreCalendarStyle? null 인스턴스별 스타일 (chrome / 슬롯 / 상태 토큰)

스타일 시스템 (Style System)#

Calendar 의 모든 chrome / dimensional / 슬롯 / 상태 토큰 override 는 단일 calendarStyle: CoreCalendarStyle 슬롯으로 흐릅니다 (Epic #1302). selectionMode / initialView 같은 시맨틱 enum 과 동작 필드는 위젯에 그대로 남고, 스타일 슬롯에는 들어가지 않습니다 (원칙 6 / 7).

구조#

CoreCalendarStyle 은 chrome 슬롯 + nested sub-component Style + state-token 색상 슬롯을 단일 진입점으로 보유합니다.

필드타입default적용 대상
dayCellSize double? defaultDayCellSize (space32) 일 / 월 / 연도 셀 크기
monthCellWidth double? defaultMonthCellWidth (size56) 월 / 연도 셀 너비
cellSpacing double? defaultCellSpacing (space8) 셀 간 간격
headerGridGapStyle CoreGapStyle? defaultHeaderGridGapStyle (CoreGapStyle(size: space16)) 헤더 ↔ 그리드 간격 (Gap 슬롯)
yearRowSpacing double? defaultYearRowSpacing (space8) 연도 그리드 행 간격
arrowIconSize double? defaultArrowIconSize (space12) 화살표 아이콘 크기
endpointPillBorderRadius CoreBorderRadius? defaultEndpointPillBorderRadius (radius8) range 양 끝 pill 모서리
fadedOpacity double? defaultFadedOpacity (opacity50) 다른 달 날짜 불투명도
yearGridSize int? defaultYearGridSize (16) 연도 그리드 총 항목 수
monthGridColumns int? defaultMonthGridColumns (4) 월 그리드 열 수
yearGridColumns int? defaultYearGridColumns (4) 연도 그리드 열 수
navButtonStyle CoreButtonStyle? prev / next 네비 버튼 / 헤더 버튼
dateCellStyle CoreButtonStyle? 날짜 / 월 / 연도 셀 버튼 chrome
arrowIconStyle CoreIconStyle? prev / next 화살표 아이콘
headerTextStyle CoreTextStyle? defaultHeaderTextStyle (bodySmall) 헤더 라벨 ("May 2026")
weekdayTextStyle CoreTextStyle? defaultWeekdayTextStyle (labelMedium) 요일 라벨 (Sun / Mon …)
cellTextStyle CoreTextStyle? defaultCellTextStyle (bodySmall) 날짜 / 월 / 연도 셀 텍스트
selectedColor CoreColor? defaultSelectedColor (primary) 선택된 셀 배경
selectedTextColor CoreColor? defaultSelectedTextColor (onPrimary) 선택된 셀 텍스트
todayColorCoreColor?오늘 셀 배경
todayTextColorCoreColor?오늘 셀 텍스트
rangeSelectionColor CoreColor? defaultRangeSelectionColor (secondary) range 내부 셀 배경
rangeSelectionTextColor CoreColor? defaultRangeSelectionTextColor (onSecondary) range 내부 셀 텍스트
weekdayLabelColor CoreColor? defaultWeekdayLabelColor (onSurface) 요일 라벨 색

상태 토큰들은 grid 안의 state-coloured 셀로, 단일 sub-component chrome 에 귀속되지 않으므로 nested slot Style 이 아닌 평면(flat) CoreColor 슬롯으로 노출합니다.

Resolve chain#

design system default
  → CoreCalendarTheme.style                   // 프로젝트 공통
  → parent component slot override
      (예: CoreDatePickerStyle.calendarStyle)
  → widget.calendarStyle                      // 인스턴스별

CoreCalendarStyle.merge 는 nested slot Style (navButtonStyle 등) 을 재귀적으로 머지하므로 부모 / theme / 인스턴스 단계에서 부분 override 가 가능합니다.

사용 예#

Calendar(
  selectionMode: CoreCalendarSelectionMode.single,
  calendarStyle: CoreCalendarStyle(
    selectedColor: CoreColor.token(CoreColors.success),
    dateCellStyle: CoreButtonStyle(
      labelStyle: CoreTextStyle(fontWeight: CoreFontWeight.semiBold),
    ),
  ),
  onChanged: (value) => print(value),
)
Calendar(
  selectionMode: CoreCalendarSelectionMode.single,
  calendarStyle: CoreCalendarStyle(
    selectedColor: CoreColor.token(CoreColors.primary),
    rangeSelectionColor: CoreColor.token(CoreColors.secondary),
  ),
  onChanged: (value) => print(value),
)

변형 (Variants)#

단일 선택 (Single)#

Calendar.single(
  onChanged: (value) => print('Selected: $value'),
)

범위 선택 (Range)#

Calendar.range(
  onChanged: (value) => print('Range: $value'),
)

범위 모드에서는 듀얼 캘린더 레이아웃이 표시됩니다.

다중 선택 (Multi)#

Calendar.multi(
  onChanged: (value) => print('Multi: $value'),
)

표시 전용 (None)#

Calendar(
  selectionMode: CoreCalendarSelectionMode.none,
)

동작 스펙 (Behavior)#

선택 모드#

CoreCalendarSelectionMode.none    // 표시 전용
CoreCalendarSelectionMode.single  // 단일 날짜 선택
CoreCalendarSelectionMode.range   // 시작~끝 범위 선택
CoreCalendarSelectionMode.multi   // 다중 날짜 선택
  • Single: 클릭으로 선택, 다시 클릭하면 해제
  • Range: 첫 번째 클릭=시작, 두 번째 클릭=끝. 자동 정렬(start < end)
  • Multi: 클릭할 때마다 선택/해제 토글

CoreCalendarValue#

// 단일 선택
CoreCalendarValue.single(DateTime(2024, 3, 15))

// 범위 선택
CoreCalendarValue.range(DateTime(2024, 3, 10), DateTime(2024, 3, 20))

// 다중 선택
CoreCalendarValue.multi([DateTime(2024, 3, 5), DateTime(2024, 3, 15)])

뷰 전환#

  • CoreCalendarViewType.date: 월별 일 그리드 (6행 × 7열)
  • CoreCalendarViewType.month: 월 선택 (4×3 그리드)
  • CoreCalendarViewType.year: 연도 선택 (4×4 그리드)
  • 헤더의 월/연도를 클릭하면 상위 뷰로 전환

날짜 상태 검증#

Calendar.single(
  stateBuilder: (date) {
    if (date.isBefore(DateTime.now())) return CoreCalendarDateState.disabled;
    return CoreCalendarDateState.enabled;
  },
  onChanged: (value) => print(value),
)

크로스 플랫폼 차이점 (Platform Differences)#

항목FlutterWeb
클래스명CalendarCalendar
값 모델CoreCalendarValueCoreCalendarValue
선택 모드none/single/range/multinone/single/range/multi
뷰 전환date → month → yeardate → month → year
범위 레이아웃듀얼 캘린더듀얼 캘린더
네비게이션 Button(variant: .outline) Button(variant: .outline)
날짜 셀 Button(variant: .ghost/.primary/.secondary) Button(variant: .ghost/.primary/.secondary)