NumberField
숫자만 입력받는 필드입니다. 값을 단계적으로 올리고 내리는 stepper, 범위 제한, 서식 표시가 함께 제공됩니다.
import { NumberField } from 'neba';
<NumberField label="좌석" defaultValue={3} min={1} max={20} />;Props
| Prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| variant공통 | 'solid' | 'outline' | 'text' | 'outline' | 표면의 무게. TextField와 같은 셸입니다 |
| size공통 | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'md' | 높이와 타입 스케일. 스테퍼는 em이므로 숫자를 따라갑니다 |
| color공통 | 'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info' | 'primary' | 의미론적 색 역할. 표면은 흰색이므로 가장자리·포커스 링·캐럿·스테퍼의 호버에 나타납니다 |
| density공통 | 'default' | 'compact' | 'default' | 여백만 바꿉니다. 높이와 글자 크기는 그대로 |
| elevation공통 | 0 | 1 | 2 | 3 | 0 | 그림자 깊이. 0은 그림자 없음 |
| value | number | null | — | 값. null이 비어 있음입니다. 파싱해야 하는 문자열이 아닙니다 |
| defaultValue | number | — | 초기 값 |
| onValueChange | (value: number | null) => void | — | 값이 바뀔 때마다. 타이핑, 스테핑, 휠 |
| onValueCommitted | (value: number | null) => void | — | 값이 자리를 잡을 때. 타이핑 후 blur, 누르기를 뗄 때 |
| min | number | — | 범위의 시작. 스테핑은 여기서 멈춥니다 |
| max | number | — | 범위의 끝 |
| step | number | 'any' | 1 | 한 걸음의 크기. any는 step 검증을 끕니다 |
| largeStep | number | 10 | Shift를 누른 채의 한 걸음 |
| smallStep | number | 0.1 | Alt를 누른 채의 한 걸음 |
| snapOnStep | boolean | false | 스테핑이 step의 배수에 붙는지 |
| allowWheelScrub | boolean | false | 포커스된 채 호버 중일 때 휠이 값을 바꾸는지. 포인터 아래에서 스크롤되는 페이지와 바뀌는 필드는 같은 제스처이고, 의도된 것은 하나뿐입니다 |
| format | Intl.NumberFormatOptions | — | 숫자를 어떻게 쓸지, 통화, 백분율, 소수 자릿수. 필드는 $1,240을 보여 주고 값으로는 1240을 보고합니다 |
| locale | Intl.LocalesArgument | — | 어느 로케일로 쓰고 읽을지. 기본은 런타임의 것. BCP 47 문자열이면 두 스테퍼의 이름도 이 언어로 씁니다 |
| steppers | 'end' | 'split' | 'none' | 'end' | 스테퍼가 앉는 자리. split은 숫자 양옆, none은 버튼 없음 |
| label | ReactNode | — | 컨트롤과 연결되는 라벨. Base UI Field가 묶어 줍니다 |
| description | ReactNode | — | 보조 설명 |
| error | ReactNode | — | 오류 메시지. 값이 있으면 invalid 상태도 함께 켜지고 색 계열이 danger로 넘어갑니다 |
| invalid | boolean | !!error | 메시지 없이 invalid만 켭니다. 외부 폼 라이브러리가 유효성을 가질 때 |
| startIcon | ReactNode | — | 숫자 앞에 놓이는 내용, 통화 기호, 단위 |
| endIcon | ReactNode | — | 숫자 뒤, 스테퍼 앞에 놓이는 내용 |
| fullWidth | boolean | false | 컨테이너 너비만큼 확장 |
| incrementLabel | string | — | 증가 버튼의 접근성 이름 |
| decrementLabel | string | — | 감소 버튼의 접근성 이름 |
| name | string | — | 폼 제출 시의 필드 이름 |
| readOnly | boolean | false | 값은 보이지만 바꿀 수 없음. 색과 가장자리는 유지한 채 채도만 빠집니다 |
| disabled | boolean | false | 사용 불가. 색 계열을 버리고 중립 회색이 됩니다 |
| shortcuts | NebaShortcuts<HTMLInputElement> | — | Shortcut이 그리는 그대로 쓴 키 조합과 그때 할 일. { 'Mod+Enter': send } 형태입니다. Mod는 Mac에서 Command, 나머지에서 Control입니다. root가 아니라 input에 붙습니다. onKeyDown은 라벨과 메시지가 함께 있는 열에 떨어지므로 currentTarget이 필드가 아닙니다 |
| placeholder | string | — | 값이 없을 때 필드에 보이는 글자 |
| required | boolean | — | 폼을 제출하기 전에 값이 있어야 하는지 |
| classNames | NebaSlots<'label' | 'shell' | 'control' | 'description' | 'error' | 'stepper'> | — | 루트 뒤에 있는 각 파트의 class. 루트 자체는 className이 맡으므로 root 키는 없습니다 |
<div>의 native 속성은 root로 전달됩니다. color와 defaultValue만 위 표와 이름이 겹쳐 제외됩니다.
value의 타입은 number | null이며 null이 비어 있음을 뜻합니다. 파싱해야 하는 문자열이 아닙니다.
shell은 TextField와 동일하므로 같은 size의 필드와 한 줄에 놓을 수 있습니다.
예시
steppers
end는 필드 오른쪽에 증감 버튼을 모아 놓습니다. split은 숫자 양옆에 마이너스와 플러스를 두어 눌러서 맞추는 수량에 적합합니다. none은 버튼을 없애고 키보드 입력만 남깁니다.
step · largeStep · smallStep
방향키는 step만큼, Shift와 함께 누르면 largeStep만큼, Alt와 함께 누르면 smallStep만큼 값을 움직입니다. snapOnStep은 그 결과를 step의 배수에 맞춥니다.
allowWheelScrub은 기본값이 꺼짐입니다. 켜면 필드 위에서 휠로 값을 조절할 수 있지만, 페이지 스크롤과 같은 제스처를 공유하게 됩니다.
format과 locale
format은 Intl.NumberFormatOptions입니다. 필드가 $1,240이나 7.5%를 보여 주더라도 value는 1240, 0.075로 유지됩니다.
variant
size
stepper는 em 단위로 그려지므로 숫자 크기를 따라갑니다. 같은 size의 Button · TextField · Select와 높이가 맞습니다.
disabled · readOnly · error
readOnly는 stepper를 비활성 상태로 남기지 않고 아예 제거합니다. 숫자는 여전히 선택해서 복사할 수 있습니다.
shortcuts
shortcuts는 키 조합에서 할 일로 가는 map이고, 조합은 Shortcut이 그리는 표기 그대로 씁니다. Mod는 Mac에서 Command, 그 밖에서는 Control이며 modifier는 정확히 일치해야 하므로 Enter와 Mod+Enter가 함께 발동하는 일은 없습니다.
<NumberField label="수량" shortcuts={{ Enter: commit }} />root가 아니라 <input>에 붙는다는 점이 여기서는 중요합니다. className도 onKeyDown도 라벨과 아래 두 줄을 담는 열에 떨어지므로 currentTarget이 필드가 아닙니다.
대신 preventDefault를 해 주지는 않습니다. ArrowUp에 건 shortcut은 실행되고 동시에 값도 한 칸 올라갑니다. 그러지 않아야 한다면 핸들러에서 직접 막으세요.
classNames
className은 루트(라벨과 shell, 그 아래 두 줄을 담는 열)에 붙고, <input> 자체는 classNames.control로 갑니다.
<NumberField label="Seats" classNames={{ control: 'text-right', stepper: 'rounded-none' }} />slot은 label, shell, control, description, error, stepper입니다. stepper는 증가·감소 버튼을 따로 두지 않고 하나로 받습니다. 서로 다르게 생긴 stepper 한 쌍을 만들려는 사람은 없기 때문입니다. 넘긴 class가 컴포넌트 자신의 class와 어떻게 겨루는지는 prop 규약을 보세요.
접근성
- 보이는 컨트롤은
inputmode="numeric"과aria-roledescription을 가진 텍스트 입력이고, 그 옆에min·max·step을 들고 있는 hidden<input type="number">가 폼 제출과 브라우저 검증을 담당합니다. 이렇게 나뉘어 있어 보이는 필드가$1,240같은 서식을 표시할 수 있습니다. label이 accessible name이 되고, stepper 버튼의 이름은incrementLabel과decrementLabel이 정합니다.- stepper는 tab 순서에서 빠져 있습니다. 필드의 방향키가 같은 일을 합니다.
min이나max에 도달한 stepper는disabled가 됩니다.- 두 스테퍼의 접근성 이름은
locale이 정합니다. BCP 47 문자열을 넘기면 숫자와 버튼이 같은 언어로 읽힙니다.