RangePicker
시작과 끝, 두 날짜를 선택합니다. 선택 사항으로 프리셋 제공.

import { RangePicker } from '@kalyx/react';
구조
<RangePicker> {/* Root: holds the { start, end } range */}
<RangePicker.Input part="start" /> {/* start-date combobox input */}
<RangePicker.Input part="end" /> {/* end-date combobox input */}
<RangePicker.Popover> {/* Floating-UI portal, role="dialog" */}
<RangePicker.Presets> {/* role="group" of quick-range buttons */}
<RangePicker.Preset value="today">Today</RangePicker.Preset> {/* one quick-range toggle */}
</RangePicker.Presets>
<RangePicker.Calendar /> {/* range-aware month grid */}
</RangePicker.Popover>
</RangePicker>
기본 사용
import { useState } from 'react';
import { RangePicker, type DateRange } from '@kalyx/react';
function Example() {
const [range, setRange] = useState<DateRange>({ start: null, end: null });
return (
<RangePicker value={range} onChange={setRange}>
<RangePicker.Input part="start" placeholder="시작" />
<RangePicker.Input part="end" placeholder="종료" />
<RangePicker.Popover>
<RangePicker.Calendar />
</RangePicker.Popover>
</RangePicker>
);
}
선택 흐름: 첫 클릭이 start를, 두 번째가 end를 설정합니다. 두 번째 클릭이 더 이르면 자동으로 위치가 교환됩니다.
직접 사용해보기
라이브 에디터는
React와 모든 Kalyx 컴포넌트가 이미 스코프에 있는 상태로 실행되므로import문을 생략했습니다. 실제 프로젝트로 옮길 때는 import 를 채워 넣으세요. 전체 import 는 위의 일반 코드 블록에 있습니다.
라이브 에디터
function BasicRange() { const [range, setRange] = React.useState({ start: null, end: null }); return ( <RangePicker value={range} onChange={setRange}> <div className="kx-live-row"> <RangePicker.Input part="start" className="kx-live-input" placeholder="시작" /> <span aria-hidden>→</span> <RangePicker.Input part="end" className="kx-live-input" placeholder="종료" /> </div> <RangePicker.Popover className="kx-live-popover"> <RangePicker.Calendar classNames={{ header: 'kx-live-header', title: 'kx-live-title', navButton: 'kx-live-nav', grid: 'kx-live-grid', gridCell: 'kx-live-cell', weekdayHeader: 'kx-live-weekday', day: 'kx-live-day-range', dayRangeStart: 'kx-live-range-start', dayRangeEnd: 'kx-live-range-end', dayInRange: 'kx-live-inrange', dayToday: 'live-day-today', dayOutsideMonth: 'kx-live-outside', dayDisabled: 'kx-live-disabled', }} /> </RangePicker.Popover> <div className="kx-live-value"> <code>{range.start?.slice(0, 10) ?? 'null'}</code> → <code>{range.end?.slice(0, 10) ?? 'null'}</code> </div> </RangePicker> ); }
결과
Loading...
<RangePicker> (Root)
| Prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
value | DateRange | - | 제어형 범위. |
defaultValue | DateRange | - | 비제어형 초기 범위. |
onChange | (range: DateRange) => void | - | 모든 변경(부분 포함)에 호출. |
disabled | DisabledRule[] | boolean | false | 비활성 규칙 또는 전체 비활성. |
readOnly | boolean | false | 변경 방지. |
weekStartsOn | 0 | 1 | locale 에서 추론 | 주 시작. 생략하면 locale 에서 추론하고, 명시한 prop 이 우선한다. |
displayFormat | string | 'yyyy-MM-dd' | 포맷 문자열. |
locale | string | 'en-US' | BCP 47 로케일. |
dir | 'ltr' | 'rtl' | 'ltr' | 레이아웃 방향. 'rtl'이면 캘린더 그리드에 dir="rtl"이 붙고 ArrowLeft/ArrowRight가 반전된다(WAI-ARIA grid 패턴). 국제화 참고. |
displayTimezone | string | - | IANA 타임존. 설정하면 Input 포맷·Calendar 하이라이트·onChange 값이 모두 이 타임존의 civil day 기준이 된다. Timezone 참고. |
labels | Partial<RangePickerLabels> | - | ARIA 라벨 재정의(기본값은 영어). |
adapter | DateAdapter | DateFnsAdapter | 커스텀 어댑터. |
children | ReactNode | - | 서브 컴포넌트. |
DateRange
type DateRange = {
start: ISODateString | null;
end: ISODateString | null;
};
선택 중에는 end >= start를 강제하지 않습니다. 커밋 시점에 자동 스왑. onChange에서 { start, end } 형태를 항상 받습니다.
<RangePicker.Input>
입력 두 개(start용 하나, end용 하나)가 필요합니다. 먼저 렌더된 입력이 Floating UI 기준점이 됩니다.
| Prop | 타입 | 설명 |
|---|---|---|
part | 'start' | 'end' | 필수. 이 입력이 관리하는 범위 측. |
format | string | Root의 displayFormat 오버라이드. |
<RangePicker.Popover>
DatePicker.Popover와 동일한 동작. role="dialog", 바깥 클릭 해제, Escape 지원.
<RangePicker.Calendar>
범위 강조가 적용된 월 그리드.
| Prop | 타입 | 설명 |
|---|---|---|
classNames | RangePickerCalendarClassNames | 스타일. |
selectionMode | 'range' | 'week' (기본 'range') | 'week' 이면 클릭 한 번에 주 전체를 선택한다. WeekPicker.Calendar 가 바로 이 컴포넌트에 selectionMode 를 'week' 으로 고정한 것이다. |
weekAnchor | 'calendar' | 'clicked' (기본 'calendar') | selectionMode="week" 일 때만 유효하다. 'calendar' 는 weekStartsOn 경계로 스냅하고, 'clicked' 는 클릭한 날에 고정된 7일 구간을 쓴다. |
fixedWeeks | boolean (기본 false) | 항상 6주 행을 렌더. 지정하지 않으면 4~6행이라 달마다 popover 높이가 바뀐다. |
showWeekNumber | boolean (기본 false) | 그리드 왼쪽에 ISO 8601 주차 열(1~53)을 렌더한다. 이 열은 WAI-ARIA grid 데이터 영역 밖의 <th scope="row"> 라 날짜 셀 키보드 내비게이션에 영향을 주지 않는다. classNames 의 weekNumberHeader / weekNumber 키로 스타일링한다. |
type RangePickerCalendarClassNames = {
root?: string;
header?: string;
title?: string;
navButton?: string;
grid?: string;
gridRow?: string;
gridCell?: string;
day?: string;
dayRangeStart?: string; // 범위의 시작 날짜
dayRangeEnd?: string; // 범위의 끝 날짜
dayInRange?: string; // 시작과 끝 사이 날짜
dayToday?: string;
dayDisabled?: string;
dayOutsideMonth?: string;
weekdayHeader?: string;
weekNumberHeader?: string; // showWeekNumber 를 켰을 때만 렌더된다
weekNumber?: string; // showWeekNumber 를 켰을 때만 렌더된다
};
data-* 어트리뷰트
날짜 버튼은 범위를 인식하는 상태 어트리뷰트를 방출합니다(활성일 때만 존재). 스타일링 참고.
| 어트리뷰트 | 활성 조건 |
|---|---|
data-range-start | 날짜가 범위의 시작일. |
data-range-end | 날짜가 범위의 종료일. |
data-in-range | 날짜가 시작과 종료 사이(배타적)에 있음. |
data-today | 날짜가 오늘. |
data-focused | 날짜가 키보드 포커스를 가짐. |
data-outside-month | 날짜가 인접한 월에 속함. |
<RangePicker.Presets>
빠른 선택 버튼 컨테이너.
| Prop | 타입 | 설명 |
|---|---|---|
classNames | RangePickerPresetsClassNames | 스타일. |
children | ReactNode | <RangePicker.Preset> 자식. |
type RangePickerPresetsClassNames = {
root?: string;
preset?: string;
presetActive?: string;
};
<RangePicker.Preset>
단일 프리셋 버튼. 내장 value 키 또는 커스텀 range를 제공하세요.
| Prop | 타입 | 설명 |
|---|---|---|
value | PresetKey | 내장 프리셋 키. |
range | DateRange | 커스텀 범위 (value 대신). |
children | ReactNode | 버튼 라벨. |
내장 PresetKey
type PresetKey =
| 'today'
| 'yesterday'
| 'last7days'
| 'last30days'
| 'thisWeek'
| 'lastWeek'
| 'thisMonth'
| 'lastMonth'
| 'thisYear';
패턴
사이드바 프리셋 + 캘린더
<RangePicker value={range} onChange={setRange}>
<div className="flex gap-2">
<RangePicker.Input part="start" />
<RangePicker.Input part="end" />
</div>
<RangePicker.Popover className="flex gap-4 p-3">
<RangePicker.Presets className="flex flex-col gap-1">
<RangePicker.Preset value="today">오늘</RangePicker.Preset>
<RangePicker.Preset value="last7days">지난 7일</RangePicker.Preset>
<RangePicker.Preset value="last30days">지난 30일</RangePicker.Preset>
<RangePicker.Preset value="thisMonth">이번 달</RangePicker.Preset>
</RangePicker.Presets>
<RangePicker.Calendar />
</RangePicker.Popover>
</RangePicker>
라이브 에디터
function RangeWithPresets() { const [range, setRange] = React.useState({ start: null, end: null }); return ( <RangePicker value={range} onChange={setRange}> <div className="kx-live-row"> <RangePicker.Input part="start" className="kx-live-input" placeholder="시작" /> <RangePicker.Input part="end" className="kx-live-input" placeholder="종료" /> </div> <RangePicker.Popover className="kx-live-popover kx-live-popover--split"> <RangePicker.Presets classNames={{ root: 'kx-live-presets', preset: 'kx-live-preset', presetActive: 'kx-live-preset-active', }} > <RangePicker.Preset value="today">오늘</RangePicker.Preset> <RangePicker.Preset value="yesterday">어제</RangePicker.Preset> <RangePicker.Preset value="last7days">지난 7일</RangePicker.Preset> <RangePicker.Preset value="last30days">지난 30일</RangePicker.Preset> <RangePicker.Preset value="thisMonth">이번 달</RangePicker.Preset> <RangePicker.Preset value="lastMonth">지난달</RangePicker.Preset> </RangePicker.Presets> <RangePicker.Calendar classNames={{ header: 'kx-live-header', title: 'kx-live-title', navButton: 'kx-live-nav', grid: 'kx-live-grid', gridCell: 'kx-live-cell', weekdayHeader: 'kx-live-weekday', day: 'kx-live-day-range', dayRangeStart: 'kx-live-range-start', dayRangeEnd: 'kx-live-range-end', dayInRange: 'kx-live-inrange', dayToday: 'live-day-today', dayOutsideMonth: 'kx-live-outside', dayDisabled: 'kx-live-disabled', }} /> </RangePicker.Popover> </RangePicker> ); }
결과
Loading...
커스텀 프리셋
<RangePicker.Preset
range={{
start: '2026-01-01T00:00:00.000Z',
end: '2026-06-30T00:00:00.000Z',
}}>
2026 상반기
</RangePicker.Preset>
범위 제약
<RangePicker
value={range}
onChange={setRange}
disabled={[{ before: '2026-01-01T00:00:00.000Z' }]}>
...
</RangePicker>
네이티브 폼 제출
각 입력에 name을 지정하면 화면에는 포맷된 날짜를 표시하되, hidden input이
각 시작·종료 값을 UTC-ISO 문자열로 제출합니다.
<RangePicker defaultValue={range}>
<RangePicker.Input part="start" name="startDate" />
<RangePicker.Input part="end" name="endDate" />
</RangePicker>