NumberField
숫자만 담는 필드입니다. 셸은 픽셀 단위로 TextField의 것이고, 그 위에 스테핑과 범위 제한과 서식이 얹힙니다.
tsx
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 | — | 어느 로케일로 쓰고 읽을지. 기본은 런타임의 것 |
| 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 | 'Increase' | 증가 버튼의 접근성 이름 |
| decrementLabel | string | 'Decrease' | 감소 버튼의 접근성 이름 |
| name | string | — | 폼 제출 시의 필드 이름 |
| readOnly | boolean | false | 값은 보이지만 바꿀 수 없음. 색과 가장자리는 유지한 채 채도만 빠집니다 |
| disabled | boolean | false | 사용 불가. 색 계열을 버리고 중립 회색이 됩니다 |
<input type="number">가 아닙니다
네이티브 숫자 입력은 브라우저마다 다른 곳의 모서리를 깎고, 로케일을 무시하고, 페이지 스크롤과 싸우는 휠 제스처를 제공하고, 필드에 말이 안 되는 것이 들어 있으면 빈 string을 건네줍니다. 이 컴포넌트는 지킬 만한 것만 지키고 각각에 답합니다.
value는number | null이고,null이 비어 있음입니다. 파싱해야 하는 문자열이 아닙니다.format은Intl.NumberFormatOptions입니다. 필드는$1,240이나7.5%를 보여 주면서 값으로는1240과0.075를 보고합니다.- 방향키는
step만큼, Shift는largeStep만큼, Alt는smallStep만큼 움직입니다. - 휠은
allowWheelScrub이 그러라고 하지 않는 한 아무것도 하지 않습니다. 포인터 아래에서 스크롤되는 페이지와 포인터 아래에서 바뀌는 필드는 같은 제스처이고, 의도된 것은 둘 중 하나뿐입니다.
예시
스테퍼
end는 모두가 봐 온 스피너입니다. split은 숫자의 양옆에 마이너스와 플러스를 두는데, 타이핑하기보다 톡톡 건드려 맞추는 수량을 위한 것입니다. none은 버튼만 빼고 나머지는 그대로 둡니다.
절반 높이의 셰브런을 세로로 쌓는 형태는 일부러 두지 않았습니다. xs에서는 화살표 하나가 3픽셀도 되지 않고, 그 정도로 작은 표적은 아무도 맞히지 못하는 표적입니다.
서식
필드가 무엇을 보여 주든 value는 순수한 숫자로 남습니다.
Variant
크기
스테퍼는 em으로 크기가 정해지므로 자기만의 사다리를 갖는 대신 숫자를 따라갑니다 — 그리고 필드는 같은 size의 Button·TextField·Select와 같은 줄에 섭니다.
상태
읽기 전용은 스테퍼를 비활성화하는 대신 아예 치웁니다. 보이는데 누를 때마다 거절하는 버튼은 없는 버튼보다 나쁩니다. 숫자는 여전히 선택할 수 있습니다 — 읽기 전용 필드도 복사해 가는 대상이기 때문입니다.
접근성
- 파싱, 범위 제한, 스테퍼의 길게 누르기 반복은 모두 Base UI가 담당합니다.
- 보이는 것은
<input type="number">가 아니라inputmode="numeric"과 _Number field_라는aria-roledescription을 단 텍스트 입력입니다. 그 옆에는min·max·step을 들고 있는 숨은<input type="number">가 있고, 폼이 제출하고 브라우저가 검증하는 것은 이쪽입니다. 둘을 갈라 놓은 덕분에 보이는 필드는 브라우저가 파싱을 거부하는 일 없이$1,240을 보여 줄 수 있습니다. label이 접근성 이름이 되고, 스테퍼의 이름은incrementLabel과decrementLabel이 짓습니다. 스테퍼는 탭 순서에서 빠져 있는데, 필드 자체의 방향키가 이미 그 일을 하기 때문입니다.min이나max에 닿은 스테퍼는disabled입니다 — 흐려지는 대신, 라이브러리의 다른 모든 비활성 컨트롤과 같이 색 계열이 바뀝니다.