TimePicker
하루 중의 시각을 열에서 고릅니다. 범위 검사는 행 하나가 대표하는 구간에 대해 이루어지고, 그래서 최솟값이 09:30이어도 9시 30분에 닿을 수 있습니다.
import { TimePicker } from 'neba';
<TimePicker 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 | — | 선택된 시각. Date이므로 날짜도 함께 지닙니다 — referenceDate를 보세요 |
| defaultValue | Date | null | — | 초기 값 |
| onValueChange | (value: Date | null) => void | — | 선택이 바뀔 때 |
| referenceDate | Date | today | 값이 아직 없을 때 고른 시각이 얹히는 날 |
| minTime | Date | null | — | 고를 수 있는 가장 이른 시각. 날짜는 무시하고 시계만 읽습니다. 열이 그리는 범위 단위로 비교하므로 09:30이면 9시는 남고 그 앞의 분들이 빠집니다 |
| maxTime | Date | null | — | 같은 범위의 반대쪽 끝 |
| 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 | 열을 건드리는 즉시 닫습니다. DatePicker와 달리 기본값이 false입니다 — 시각은 답이 둘이라, 첫 번째에서 닫으면 9시 30분을 고르는 데 팝업을 두 번 열어야 합니다 |
| labels | Partial<PickerLabels> | — | 스크린 리더가 듣는 문자열들. 열여덟 개가 한 벌이라 프롭 하나로 받습니다 — 날짜 이름은 여기 없고 Intl이 만듭니다 |
| name | string | — | 폼 제출 시의 필드 이름. 값은 HH:MM (초를 보이면 HH:MM:SS) |
| fullWidth | boolean | false | 컨테이너 너비만큼 확장 |
| label | ReactNode | — | 컨트롤과 연결되는 라벨. Base UI Field가 묶어 줍니다 |
| description | ReactNode | — | 보조 설명 |
| error | ReactNode | — | 오류 메시지. 값이 있으면 invalid 상태도 함께 켜지고 색 계열이 danger로 넘어갑니다 |
| invalid | boolean | !!error | 메시지 없이 invalid만 켭니다. 외부 폼 라이브러리가 유효성을 가질 때 |
| readOnly | boolean | false | 값은 보이지만 바꿀 수 없음. 색과 가장자리는 유지한 채 채도만 빠집니다 |
| disabled | boolean | false | 사용 불가. 색 계열을 버리고 중립 회색이 됩니다 |
다이얼이 아니라 열입니다
열은 시간 피커가 실제로 받는 질문에 답하는 형태입니다. "아홉 시 반"은 두 번의 시선이고, "정각이면 아무 때나"는 아예 건드리지 않는 열 하나입니다. 시계 판은 더 예쁘지만 읽으려면 transform이 필요하고, 이 라이브러리에는 그것이 없습니다.
각 열에서 선택된 행은 팝업이 열릴 때 한 번 화면 안으로 스크롤됩니다. 이 컴포넌트에서 유일한 명령형 작업이고, 선택 사항이 아닙니다 — 값이 45인데 00에서 열리는 60분짜리 열은 자기 답을 숨긴 것입니다.
hour12의 기본값은 로케일이 하는 대로이고, 12시간 열은 0…11이 아니라 다이얼을 읽는 순서인 12, 1, 2 … 11로 흐릅니다.
값은 Date입니다
문자열도, 분 단위 숫자도 아닙니다. 이 라이브러리에서 순간을 지니는 다른 모든 것이 Date이기 때문이고, 시각만 있는 값에는 서머타임 경계를 넘었다는 사실을 적어 둘 자리가 없기 때문입니다. referenceDate는 아직 비어 있을 때 고른 시각이 얹히는 날입니다. 기본값은 오늘이고, 컴포넌트가 마운트되어 있는 동안 고정됩니다 — 자정을 넘겨 열어 둔 팝업이 값을 슬그머니 다음 날로 옮기지 않도록.
예시
간격, 초, 24시간 다이얼
범위
작동하는 시간 피커와 답답한 시간 피커를 가르는 대목입니다. minTime과 maxTime은 후보 한 순간이 아니라 행이 덮는 구간에 대해 비교됩니다. 최솟값이 09:30이면 9시라는 행은 09:00:00–09:59:59를 덮고, 그 구간은 허용 범위와 겹치므로 남습니다. 대신 분 열에서 00부터 25까지가 흐려집니다.
후보 전체를 비교하는 — 그리고 가장 먼저 떠오르는 — 구현은 9를 통째로 숨기고, 9시 30분을 고를 수 없게 만듭니다.
shouldDisableTime은 그 행이 만들어 낼 순간과 그 행이 속한 열을 함께 받습니다. 규칙은 "점심시간 제외"만큼 성길 수도, 1분만큼 촘촘할 수도 있습니다.
팝업이 열린 채로 있는 이유
closeOnSelect는 여기서 false, DatePicker에서는 true입니다. 불일치가 아닙니다. 시각은 시와 분, 답이 둘인 질문이고, 첫 번째에서 닫으면 9시 30분을 고르는 데 팝업을 두 번 열어야 합니다. 그래서 푸터에 Done 버튼이 있습니다. "그게 그거다"라고 말할 무언가가 있어야 하니까요.
접근성
- 각 열은
role="option"행들로 이루어진role="listbox"이고, 이름은Hour,Minute,Second,AM/PM입니다. 네 이름 모두labels에서 오고 영어 기본값을 가집니다. - 각 열에서 선택된 행은
aria-selected를, 막힌 행은disabled속성이 아니라aria-disabled를 답니다. 그래야 도달할 수 있고, 왜 고를 수 없는지도 전해집니다. - 열 옆의 라이브 영역이 값이 바뀔 때마다 전체 시각을 읽어 줍니다. 라벨 없는 숫자 목록 세 개는, 화면을 보는 대신 읽는 사람에게는 그 자체로 아무 말도 하지 않습니다.