DatePicker#
캘린더 UI를 통해 날짜를 선택할 수 있는 컴포넌트입니다.
CoDatePicker는 Flutter/Web 양쪽에서 동일한 named-parameter API를 제공하는 통일 컴포넌트입니다. 내부적으로 팝업 캘린더는 CoCalendar를 사용합니다.
Live Preview#
기본 (single + popover)#
class DatePickerDefaultExample extends StatefulComponent {
const DatePickerDefaultExample({super.key});
@override
State<DatePickerDefaultExample> createState() =>
_DatePickerDefaultExampleState();
}
class _DatePickerDefaultExampleState extends State<DatePickerDefaultExample> {
String? _value;
@override
Component build(BuildContext context) {
return div(
[
CoDatePicker(
onChanged: (v) => setState(() => _value = v),
placeholder: 'Select date',
value: _value,
),
],
classes: 'w-${CoreSpace.scale.space256}',
);
}
}
class DatePickerDefaultExample extends StatefulWidget {
const DatePickerDefaultExample({super.key});
@override
State<DatePickerDefaultExample> createState() =>
_DatePickerDefaultExampleState();
}
class _DatePickerDefaultExampleState extends State<DatePickerDefaultExample> {
@override
Widget build(BuildContext context) {
return SizedBox(
width: CoreSpace.space256,
child: CoDatePicker.simple(
placeholder: 'Select date',
onChanged: (_) {},
),
);
}
}
단일 + 다이얼로그 모드#
class DatePickerDialogExample extends StatefulComponent {
const DatePickerDialogExample({super.key});
@override
State<DatePickerDialogExample> createState() =>
_DatePickerDialogExampleState();
}
class _DatePickerDialogExampleState extends State<DatePickerDialogExample> {
String? _value;
@override
Component build(BuildContext context) {
return div(
[
CoDatePicker(
onChanged: (v) => setState(() => _value = v),
value: _value,
mode: CorePromptMode.dialog,
placeholder: 'Select date',
dialogTitle: const Text('Pick a date'),
),
],
classes: 'w-${CoreSpace.scale.space256}',
);
}
}
class DatePickerDialogExample extends StatefulWidget {
const DatePickerDialogExample({super.key});
@override
State<DatePickerDialogExample> createState() =>
_DatePickerDialogExampleState();
}
class _DatePickerDialogExampleState extends State<DatePickerDialogExample> {
DateTime? _value;
@override
Widget build(BuildContext context) {
return SizedBox(
width: CoreSpace.space256,
child: CoDatePicker(
onChanged: (v) => setState(() => _value = v),
value: _value,
mode: CorePromptMode.dialog,
placeholder: 'Select date',
dialogTitle: const Text('Pick a date'),
),
);
}
}
범위 + 팝오버 모드#
class DatePickerRangeExample extends StatefulComponent {
const DatePickerRangeExample({super.key});
@override
State<DatePickerRangeExample> createState() =>
_DatePickerRangeExampleState();
}
class _DatePickerRangeExampleState extends State<DatePickerRangeExample> {
({String start, String end})? _value;
@override
Component build(BuildContext context) {
return div(
[
CoDateRangePicker(
onChanged: (v) => setState(() => _value = v),
value: _value,
mode: CorePromptMode.popover,
placeholder: Text('Select range'),
),
],
classes: 'w-${CoreSpace.scale.space256}',
);
}
}
class DatePickerRangeExample extends StatefulWidget {
const DatePickerRangeExample({super.key});
@override
State<DatePickerRangeExample> createState() =>
_DatePickerRangeExampleState();
}
class _DatePickerRangeExampleState extends State<DatePickerRangeExample> {
DateTimeRange? _value;
@override
Widget build(BuildContext context) {
return SizedBox(
width: CoreSpace.space256,
child: CoDateRangePicker(
onChanged: (v) => setState(() => _value = v),
value: _value,
mode: CorePromptMode.popover,
placeholder: const Text('Select range'),
),
);
}
}
범위 + 다이얼로그 모드 (CoDateRangePicker 기본)#
class DatePickerRangeDialogExample extends StatefulComponent {
const DatePickerRangeDialogExample({super.key});
@override
State<DatePickerRangeDialogExample> createState() =>
_DatePickerRangeDialogExampleState();
}
class _DatePickerRangeDialogExampleState
extends State<DatePickerRangeDialogExample> {
({String start, String end})? _value;
@override
Component build(BuildContext context) {
return div(
[
CoDateRangePicker(
onChanged: (v) => setState(() => _value = v),
value: _value,
placeholder: Text('Select range'),
dialogTitle: const Text('Pick a range'),
),
],
classes: 'w-${CoreSpace.scale.space256}',
);
}
}
class DatePickerRangeDialogExample extends StatefulWidget {
const DatePickerRangeDialogExample({super.key});
@override
State<DatePickerRangeDialogExample> createState() =>
_DatePickerRangeDialogExampleState();
}
class _DatePickerRangeDialogExampleState
extends State<DatePickerRangeDialogExample> {
DateTimeRange? _value;
@override
Widget build(BuildContext context) {
return SizedBox(
width: CoreSpace.space256,
child: CoDateRangePicker(
onChanged: (v) => setState(() => _value = v),
value: _value,
placeholder: const Text('Select range'),
dialogTitle: const Text('Pick a range'),
),
);
}
}
사용 시기 (When to Use)#
이 컴포넌트를 사용하세요:
- 폼에서 날짜 입력이 필요할 때 (생년월일, 예약일 등)
- 트리거 버튼과 캘린더 팝업을 결합한 UI가 필요할 때
- 체크인/체크아웃 같은 날짜 범위 선택이 필요할 때 (Flutter에서
CoDateRangePicker사용)
대신 다른 컴포넌트를 사용하세요:
CoCalendar: 캘린더를 항상 인라인으로 표시할 때CoTextField: 날짜 형식 텍스트만 입력받을 때CoSelect: 미리 정의된 날짜 옵션 중 선택할 때
기본 사용법 (Basic Usage)#
// 기본 날짜 선택 — 내부 상태 관리 포함
CoDatePicker.simple(
placeholder: 'Select date',
onChanged: handleDateChanged,
)
// 외부 상태로 제어
CoDatePicker(
value: selectedDate,
placeholder: 'Select date',
onChanged: handleDateChanged,
)
// 날짜 범위 선택
CoDateRangePicker(
value: selectedRange,
onChanged: handleRangeChanged,
)
// 기본 날짜 선택 — ISO 8601 문자열(YYYY-MM-DD)로 값 전달
CoDatePicker(
onChanged: handleDateChanged,
placeholder: 'Select date',
value: selectedDate,
)
// Dialog 모드 — 중앙 모달 + Cancel/Save 액션
CoDatePicker(
onChanged: handleDateChanged,
value: selectedDate,
mode: CorePromptMode.dialog,
placeholder: 'Select date',
dialogTitle: const Text('Pick a date'),
)
// 날짜 범위 — Web도 CoDateRangePicker 지원
CoDateRangePicker(
onChanged: handleRangeChanged,
value: selectedRange, // ({start: 'YYYY-MM-DD', end: 'YYYY-MM-DD'})
mode: CorePromptMode.popover,
placeholder: const Text('Select range'),
)
// 범위 + Dialog 모드 (CoDateRangePicker 기본)
CoDateRangePicker(
onChanged: handleRangeChanged,
value: selectedRange,
placeholder: const Text('Select range'),
dialogTitle: const Text('Pick a range'),
)
Props / Parameters#
공통 Contract 필드 (CoreDatePickerContract):
| 속성 | Flutter 타입 | Web 타입 | 기본값 | 설명 |
|---|---|---|---|---|
placeholder |
String? |
String? |
null |
값이 없을 때 표시 텍스트 |
enabled |
bool |
bool |
true |
상호작용 허용 여부 |
onChanged |
ValueChanged<DateTime?>? |
CoreValueChanged<String>? |
null |
값 변경 콜백 |
min |
DateTime? |
String? (ISO) |
null |
최소 선택 가능 날짜 |
max |
DateTime? |
String? (ISO) |
null |
최대 선택 가능 날짜 |
label |
String? |
String? |
null |
트리거 상단 라벨 |
description |
String? |
String? |
null |
트리거 하단 보조 텍스트 |
errorText |
String? |
String? |
null |
에러 메시지. 존재 시 description은 숨김 |
mode |
CorePromptMode? |
CorePromptMode? |
popover (single) / dialog (range) |
표시 모드 (popover/dialog) |
dialogTitle |
Widget? |
Component? |
null |
다이얼로그 모드 제목 슬롯 |
datePickerStyle (single) / dateRangePickerStyle (range) |
CoreDatePickerStyle<Color, List<BoxShadow>>? |
CoreDatePickerStyle<String, String>? |
null |
인스턴스 스타일 (Style 시스템 참조) |
Flutter 확장 필드:
| 속성 | 타입 | 기본값 | 설명 |
|---|---|---|---|
value |
DateTime? |
null |
현재 선택된 날짜 |
initialView |
CoreCalendarView? |
null |
초기 캘린더 뷰 (null 시 value 기준) |
stateBuilder |
CoreCalendarDateStateBuilder? |
null |
각 날짜 셀 상태 빌더 |
placeholderWidget |
Widget? |
null |
커스텀 placeholder 위젯 |
popoverAlignment |
AlignmentGeometry? |
Alignment.topLeft |
팝오버 정렬점 |
popoverAnchorAlignment |
AlignmentGeometry? |
Alignment.bottomLeft |
앵커 정렬점 |
Web 확장 필드:
| 속성 | 타입 | 기본값 | 설명 |
|---|---|---|---|
value |
String? |
null |
현재 값 (ISO 8601) |
id | String? | null | DOM 요소 id |
classes |
String? |
null |
트리거에 적용할 추가 CSS 클래스 |
css |
Styles? |
null |
루트에 적용할 인라인 스타일 |
스타일 시스템 (Style System)#
DatePicker 의 모든 chrome / dimensional / nested-slot 오버라이드는 CoreDatePickerStyle<Clr, Shadow>
단일 슬롯으로 흐릅니다 (Epic #1302 원칙 6/7/8). 평면 chrome 필드(borderRadius / padding /
height / gap / iconSize / popoverPadding / popoverGap)는 모두 제거되었습니다.
시맨틱 vs 스타일#
-
시맨틱 enum / behaviour: 위젯 파라미터로 직접 (
mode,placeholder,enabled,onChanged,min,max,label,description,errorText,dialogTitle) -
chrome / dimensional / 슬롯 스타일:
CoreDatePickerStyle한곳으로 (popoverStyle/dialogStyle/calendarStyle/labelStyle/descriptionStyle/errorStyle) -
변형(variant) 교체: asChild —
dialogTitle/ 커스텀placeholder/ 커스텀builder(range) 등의 슬롯에 직접 위젯 주입
Resolve chain#
design system default for date picker
→ CoreDatePickerTheme.style // 프로젝트 공통
→ parent component slot override
→ widget.datePickerStyle // 인스턴스별 (range: dateRangePickerStyle)
각 nested 슬롯 스타일 (popoverStyle / dialogStyle / calendarStyle) 은 자기 컴포넌트의 자체 resolve chain 으로 다시 한 번 머지됩니다. 예:
popoverStyle.triggerStyle 의 chrome 은 → CoreButtonTheme → variant 룩업 → 부모 슬롯 →
widget.datePickerStyle.popoverStyle.triggerStyle 순.
옛 평면 필드 → 새 위치 매핑#
| 옛 chrome 필드 | 새 위치 |
|---|---|
height | popoverStyle.triggerStyle.height |
padding |
popoverStyle.triggerStyle.paddingH 또는 popoverStyle.panelPadding (의도에 따라) |
borderRadius | popoverStyle.triggerStyle.borderRadius |
gap (트리거 내부) | popoverStyle.triggerStyle.gap |
iconSize | popoverStyle.triggerStyle.trailingIconStyle.size |
popoverPadding | popoverStyle.panelPadding |
popoverGap | popoverStyle.gap |
사용 예 (Flutter)#
CoDatePicker(
placeholder: 'Select date',
onChanged: handleChanged,
datePickerStyle: CoreDatePickerStyle(
popoverStyle: CorePopoverStyle(
panelBorderRadius: 12,
gap: 8,
triggerStyle: CoreButtonStyle(
height: 44,
paddingH: 16,
),
),
calendarStyle: CoreCalendarStyle(
selectedColor: Color(0xFF3B82F6),
),
labelStyle: CoreTextStyle(fontWeight: 600),
),
)
변형 (Variants)#
Controlled (Flutter)#
// 내부 상태 관리 — simple 팩토리 또는 ControlledCoDatePicker
ControlledCoDatePicker(
initialValue: DateTime.now(),
onChanged: handleDateChanged,
)
날짜 범위 선택 (Flutter)#
CoDateRangePicker(
value: DateTimeRange(checkIn, checkOut),
onChanged: handleRangeChanged,
)
비활성 날짜 지정 (Flutter)#
CoDatePicker(
value: selectedDate,
onChanged: handleDateChanged,
stateBuilder: (date) =>
date.weekday <= 5
? CoreCalendarDateState.enabled
: CoreCalendarDateState.disabled,
)
동작 스펙 (Behavior)#
표시 모드 (Flutter)#
// Popover 모드 (기본) — 트리거 아래에 캘린더 드롭다운
CoDatePicker(
mode: PromptMode.popover,
popoverAlignment: Alignment.topLeft,
popoverAnchorAlignment: Alignment.bottomLeft,
value: selectedDate,
onChanged: handleDateChanged,
)
// Dialog 모드 — 전체 화면 모달에 캘린더 표시
CoDatePicker(
mode: PromptMode.dialog,
dialogTitle: Text('날짜 선택'),
value: selectedDate,
onChanged: handleDateChanged,
)
CoDatePickerController (Flutter)#
final controller = CoDatePickerController(DateTime(2024, 3, 15));
// 프로그래매틱 제어
controller.value = DateTime.now();
ValueNotifier<DateTime?>기반ComponentControllermixin으로 폼 통합
범위 선택 (Flutter)#
CoDateRangePicker는 기본PromptMode.dialog사용CoCalendar의 range 모드를 활용하여 시작/끝 날짜를 동시에 선택
Web 구현#
-
트리거:
<div>기반 field-style 렌더링 (CoIcon(CoLucideIcons.calendar | calendarRange)+ placeholder/선택 값을Textchain 으로 표시) -
Popover mode:
CoPopover사용 —CoOverlayHost.popover레이어로 portal, ancestoroverflow: hidden/transform영향 없음 -
Dialog mode:
CoDialog사용 —openboolean 으로 제어, Cancel/Save 액션으로 값 commit -
인터랙션: 클릭 / outside-click / Escape —
CoPopover/CoDialog가 모두 처리 (직접 document listener 불필요) -
Single 값은 ISO 8601 (
YYYY-MM-DD) 문자열, range 값은({String start, String end})레코드 min/max는 ISO 문자열로 전달 (내부에서DateTime으로 파싱)
사용 가이드라인 (Usage Guidelines)#
Do#
적절한 placeholder로 기대 형식을 안내하세요.
CoDatePicker(
placeholder: 'YYYY-MM-DD',
value: selectedDate,
onChanged: handleDateChanged,
)
빈 상태에서 사용자가 무엇을 입력해야 하는지 알 수 있습니다.
Don't#
미래/과거 날짜를 선택 가능하게 두지 마세요 (해당되는 경우).
// Bad — 과거 날짜에 예약 가능
CoDatePicker(value: selectedDate, onChanged: handleDateChanged)
// Good — 미래 날짜만 선택 가능
CoDatePicker(
min: DateTime.now(),
value: selectedDate,
onChanged: handleDateChanged,
)
Do#
범위 선택에는 CoDateRangePicker를 사용하세요 (Flutter).
CoDateRangePicker(
value: DateTimeRange(checkIn, checkOut),
onChanged: (range) {
setState(() { checkIn = range?.start; checkOut = range?.end; });
},
)
시작/끝 날짜가 시각적으로 연결되어 사용자가 범위를 쉽게 이해합니다.
Don't#
두 개의 독립 CoDatePicker로 범위를 구현하지 마세요.
시작일이 끝일보다 뒤일 수 있는 오류가 발생합니다. CoDateRangePicker가 자동으로 순서를 보장합니다.
접근성 (Accessibility)#
키보드 인터랙션#
| 키 | 동작 |
|---|---|
Enter / Space | 캘린더 팝업 열기/닫기 |
← / → | 이전/다음 날짜 이동 (캘린더 내) |
↑ / ↓ | 이전/다음 주 이동 (캘린더 내) |
Page Up/Down | 이전/다음 월 이동 (캘린더 내) |
Escape | 팝업 닫기 |
스크린 리더#
- 트리거에 현재 선택 날짜 읽힘
- 팝업 캘린더에
role="application",aria-label="Calendar"설정 errorText존재 시aria-invalid="true"설정- 비활성 상태에서
aria-disabled="true"설정
포커스 관리#
- 팝업 열림 시 캘린더로 포커스 이동
- 날짜 선택 또는 Escape 시 트리거로 포커스 복귀
크로스 플랫폼 차이점 (Platform Differences)#
CoDatePicker는CoreDatePickerContract를 통해 핵심 API(placeholder,enabled,onChanged,min,max,label,description,errorText)를 통일합니다. 아래는 플랫폼 고유 차이점만 나열합니다.
| 항목 | Flutter | Web |
|---|---|---|
| 값 타입 | DateTime? (single) / DateTimeRange? (range) |
String? ISO 8601 (single) / ({String start, String end})? (range) |
| 표시 모드 | CorePromptMode (popover/dialog) |
CorePromptMode (popover/dialog) |
| 범위 선택 | CoDateRangePicker (Flutter) |
CoDateRangePicker (Web) — 동일 contract |
| 컨트롤러 | CoDatePickerController (외부 상태) | 내부 상태 관리 |
| 캘린더 UI | CoCalendar | CoCalendar (동일) |
| Popover 인프라 | CoPopover + _popoverController |
CoPopover + _popoverController (동일) |
| Dialog 인프라 | CoDialog + showDialog |
CoDialog (open boolean 제어) |