TextField
한 줄 또는 여러 줄 텍스트를 입력받습니다. 라벨과 설명, 오류 메시지가 하나의 컴포넌트로 묶여 있습니다.
import { TextField } from 'neba';
<TextField label="이메일" value={email} onChange={(event) => setEmail(event.target.value)} />;Props
| Prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| variant공통 | 'solid' | 'outline' | 'text' | 'outline' | 표면의 무게. solid도 색으로 채우지 않습니다. 필드가 담는 것은 사용자 데이터입니다 |
| size공통 | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'md' | 높이와 타입 스케일. Button과 같은 높이라서 한 줄에 섞어 놓아도 기준선이 맞습니다 |
| color공통 | 'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info' | 'primary' | 의미론적 색 역할. 표면은 흰색이므로 가장자리와 포커스 링, 캐럿에만 나타납니다 |
| density공통 | 'default' | 'compact' | 'default' | 여백만 바꿉니다. 높이와 글자 크기는 그대로 |
| elevation공통 | 0 | 1 | 2 | 3 | 0 | 그림자 깊이. 필드는 떠 있는 표면이 아니므로 거의 올리지 않습니다 |
| label | ReactNode | — | 컨트롤 위 라벨. Base UI Field로 연결됩니다 |
| description | ReactNode | — | 컨트롤 아래 보조 설명 |
| error | ReactNode | — | 컨트롤 아래 오류 메시지. 값이 있으면 invalid 상태도 함께 켜집니다 |
| invalid | boolean | !!error | 메시지 없이 invalid만 켭니다. 외부 폼 라이브러리가 유효성을 가질 때 |
| multiline | boolean | false | input 대신 textarea로 렌더링합니다. 나머지 축은 그대로 |
| rows | number | 3 | multiline일 때 보이는 줄 수 |
| resize | 'none' | 'vertical' | 'horizontal' | 'both' | 'vertical' | 사용자가 끌어서 크기를 바꿀 수 있는 방향. 가로는 폼의 열을 깨뜨립니다 |
| startIcon | ReactNode | — | 컨트롤 앞에 놓이는 내용 |
| endIcon | ReactNode | — | 컨트롤 뒤에 놓이는 내용 |
| loading | boolean | false | endIcon 자리에 스피너를 띄웁니다. 입력은 계속 가능합니다 |
| readOnly | boolean | false | 읽기 전용. 선택과 복사는 됩니다 |
| disabled | boolean | false | 사용 불가 |
| fullWidth | boolean | false | 컨테이너 너비만큼 확장 |
| shortcuts | NebaShortcuts<HTMLInputElement | HTMLTextAreaElement> | — | Shortcut이 그리는 그대로 쓴 키 조합과 그때 할 일. { 'Mod+Enter': send } 형태입니다. Mod는 Mac에서 Command, 나머지에서 Control입니다. 컨트롤에 붙으므로 event.currentTarget이 input 또는 textarea입니다. onKeyDown보다 먼저 실행되고 그것을 대체하지 않으며, 대신 preventDefault를 해 주지도 않습니다 |
| onChange | ChangeEventHandler<HTMLInputElement | HTMLTextAreaElement> | — | 네이티브 change 이벤트. 값만 필요하면 `onValueChange`를 쓰세요 |
| classNames | NebaSlots<'label' | 'shell' | 'control' | 'description' | 'error'> | — | 루트 뒤에 있는 각 파트의 class. 루트 자체는 className이 맡으므로 root 키는 없습니다 |
<input>의 native 속성은 그대로 전달됩니다. color와 size는 위 표의 것과 이름이 겹쳐 제외되며, onChange는 multiline일 때 <textarea> 이벤트도 받도록 넓혀져 있습니다.
예시
variant
세 가지 무게 모두 sheet를 색으로 채우지 않습니다. 필드가 담는 것은 사용자가 입력한 텍스트이고, caret과 선택 영역, placeholder가 강조색 채움 위에서는 읽히지 않기 때문입니다. color는 테두리와 focus ring, caret에 나타납니다.
size
Button과 높이가 같으므로 툴바처럼 한 줄에 섞어 놓아도 기준선이 맞습니다. 필드에서는 이 높이가 고정값이 아니라 하한입니다. 단계보다 큰 글자를 넣으면(크론 표현식, 코드, 숫자 하나) 셸이 잘리지 않고 그만큼 늘어납니다.
multiline · rows · resize
multiline은 <textarea>로 렌더링하고 나머지 축은 그대로 유지합니다. rows={1}은 한 줄 필드와 정확히 같은 높이입니다. resize의 기본값은 세로 방향만 허용합니다. 가로 리사이즈는 폼의 열 정렬을 깨뜨립니다.
startIcon · endIcon · loading
loading은 endIcon 자리에 spinner를 놓고 aria-busy를 붙이지만 입력은 막지 않습니다. 대개 방금 입력한 값 때문에 로딩 중이기 때문입니다.
error · invalid · disabled · readOnly
error에 메시지를 주면 invalid 상태가 함께 켜지고 필드 전체가 danger 계열로 옮겨갑니다. 메시지 없이 invalid 상태만 표시하려면 invalid를 직접 주세요.
value와 onChange
native <input>과 동일하게 동작합니다.
shortcuts
shortcuts는 키 조합에서 할 일로 가는 map이고, 조합은 Shortcut이 그리는 표기 그대로 씁니다. 폼이 사용자에게 보여 주는 키와 실제로 바인딩하는 키가 같은 문자열이 됩니다.
<TextField
label="메시지"
multiline
shortcuts={{
'Mod+Enter': (event) => {
event.preventDefault();
send();
},
Escape: clear
}}
/>Mod는 Mac에서 Command, 그 밖에서는 Control입니다. modifier는 정확히 일치해야 하므로 Enter와 Mod+Enter는 절대 함께 발동하지 않는 두 항목입니다.
control에 붙기 때문에 event.currentTarget이 <input> 또는 <textarea>이고, event.currentTarget.value가 방금 입력된 값입니다. 대신 preventDefault를 해 주지는 않습니다. 줄바꿈까지 막아야 하는 Mod+Enter라면 직접 부르세요. onKeyDown은 여전히 모든 키를 받고 map 다음에 실행됩니다. 둘 중 어느 쪽도 다른 쪽을 대체하지 않습니다.
classNames
className은 루트(라벨과 shell, 그 아래 두 줄을 담는 열)에 붙습니다. <input> 자체는 classNames로 갑니다. root 키는 없습니다. 그것이 이미 className이기 때문입니다.
<TextField
label="Email"
className="w-80"
classNames={{ label: 'uppercase tracking-wide', control: 'font-mono' }}
/>slot은 label, shell, control, description, error입니다. shell은 테두리와 채움, focus ring을 두른 상자이고 control은 그 안의 <input> 또는 <textarea>입니다. 넘긴 class가 컴포넌트 자신의 class와 어떻게 겨루는지는 prop 규약을 보세요.
접근성
label·description·error가id와aria-describedby로 컨트롤에 연결됩니다.- floating label은 제공하지 않습니다.
- focus ring은
<input>이 아니라 감싸는 shell에 그려지므로 테두리를 그대로 따라갑니다. - shell의 여백을 클릭해도 caret이 들어갑니다.