날짜 · 시각
칸에 바로 적을 수도, 달력에서 고를 수도 있습니다. 값은 늘 문자열입니다: 날짜 'YYYY-MM-DD', 시각 'HH:mm'.
칸은 입력칸과 같은 파인 칸 안의 연 · 월 · 일 조각(spinbutton)입니다. 달력은 드롭다운 층(60)에 뜨는 판이고 모서리를 꺾지 않습니다. 고른 날은 primary 면, 오늘은 굵기와 점, 고를 수 없는 날은 가로줄로 말합니다. 시각은 한국어 · 일본어가 24시간, 영어가 12시간이 기본입니다. react-day-picker · date-fns · radix-ui 가 필요합니다.
- import
- import { DatePicker, DateRangePicker, DateField, Calendar, TimeField, toUtcIso, fromUtcIso } from "@uyoolabs/ui/date";
- 쓰는 곳
- 마감일(DatePicker) · 결제 리포트 · 감사 로그 기간(DateRangePicker, 빠른 선택) · 회의 예약 날짜 + 시작 · 끝(DatePicker + TimeField options) · 공지 게시 기간 · 달력 판만 페이지에 둘 때(Calendar).
- 쓰지 않는 경우
- 날짜와 시각을 한 칸에 섞지 않습니다(DateField + TimeField 를 나란히). 생일 · 먼 과거를 달력 칸만으로 고르게 하지 않습니다(칸에 적거나 captionLayout="dropdown"). 주말 · 공휴일을 빨갛게 칠하지 않습니다(빨강은 오류). ‘10/11’ 처럼 언어마다 순서가 다른 숫자만의 날짜를 쓰지 않습니다.
- 시간대
- 날짜만 고르는 칸은 시간대가 없는 달력 날짜로 주고받습니다. 시각이 붙으면 toUtcIso(날짜, 시각, 시간대)로 UTC 를 보내고, 보여 줄 때 fromUtcIso 로 바꿉니다. 오프셋은 그 날짜의 것이라 서머타임 건너편도 맞습니다. 없는 시각(서머타임 시작 02:30)은 null, 두 번 있는 시각은 앞의 것입니다.
DatePicker
칸 + 달력 단추 + 팝오버 달력. 고르면 닫히고 포커스는 달력 단추로 돌아옵니다.
20261024
값: 2026-10-24
| 키 | 동작 |
|---|---|
| ←·→ | 조각 사이를 옮긴다(연 · 월 · 일, 시 · 분 · 오전/오후) |
| ↑·↓ | 조각 값을 하나씩 바꾼다. 끝에서 돈다(12 → 1, 59 → 0) |
| 숫자 | 자릿수가 차면 다음 조각으로 넘어간다. 12시간 칸의 시에 ‘15’ 는 오후 3시 |
| Backspace | 조각을 비운다 |
| 붙여 넣기 | ‘2026-10-24’ · ‘2026. 10. 24.’ · ‘2026/10/24’ · ‘14:30’ · ‘2:30 PM’ · ‘오후 2:30’ 을 통째로 읽는다 |
| Alt+↓ | 달력(시각 목록)을 연다. 포커스는 고른 날, 없으면 오늘 |
| 키 | 동작 |
|---|---|
| ←·→ | 하루 |
| ↑·↓ | 한 주 |
| Home·End | 주의 처음 · 끝 |
| PageUp·PageDown | 이전 · 다음 달(Shift 를 더하면 해) |
| Enter·Space | 고른다. 단일은 닫히고 포커스는 달력 단추로 |
| Esc | 바꾸지 않고 닫는다. 포커스는 달력 단추로 |
| 범위 | Enter 두 번으로 첫 날 · 끝 날, Tab 으로 [적용]. 적용 전 Esc 는 처음 값 |
| ↑·↓ | 빠른 선택 목록에서 옮기고 Enter 로 범위를 채운다(적용은 따로) |
DateRangePicker
빠른 선택 · 달력(넓으면 두 달) · 바닥 요약. [적용]을 눌러야 값이 바뀝니다.
TimeField
시 · 분(· 오전/오후) 조각. 보는 사람과 다른 시간대면 약어(EDT)를 붙입니다. options 는 30분 목록 단추입니다.
20261024
1430KST
1530KST
보낼 값: 2026-10-24T05:30:00Z
props
소스의 타입과 JSDoc 에서 생성한 표입니다. 컴포넌트를 고치면 pnpm gen 으로 다시 만듭니다.
DatePicker
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| value | CalendarDate | null | — | 고른 날짜(제어). 'YYYY-MM-DD' 이고 시간대가 없다. 비었거나 잘못된 값이면 null |
| defaultValue | CalendarDate | null | null | 처음 날짜(비제어) |
| onValueChange | (value: CalendarDate | null) => void | — | 바른 날짜가 되거나 비었을 때 부른다. 없는 날 · 범위 밖은 null 로 알리고 칸은 오류 문장을 보인다 |
| min | CalendarDate | — | 고를 수 있는 첫날 |
| max | CalendarDate | — | 고를 수 있는 끝날 |
| isDisabled | (date: CalendarDate) => boolean | — | 고를 수 없는 날(휴무일 등) |
| name | string | — | 숨은 input 의 이름. FormData 에 'YYYY-MM-DD' 로 들어간다 |
| required | boolean | — | |
| disabled | boolean | — | |
| readOnly | boolean | — | 읽기 전용 면. 조각은 포커스를 받지만 바뀌지 않는다 |
| invalid | boolean | — | 바깥(Field)이 정한 오류. 경계를 bad 로 그리고 칸의 오류 문장은 Field 에 맡긴다 |
| lang | "ko" | "en" | "ja" | — | 요일 · 칸 순서 · 문구의 언어 |
| labels | Partial<DatePickerLabels> | — | 내장 문구 중 바꿀 것만 |
| id | string | — | Field 가 넘기는 id. 그 <label for> 가 칸의 이름이 된다 |
| aria-describedby | string | — | |
| aria-invalid | boolean | "true" | "false" | — | |
| aria-required | boolean | "true" | "false" | — | |
| aria-label | string | — | |
| aria-labelledby | string | — | |
| className | string | — | |
| weekStartsOn | 0 | 1 | — | 주의 첫날. 기본은 로캘(ko · ja · en-US 모두 일요일) |
| captionLayout | "label" | "dropdown" | — | label: 달 이름 글자. dropdown: 연 · 월 고르기(생일 · 먼 과거) |
| open | boolean | — | 달력 열림(제어) |
| defaultOpen | boolean | false | 처음 열림(비제어) |
| onOpenChange | (open: boolean) => void | — | |
| today | CalendarDate | — | 오늘. 기본은 보는 사람의 오늘이다. 픽스처 · 테스트에서 고정한다 |
DateRangePicker
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| min | CalendarDate | — | 고를 수 있는 첫날 |
| max | CalendarDate | — | 고를 수 있는 끝날 |
| isDisabled | (date: CalendarDate) => boolean | — | 고를 수 없는 날(휴무일 등) |
| name | string | — | 숨은 input 의 이름. FormData 에 'YYYY-MM-DD' 로 들어간다 |
| required | boolean | — | |
| disabled | boolean | — | |
| readOnly | boolean | — | 읽기 전용 면. 조각은 포커스를 받지만 바뀌지 않는다 |
| invalid | boolean | — | 바깥(Field)이 정한 오류. 경계를 bad 로 그리고 칸의 오류 문장은 Field 에 맡긴다 |
| lang | "ko" | "en" | "ja" | "ko" | 요일 · 칸 순서 · 문구의 언어 |
| labels | Partial<DatePickerLabels> | — | 내장 문구 중 바꿀 것만 |
| id | string | — | Field 가 넘기는 id. 그 <label for> 가 칸의 이름이 된다 |
| aria-describedby | string | — | |
| aria-invalid | boolean | "true" | "false" | — | |
| aria-required | boolean | "true" | "false" | — | |
| aria-label | string | — | |
| aria-labelledby | string | — | |
| className | string | — | |
| weekStartsOn | 0 | 1 | — | 주의 첫날. 기본은 로캘(ko · ja · en-US 모두 일요일) |
| open | boolean | — | 달력 열림(제어) |
| defaultOpen | boolean | false | 처음 열림(비제어) |
| onOpenChange | (open: boolean) => void | — | |
| today | CalendarDate | — | 오늘. 기본은 보는 사람의 오늘이다. 픽스처 · 테스트에서 고정한다 |
| value | DateRange | — | 적용한 기간(제어) |
| defaultValue | DateRange | { from: null, to: null } | 처음 기간(비제어) |
| onValueChange | (range: DateRange) => void | — | 적용을 눌렀을 때만 부른다. 두 번 눌러 고르는 동안에는 부르지 않는다 |
| presets | DateRangePreset[] | false | — | 빠른 선택. 기본: 오늘 · 어제 · 지난 7일 · 지난 30일 · 이번 달 · 지난달 + 사용자 지정. false 면 없다 |
| months | 1 | 2 | — | 나란히 보일 달 수. 기본은 화면 폭이 640 이상이면 2, 아니면 1 |
DateField
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| value | CalendarDate | null | — | 고른 날짜(제어). 'YYYY-MM-DD' 이고 시간대가 없다. 비었거나 잘못된 값이면 null |
| defaultValue | CalendarDate | null | — | 처음 날짜(비제어) |
| onValueChange | (value: CalendarDate | null) => void | — | 바른 날짜가 되거나 비었을 때 부른다. 없는 날 · 범위 밖은 null 로 알리고 칸은 오류 문장을 보인다 |
| min | CalendarDate | — | 고를 수 있는 첫날 |
| max | CalendarDate | — | 고를 수 있는 끝날 |
| isDisabled | (date: CalendarDate) => boolean | — | 고를 수 없는 날(휴무일 등) |
| name | string | — | 숨은 input 의 이름. FormData 에 'YYYY-MM-DD' 로 들어간다 |
| required | boolean | — | |
| disabled | boolean | — | |
| readOnly | boolean | — | 읽기 전용 면. 조각은 포커스를 받지만 바뀌지 않는다 |
| invalid | boolean | — | 바깥(Field)이 정한 오류. 경계를 bad 로 그리고 칸의 오류 문장은 Field 에 맡긴다 |
| lang | "ko" | "en" | "ja" | — | 요일 · 칸 순서 · 문구의 언어 |
| labels | Partial<DatePickerLabels> | — | 내장 문구 중 바꿀 것만 |
| id | string | — | Field 가 넘기는 id. 그 <label for> 가 칸의 이름이 된다 |
| aria-describedby | string | — | |
| aria-invalid | boolean | "true" | "false" | — | |
| aria-required | boolean | "true" | "false" | — | |
| aria-label | string | — | |
| aria-labelledby | string | — | |
| className | string | — |
Calendar
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| min | CalendarDate | — | 고를 수 있는 첫날. 그 앞은 고를 수 없는 날(가로줄)이고, 그 달 앞으로는 넘기지 못한다 |
| max | CalendarDate | — | 고를 수 있는 끝날 |
| isDisabled | (date: CalendarDate) => boolean | — | 고를 수 없는 날(휴무일 등). 날 칸은 흐린 글자 + 가로줄이고 aria-disabled 다 |
| weekStartsOn | 0 | 1 | — | 주의 첫날. 기본은 로캘(ko · ja · en-US 모두 일요일) |
| captionLayout | "label" | "dropdown" | — | label: 달 이름 글자. dropdown: 연 · 월 고르기(생일 · 먼 과거) |
| today | CalendarDate | — | 오늘. 기본은 보는 사람의 오늘이다. 픽스처 · 테스트에서 고정한다 |
| defaultMonth | CalendarDate | — | 처음 보일 달(그 날짜가 든 달). 기본은 고른 날, 없으면 오늘 |
| month | CalendarDate | — | 보이는 달(제어). 그 날짜가 든 달을 보인다 |
| onMonthChange | (month: CalendarDate) => void | — | 이전 · 다음 달로 넘기거나 키보드로 달을 건넜을 때. 그 달의 1일 |
| numberOfMonths | 1 | 2 | — | 나란히 보일 달 수 |
| autoFocus | boolean | — | 처음 그릴 때 고른 날(없으면 오늘)에 포커스를 둔다. 팝오버로 열 때 쓴다 |
| lang | "ko" | "en" | "ja" | — | 내장 문구의 언어. 요일 · 달 이름도 이 언어다 |
| labels | Partial<DatePickerLabels> | — | 내장 문구 중 바꿀 것만 |
| className | string | — | |
| onPreviewChange | (date: CalendarDate | null) => void | — | 범위를 고르는 중 손이나 키보드 포커스가 놓인 날. 미리보기 띠를 거기까지 그린다 |
| mode필수 | "single" | "range" | — | |
| selected | CalendarDate | null | DateRange | — | |
| onSelect | (date: CalendarDate) => void | (range: DateRange) => void | — | 날을 누를 때마다 부른다. 첫 날을 누르면 to 가 null 이다 |
TimeField
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| value | WallTime | null | — | 벽시계 시각(제어). 'HH:mm' 24시간 |
| defaultValue | WallTime | null | null | 처음 시각(비제어) |
| onValueChange | (value: WallTime | null) => void | — | 바른 시각이 되거나 비었을 때 부른다. 다 채우지 않았거나 없는 시각이면 null |
| hourCycle | 12 | 24 | — | 기본: 로캘(ko 24 · ja 24 · en 12, 결정 #46) |
| step | number | 1 | 분 조각 ↑ ↓ 단위 |
| min | WallTime | — | |
| max | WallTime | — | |
| timeZone | string | — | 표시 시간대(IANA). 기본은 보는 사람의 시간대 |
| showTimeZone | boolean | "auto" | "auto" | 시간대 약어를 칸 끝에 붙일지. "auto" 는 보는 사람의 시간대와 timeZone 이 다를 때만 |
| referenceDate | CalendarDate | — | 이 날짜 기준으로 약어 · 서머타임을 본다. 없으면 오늘 |
| options | boolean | { optionStep?: number; start?: WallTime } | — | 30분 단위 목록 단추. 끝 시각이면 start 를 주어 걸린 시간을 옆에 단다 |
| name | string | — | 숨은 input 의 이름. FormData 에 'HH:mm' |
| required | boolean | — | |
| disabled | boolean | — | |
| readOnly | boolean | — | |
| invalid | boolean | — | 바깥(Field)이 정한 오류 |
| lang | "ko" | "en" | "ja" | "ko" | |
| labels | Partial<TimeFieldLabels> | — | |
| id | string | — | |
| aria-describedby | string | — | |
| aria-invalid | boolean | "true" | "false" | — | |
| aria-required | boolean | "true" | "false" | — | |
| aria-label | string | — | |
| aria-labelledby | string | — | |
| className | string | — |