DateTimePicker
날과 시각을 한 팝업에서 고릅니다. 달력과 시계는 정확히 같은 높이로 나란히 서고, 그래서 팝업은 크기가 다른 두 덩어리가 아니라 하나의 사각형입니다.
import { DateTimePicker } from 'neba';
<DateTimePicker label="게시 시각" placeholder="순간을 고르세요" minuteStep={15} clearable />;Props
| Prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| variant공통 | 'solid' | 'outline' | 'text' | 'outline' | 표면의 무게. 채움 / 하이라인 / 없음 |
| size공통 | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'md' | 높이와 타입 스케일 |
| color공통 | 'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info' | 'primary' | 의미론적 색 역할. 임의 색상값은 받지 않습니다 |
| density공통 | 'default' | 'compact' | 'default' | 여백만 바꿉니다. 높이와 글자 크기는 그대로 |
| elevation공통 | 0 | 1 | 2 | 3 | 0 | 그림자 깊이. 0은 그림자 없음 |
| value | Date | null | — | 선택된 순간. onValueChange와 함께 제어 컴포넌트로 씁니다 |
| defaultValue | Date | null | — | 초기 값 |
| onValueChange | (value: Date | null) => void | — | 선택이 바뀔 때 |
| defaultMonth | Date | — | 값이 없을 때 달력이 열리는 달. 기본값은 이번 달 |
| minDate | Date | null | — | 고를 수 있는 가장 이른 순간. DatePicker와 달리 시각까지 읽습니다 — 그 날은 달력에 남고, 시계에서 그 앞 시간들이 빠집니다 |
| maxDate | Date | null | — | minDate의 반대쪽 끝 |
| shouldDisableDate | (date: Date) => boolean | — | 범위 안이지만 고를 수 없는 날 — 주말, 공휴일, 이미 예약된 방. 셀은 목록에 남은 채 비활성이 됩니다 |
| weekStartsOn공통 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | — | 주가 시작하는 요일. 일요일이 0입니다. 기본값은 로케일이 말하는 것 |
| hour12 | boolean | — | 12시간 다이얼과 오전/오후 열. 기본값은 로케일이 하는 대로 |
| showSeconds | boolean | false | 초 열을 추가합니다 |
| hourStep | number | 1 | 시 열의 간격 |
| minuteStep | number | 1 | 분 열의 간격 |
| secondStep | number | 1 | 초 열의 간격 |
| shouldDisableTime | (value: Date, unit: 'hour' | 'minute' | 'second' | 'meridiem') => boolean | — | 개별 행을 막습니다. 열마다 행마다 한 번씩, 그 행이 만들 순간과 어느 열인지를 받습니다 |
| showNowButton | boolean | true | 푸터에 지금으로 가는 단축 버튼 |
| open | boolean | — | 팝업이 열려 있는지. onOpenChange와 함께 제어 컴포넌트로 씁니다 |
| defaultOpen | boolean | false | 처음에 열린 채로 시작 |
| onOpenChange | (open: boolean) => void | — | 열리거나 닫힐 때 |
| locale | string | — | BCP 47 태그. 월·요일 이름, 헤더의 연/월 버튼 순서, 트리거의 표기를 정합니다. 기본값은 브라우저의 로케일 |
| format | Intl.DateTimeFormatOptions | — | 트리거가 값을 쓰는 방식. Intl에 그대로 넘어갑니다 |
| placeholder | ReactNode | — | 아무것도 고르지 않았을 때 트리거에 보이는 내용 |
| clearable | boolean | false | 값을 비우는 ×를 트리거에 답니다 |
| closeOnSelect | boolean | false | 날을 고르는 즉시 닫습니다. 순간은 날 *그리고* 시각이므로, 둘 중 첫 번째에서 닫으면 두 번째를 묻지 못한 채 끝납니다 |
| labels | Partial<PickerLabels> | — | 스크린 리더가 듣는 문자열들. 열여덟 개가 한 벌이라 프롭 하나로 받습니다 — 날짜 이름은 여기 없고 Intl이 만듭니다 |
| name | string | — | 폼 제출 시의 필드 이름. 값은 YYYY-MM-DDTHH:MM, 로컬 기준입니다 |
| fullWidth | boolean | false | 컨테이너 너비만큼 확장 |
| label | ReactNode | — | 컨트롤과 연결되는 라벨. Base UI Field가 묶어 줍니다 |
| description | ReactNode | — | 보조 설명 |
| error | ReactNode | — | 오류 메시지. 값이 있으면 invalid 상태도 함께 켜지고 색 계열이 danger로 넘어갑니다 |
| invalid | boolean | !!error | 메시지 없이 invalid만 켭니다. 외부 폼 라이브러리가 유효성을 가질 때 |
| readOnly | boolean | false | 값은 보이지만 바꿀 수 없음. 색과 가장자리는 유지한 채 채도만 빠집니다 |
| disabled | boolean | false | 사용 불가. 색 계열을 버리고 중립 회색이 됩니다 |
나머지는 전부 DatePicker와 TimePicker의 것 그대로입니다 — 같은 세 가지 달력 뷰, 같은 열 동작, 같은 Date 값. 여기서 읽을 만한 것은 정말로 다른 부분입니다.
범위를 시각까지 읽습니다
DateTimePicker가 필드 두 개를 나란히 놓는 대신 별도의 컴포넌트일 값을 하는 대목입니다.
DatePicker의 minDate는 날짜 단위입니다 — 27일은 있거나 없거나입니다. 여기서는 아닙니다. 스케줄 폼이 실제로 필요로 하는 규칙은 "지금 이전은 안 됨"이고, 지금은 날인 동시에 시각이기 때문입니다. 그래서 27일 09:30이 minDate이면 달력에서 27일은 그대로 고를 수 있고, 시계에서 오전이 흐려집니다.
시계 쪽 검사는 TimePicker가 하는 구간 비교를, 열이 어느 날에 쓰고 있는지 알 수 있도록 절대 시간축 위로 옮긴 것입니다. 경계가 되는 날에는 최솟값 이전 시간들이 막히고, 그 뒤의 날에는 하나도 막히지 않습니다.
값은 하나, 반쪽은 둘, 순서는 없음
날을 고르면 날이 바뀌고 시계는 그대로입니다. 시를 고르면 시계가 바뀌고 날은 그대로입니다. 날짜를 고칠 때마다 시각을 자정으로 되돌리는 피커는 순간을 고르는 일을 순서 있는 작업으로 만듭니다. 팝업을 쓰인 순서대로 읽는 사람은 없습니다.
closeOnSelect의 기본값이 false이고 푸터에 Done이 있는 것도 같은 이유입니다. 순간은 날 그리고 시각이므로, 둘 중 첫 번째에서 닫으면 두 번째를 묻지 못한 채 끝납니다.
글리프는 하나입니다
트리거는 달력을 달고 시계는 달지 않습니다. 컨트롤은 한 번에 두 가지를 말할 수 없고, 읽는 사람이 눈으로 찾는 쪽은 날짜입니다.
접근성
달력은 role="grid", 시계는 role="listbox" 열들입니다 — 이 컴포넌트를 이루는 두 컴포넌트에서와 정확히 같습니다. DatePicker와 TimePicker를 보세요. 트리거는 Intl로 양쪽을 한 문자열에 씁니다. 스크린 리더가 읽는 것은 이어 붙여야 할 두 필드가 아니라 하나의 문장입니다.