DateRangePicker
업무 기간 조회용 범위 선택기 — 임의선택(프리셋)/달력선택(듀얼 먼스) 2탭 팝업. 비제어 defaultValue + onChange(즉시)/onConfirm(선택완료 확정). 외부 클릭·Escape는 이전 확정값을 복원합니다 (DK WizDatePickerEx 사양 승격).
마지막 업데이트 2026-06-11
한눈에#
업무 조회용 기간(시작~끝) 선택기입니다 — 임의선택(프리셋)/달력선택(듀얼 먼스) 2탭 팝업으로, 연·월·분기·반기 프리셋과 듀얼 먼스 달력을 함께 제공합니다.
아직 확정 없음
- 임의선택·달력선택 2탭
- 프리셋 + 듀얼 먼스
- onChange 즉시 / onConfirm 확정
- 비제어 + BottomSheet 모바일
임시 변경은 onChange로 즉시, 선택완료로 확정될 때 onConfirm — 외부 클릭·Escape는 직전 확정값 복원
트리거는 Input 어휘이고, 팝업 상단의 "임의선택"/"달력선택" 2탭으로 입력
방식을 전환합니다. 모든 임시 변경은 onChange로 즉시, "선택완료"로 확정될
때 onConfirm이 호출되며 트리거 표시값도 그 시점에 갱신됩니다.
사용 시점#
매출·로그 조회처럼 업무 단위 기간을 프리셋+달력으로 고르면 DateRangePicker — 단일 날짜면 DatePicker입니다.
매출·로그 조회처럼 연·월·분기·반기 프리셋과 듀얼 먼스로 시작~끝 기간을
플레이그라운드#
컨트롤로 props를 조작하면 미리보기와 코드가 실시간 갱신됩니다.
import { DateRangePicker } from '@wds/ui-web';
<DateRangePicker />변형#
입력 방식 2탭이 변형 축입니다.
임의선택 (프리셋) — 연도 · 빠른선택(오늘·이번주·이번달) · 월(6열 그리드) ·
분기 · 반기 버튼 그리드입니다. 연도·월·분기는 결합 가능 유형이라 같은
유형의 두 항목을 차례로 누르면 범위가 결합됩니다(2클릭 범위 결합) — 예: 3월 →
6월을 누르면 "2026년 3월~6월". 늦은 쪽을 먼저 눌러도 정방향으로 스왑됩니다.
같은 항목을 다시 누르면 그 항목 단일 범위로 돌아가고, 빠른선택·반기는 단일
클릭으로 즉시 확정되는 단일 범위입니다. 연도 버튼을 누르면 월/분기/반기
프리셋이 그 연도 기준으로 바뀝니다. showQuickSelect/showQuarter/showHalf로
각 절을 끄고, years로 연도 버튼 목록(기본 최근 3년)을 바꿉니다.
달력선택 (듀얼 먼스) — 시작일·종료일 두 개의 Calendar를 나란히 띄웁니다.
각 달력 상단에는 시작일/종료일 제목과 현재 선택값(미선택 시 "선택 안 됨")이
표시되어 어느 달력이 무엇을 고르는지, 지금 무엇이 잡혀 있는지 한눈에 보입니다
(startLabel/endLabel로 변경 가능). 두 달력은 이미 접근 이름을 갖고 있어
스크린리더에는 동일한 구분이 전달되며, 시각 헤더는 그 구분을 눈으로도 보이게 합니다.
시작일을 종료일 이후로 바꾸면 종료일이 비워지고 종료 달력이 재마운트됩니다.
종료 달력의 최소 선택일은 선택된 시작일로 제한됩니다. 달력으로 고르면 프리셋
선택 표시(aria-pressed)는 해제됩니다.
푸터의 초기화는 임시 선택을 빈 범위로 되돌리고(프리셋 표시·달력 모두 리셋), 선택완료는 현재 임시 선택을 확정합니다.
크기#
단일 크기입니다 — 트리거 높이는 input.height(44px), 최소 폭 16rem으로 Input과
같은 폼 컨트롤 줄맞춤입니다. 팝업은 내용 폭(max-content)이되 뷰포트를 넘지
않도록 max-width: calc(100vw - space.8)로 제한되고, 좁으면 듀얼 먼스가 세로로
스택됩니다(bp.mobile 767px 기준).
상태#
비제어 컴포넌트입니다 — defaultValue로 시작하고, 임시 변경(draft)은
onChange로, 확정은 onConfirm으로 통지합니다. 열림/닫힘도 내부 소유:
트리거 클릭으로 열고, 선택완료·Escape·외부 클릭으로 닫힙니다. 단, Escape와
외부 클릭은 취소라 직전 확정값을 복원합니다(선택완료만 확정). 트리거의
Hover/Focus/Disabled는 CSS 의사클래스로 표현되고, 열림 동안
aria-expanded="true"가 유지됩니다.
모바일#
DK 모바일 재활용 표준입니다 — mobile을 켜면 팝업 대신
BottomSheet 안에 2탭 + 패널이 렌더되고,
footer의 초기화/선택완료 버튼으로 마무리합니다. 시트의 닫힘 경로(배경
클릭·Escape·핸들 드래그 다운)는 취소로 동작해 직전 확정값을 복원합니다. 시트
제목은 mobileTitle(기본 placeholder)입니다. 라이브 데모는 데스크톱
뷰포트라 코드로 안내합니다.
<DateRangePicker
mobile
mobileTitle="조회 기간"
years={[2024, 2025, 2026]}
onConfirm={(value) => applyPeriod(value)}
/>
Props#
| Prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
defaultValue | { start: Date | null; end: Date | null } | — | 초기 범위 (비제어) — 이후 상태는 내부 소유 |
onChange | (value: DateRangeValue) => void | — | 모든 임시 변경(프리셋 클릭·달력 클릭·초기화)에 즉시 호출 |
onConfirm | (value: DateRangeValue) => void | — | "선택완료"로 확정될 때 호출 — 트리거 표시값도 이 시점에 갱신 |
years | ReadonlyArray<number> | 최근 3년 | 프리셋 연도 버튼 목록 |
showQuickSelect | boolean | true | 빠른선택(오늘·이번주·이번달) 절 노출 |
showQuarter | boolean | true | 분기 절 노출 |
showHalf | boolean | true | 반기 절 노출 |
min | Date | — | 달력 최소 선택일 — Calendar로 전달 |
max | Date | — | 달력 최대 선택일 — Calendar로 전달 |
disabled | (date: Date) => boolean | — | 비활성 날짜 판별 — Calendar로 전달 |
placeholder | string | '기간 선택' | 값이 없을 때 트리거 텍스트 + 팝업 dialog의 aria-label |
confirmText | string | '선택완료' | 확정 버튼 라벨 |
resetText | string | '초기화' | 초기화 버튼 라벨 |
presetTabText | string | '임의선택' | 프리셋 탭 라벨 |
calendarTabText | string | '달력선택' | 달력 탭 라벨 |
startLabel | string | '시작일' | 왼쪽(시작) 달력 상단 제목 — 시각 헤더이자 그 달력의 접근 이름 |
endLabel | string | '종료일' | 오른쪽(종료) 달력 상단 제목 — 시각 헤더이자 그 달력의 접근 이름 |
showCalendarHeader | boolean | true | 달력 상단 제목+선택값 헤더 표시 (false면 달력만) |
mobile | boolean | false | true면 BottomSheet 표면으로 전환 (DK 모바일 표준) |
mobileTitle | string | — | 시트 제목 — 기본 placeholder |
…rest | Omit<ButtonHTMLAttributes, 'onChange' | 'value' | 'defaultValue' | 'disabled'> | — | className 등은 트리거 button으로 전달 |
DateRangeValue(onChange/onConfirm 인자):
| Prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
start | Date | null | — | 시작일 (없으면 null) |
end | Date | null | — | 종료일 (없으면 null) |
presetType | DateRangePresetType | — | 프리셋으로 만든 범위면 유형 — 'today'|'thisWeek'|'thisMonth'|'month'|'quarter'|'half'|'year' |
presetLabel | string | — | 프리셋 라벨 — 트리거 표시는 이 값을 우선 (예: "2026년 3월~6월") |
접근성#
트리거가 aria-haspopup="dialog" + aria-expanded, 팝업이
role="dialog"(이름은 placeholder)입니다. 2탭은 aria-pressed 토글
버튼이고, 프리셋 절은 각각 role="group" + aria-label로 묶입니다.
| 키 | 동작 |
|---|---|
트리거 Enter / Space | 팝업 열기 (네이티브 button) — 다시 누르면 취소 닫기 |
Escape (팝업 내부 어디서든) | 취소 — 직전 확정값 복원 + 트리거 포커스 복원 |
Tab | 탭·프리셋 버튼·달력·푸터 버튼 순회 |
| 달력 키보드 | 화살표(일)·Home/End(주 시작·끝)·PageUp/Down(월) — Calendar APG grid |
- 외부 클릭도 취소(직전 확정값 복원, 포커스는 클릭 대상에 양보)
- 프리셋 선택 표시는
aria-pressed, 결합 선택이면 두 항목 모두 눌림 표시 - 달력 월 라벨은
aria-live="polite"로 갱신 안내 (Calendar 합성) - 팝업 등장 모션은 opacity/transform만 +
prefers-reduced-motion존중
토큰#
component 토큰 없이 semantic(+input 어휘)을 직접 소비합니다(신설 기준 §4 미충족).
| 속성 | 토큰 |
|---|---|
| 트리거 | input.height/bg/border/radius/fg · placeholder input.placeholder |
| 트리거 포커스/열림 | color.focus-ring(아웃라인) · input.border-focus |
| 팝업 | color.surface-raised · color.border · radius.lg · shadow.lg · z.dropdown |
| 2탭 | color.surface-muted(트랙) · 활성 color.surface-raised + color.primary-text + shadow.sm |
| 프리셋 버튼 | color.surface · color.border · 높이 space.8 · hover color.surface-hover → 눌림 color.surface-pressed — 선택 color.primary + color.on-primary |
| 듀얼 먼스 | Calendar 토큰(color.primary·color.on-primary·color.primary-subtle) |
| 푸터 | 초기화 color.surface+border-strong+primary-text(눌림 color.surface-pressed) · 선택완료 button.bg/fg |
| 모션 | duration.fast + 팝업 등장 ease.emphasized-decelerate / ease.standard |