본문으로 건너뛰기

PlTextField

한 줄 또는 여러 줄 텍스트 입력입니다. 라벨과 보조 설명, 오류 메시지가 직접 엮어야 하는 세 요소가 아니라 컴포넌트의 일부입니다.

React
tsx
import { PlTextField } from 'plass-ui';

<PlTextField label="Email" type="email" description="We never share it." />;
dart
import 'package:plass_ui/plass_ui.dart';

PlTextField(
  controller: email,
  label: const Text('Email'),
  keyboardType: TextInputType.emailAddress,
  description: const Text('We never share it.'),
);

Props

Prop타입기본값설명
variant공통'solid' | 'glass' | 'ghost''glass'표면의 재질. 필드에서 solid는 색 유리판이 아니라 시트에 파인 우물입니다 — 필드가 담는 것은 사용자 데이터입니다
size공통'xs' | 'sm' | 'md' | 'lg' | 'xl''md'높이와 타입 스케일. PlButton과 같은 높이라서 한 줄에 섞어 놓아도 기준선이 맞습니다
color공통'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info''primary'의미론적 색 역할. 유리는 물들이지 않으므로 가장자리와 포커스 링, 캐럿에만 나타납니다
density공통'default' | 'compact''default'여백만 바꿉니다. 높이와 글자 크기는 그대로
elevation공통0 | 1 | 2 | 30그림자 깊이. 필드는 떠 있는 표면이 아니라 파인 자리이므로 기본값이 0입니다
labelReactNode컨트롤 위 라벨. Base UI Field로 연결됩니다
descriptionReactNode컨트롤 아래 보조 설명
errorReactNode컨트롤 아래 오류 메시지. 값이 있으면 invalid 상태도 함께 켜집니다
invalidboolean!!error메시지 없이 invalid만 켭니다. 외부 폼 라이브러리가 유효성을 가질 때
multilinebooleanfalseinput 대신 textarea로 렌더링합니다. 나머지 축은 그대로
rowsnumber3multiline일 때 보이는 줄 수
resize'none' | 'vertical' | 'horizontal' | 'both''vertical'multiline을 사용자가 어느 방향으로 늘릴 수 있는지. 가로로 늘리면 폼의 열이 깨지므로 세로만 기본값입니다
startIconReactNode컨트롤 앞에 놓이는 내용. 1.2em으로 그려져 글자 크기를 따라갑니다
endIconReactNode컨트롤 뒤에 놓이는 내용
loadingbooleanfalseendIcon 자리에 스피너를 띄우고 busy로 표시합니다. 입력은 계속 가능합니다
readOnlybooleanfalse값은 읽고 복사할 수 있지만 고쳐 쓸 수는 없습니다. 채도가 빠지고 평평해집니다
disabledbooleanfalse사용 불가. 시트 너머로 페이지가 비쳐 보이며, 포커스 순서에서 빠집니다
fullWidthbooleanfalse컨테이너 너비만큼 확장
hotKeysRecord<string, () => void>이 컨트롤이 답할 chord들. PlHotKeys가 그리는 것과 같은 철자입니다 — { 'Mod+Enter': save, Escape: cancel }. 맞는 chord는 **소비됩니다**
classNames{ label?, control?, description?, error?: string }className이 닿지 않는 부분에 붙는 class. control은 실제로 조작하는 부분입니다
Prop타입기본값설명
controllerTextEditingController?편집 중인 텍스트. Flutter가 텍스트를 두는 자리입니다. 빼면 필드가 하나를 스스로 만듭니다
onChangedValueChanged<String>?모든 변화를 알립니다
onSubmittedValueChanged<String>?키보드에서 제출했을 때
variant공통PlassVariantPlassVariant.solid표면의 재질. 필드에서 solid는 색 유리판이 아니라 시트에 파인 우물입니다 — 필드가 담는 것은 사용자 데이터입니다
size공통PlassSizePlassSize.md높이와 타입 스케일. PlButton과 같은 높이라서 한 줄에 섞어 놓아도 기준선이 맞습니다
color공통PlassColorPlassColor.primary의미론적 색 역할. 유리는 물들이지 않으므로 가장자리와 포커스 링, 캐럿에만 나타납니다
density공통PlassDensityPlassDensity.standard여백만 바꿉니다. 높이와 글자 크기는 그대로
elevation공통int0그림자 깊이. 필드는 떠 있는 표면이 아니라 파인 자리이므로 기본값이 0입니다
multilineboolfalseinput 대신 textarea로 렌더링합니다. 나머지 축은 그대로
rowsint3multiline일 때 보이는 줄 수
labelWidget?컨트롤 위 라벨. Base UI Field로 연결됩니다
descriptionWidget?컨트롤 아래 보조 설명
errorWidget?컨트롤 아래 오류 메시지. 값이 있으면 invalid 상태도 함께 켜집니다
invalidbool?!!error메시지 없이 invalid만 켭니다. 외부 폼 라이브러리가 유효성을 가질 때
placeholderString?비어 있는 동안 보이는 것. React에서는 네이티브 속성이라 표에 없습니다
startIconWidget?컨트롤 앞에 놓이는 내용. 1.2em으로 그려져 글자 크기를 따라갑니다
endIconWidget?컨트롤 뒤에 놓이는 내용
loadingboolfalseendIcon 자리에 스피너를 띄우고 busy로 표시합니다. 입력은 계속 가능합니다
fullWidthboolfalse컨테이너 너비만큼 확장
readOnlyboolfalse값은 읽고 복사할 수 있지만 고쳐 쓸 수는 없습니다. 채도가 빠지고 평평해집니다
disabledboolfalse사용 불가. 시트 너머로 페이지가 비쳐 보이며, 포커스 순서에서 빠집니다
obscureTextboolfalse입력한 것을 가립니다. 비밀번호용
keyboardTypeTextInputType?터치 기기에서 올릴 키보드
maxLengthint?받을 글자 수. 카운터가 아니라 formatter입니다 — 아래에 아무것도 그려지지 않습니다
semanticLabelString?보이는 label이 없는 필드를 스크린 리더가 부를 이름. placeholder는 이름이 아닙니다
hotKeysPlassHotKeys?이 컨트롤이 답할 chord들. PlHotKeys가 그리는 것과 같은 철자입니다 — { 'Mod+Enter': save, Escape: cancel }. 맞는 chord는 **소비됩니다**

네이티브 <input> 속성은 그대로 전달되고, multiline일 때는 <textarea> 속성이 그대로 전달됩니다. 예외는 위 공통 축과 이름이 겹치는 colorsize입니다.

className은 label과 control, 그 아래 두 줄을 함께 담는 stack에 붙습니다. 그 안쪽 네 부분에 닿는 것이 classNames입니다: label, control(글자가 들어가는 상자), description, error.

값은 TextEditingController에 삽니다. Flutter가 텍스트를 두는 자리가 거기입니다. 빼면 필드가 하나를 스스로 만들지만, 값이 앱에 필요한 필드라면 앱이 controller를 건네주어야 하는 필드입니다.

아래에 있는 것은 TextField가 아니라 EditableText입니다. 앞의 것은 Material이고, 이 패키지는 Material도 Cupertino도 가져오지 않습니다. Material이 그 위에 얹는 것(데코레이션, 카운터, 리플)이 바로 이 컴포넌트가 대신하는 것입니다.

공통 축이 라이브러리 전체에서 뜻하는 것은 prop 규칙에 있습니다.

Examples

variant

glass가 기본값입니다. hairline을 두른 시트이고, 그 선은 시트 자신의 흰 테두리가 아니라 --plass-border입니다. 필드는 대개 카드 위에 놓이는데, 흰 카드 위 거의 흰 상자에 흰 선을 두르면 모양이 보이지 않기 때문입니다. solid우물입니다. 가장 불투명한 유리에 라이브러리에서 유일한 inset 그림자가 얹힌 형태로, 파인 것처럼 보여야 하는 필드에 씁니다. ghost는 포인터가 올라오기 전까지 표면이 없어서 테이블 셀 안의 필드에 어울립니다.

solid 필드는 의도적으로 색 유리판이 아닙니다. 캐럿과 텍스트 선택, placeholder 아래에 깔린 그러데이션은 읽히지 않기 때문에, 색 계열은 대신 hairline과 focus ring, 캐럿에 나타납니다.

React

size

PlButton과 같은 사다리입니다. xs 24px · sm 32px · md 40px · lg 48px · xl 56px. 같은 size의 필드와 버튼은 한 줄에서 기준선이 맞습니다.

React

label, description, error

셋 다 노드이고, 셋 다 Base UI의 Field가 컨트롤과 연결해 줍니다. 라벨은 컨트롤을 가리키고, 두 메시지는 모두 컨트롤의 aria-describedby에 들어갑니다.

셋 다 위젯이고, 셋 다 필드 자신의 semantics 노드에 포함됩니다. 그래서 스크린 리더가 라벨과 필드와 메시지를 셋이 아니라 하나로 읽습니다.

floating label variant는 없습니다. floating label은 입력 중인 대상에 transform을 걸어야 하는데, 캐럿 아래에서 움직이는 라벨은 이 라이브러리가 컨트롤에 대해 유일하게 금지하는 효과입니다.

유효성

error는 메시지를 담는 동시에 필드를 invalid로 만들고, 그러면 slot 계열 전체가 danger로 넘어갑니다. hairline과 focus ring, 캐럿, 메시지가 한꺼번에 바뀝니다.

폼 라이브러리가 유효성을 가질 때를 위한 탈출구가 둘 있습니다. invalid는 메시지 없이 상태만 켜고, invalid={false}invalid: false는 상태 없이 메시지만 보여 줍니다.

React

multiline

나머지 축은 완전히 동일하고, 한 줄짜리 여러 줄 필드는 같은 size의 한 줄 필드와 정확히 같은 높이입니다. 세로 여백이 높이 사다리에서 계산되기 때문에 density는 여기에 손대지 않습니다.

<textarea>를 렌더링합니다. resize는 사용자가 어느 방향으로 끌 수 있는지를 정합니다. 가로로 늘리면 폼의 열이 깨지므로 기본값은 세로 축만입니다.

resize는 없습니다. textarea의 크기 조절 손잡이는 브라우저의 것이고, Flutter에는 내놓을 대응물이 없습니다. 크기가 달라져야 하는 필드는 주변 레이아웃이 크기를 바꿔 주는 필드입니다.

React

startIcon과 endIcon

행이 아니라 글자를 기준으로 크기가 정해집니다. 컨트롤 안이 아니라 shell에 붙으며 컨트롤의 focus에 반응합니다. 필드가 focus되면 adornment가 muted에서 accent 색으로 바뀝니다.

adornment는 컨트롤의 첫 줄을 기준으로 가운데 정렬되므로, multiline 필드가 늘어나도 자리를 지킵니다.

React

loading · readOnly · disabled

prop겉모습입력Focus
loadingendIcon 자리에 스피너가능유지
readOnly색은 유지, 평평해지고 채도가 빠짐불가, 선택은 가능유지
disabled시트 너머로 페이지가 비쳐 보임불가잃음

loading이 입력을 계속 허용하는 것은 의도된 것입니다. 필드가 로딩 중인 이유는 대개 거기에 입력된 내용 때문입니다.

React

hotKeys

Mod+Enter로 저장하고 Escape로 비우는 필드는 달리 둘 곳이 없는 키보드 어포던스입니다. hotKeys는 chord와 그 chord가 하는 일의 map이고, PlHotKeys가 그리는 것과 같은 vocabulary로 씁니다. 필드 옆에 찍힌 키캡과 실제로 동작하는 키가 문자열 하나에서 나오므로 서로 어긋날 수가 없습니다.

Mod는 platform에 따라 정해집니다. 항목 하나가 Mac에서는 ⌘, 그 밖에서는 Ctrl입니다. Esc, Return, Cmd, Option도 키캡과 같은 키로 접힙니다.

맞는 chord는 소비됩니다(handler가 실행되고 키는 더 이상 가지 않습니다. 그래서 여기 묶은 Escape는 필드를 감싼 dialog를 닫지 않고, Enter는 form을 제출하지 않습니다. 키를 묶는다는 것이 그런 뜻이고, 그래서 이건 글자가 아니라 chord여야 합니다) { a: … }a를 칠 수 없는 필드입니다.

React

Controlled

valueonChange는 네이티브 input에서와 똑같이 동작합니다. onChange는 두 요소를 모두 받도록 타입이 잡혀 있어서 multiline에서도 같은 핸들러가 그대로 쓰입니다.

controller가 값이고, onChanged는 모든 변화를 알려줍니다. maxLength는 카운터가 아니라 formatter입니다. 스물다섯 번째 글자가 도착하지 못하게 막을 뿐, 직접 그리지 않는 한 필드 아래에는 아무것도 그려지지 않습니다.

React

Accessibility

  • 네이티브 <input>을, multiline에서는 <textarea>를 렌더링합니다. 둘 다 각자의 요소가 받는 모든 속성을 받습니다.
  • label은 컨트롤을 가리키는 실제 <label>입니다. 라벨이 없다면 aria-label을 주거나, placeholder가 유일한 이름이 되지 않게 하세요.
  • descriptionerror는 모두 aria-describedby에 들어가므로, 스크린 리더가 메시지를 필드 뒤가 아니라 필드와 함께 읽습니다.
  • errorinvalidaria-invalid를 설정합니다.
  • focus ring은 컨트롤이 아니라 shell에 그려져서, 안쪽에 떠 있는 사각형이 아니라 유리의 가장자리를 따라갑니다. :focus-visible에서만 나타납니다.
  • shell의 여백을 클릭하면 네이티브 input 안을 클릭했을 때처럼 캐럿이 필드로 들어갑니다.
  • 텍스트 필드로 알려지고, 읽기 전용이거나 사용할 수 없을 때는 그렇게 알려집니다.
  • 라벨과 필드, 설명, 메시지는 하나의 semantics 노드입니다. 그래서 스크린 리더가 차례로가 아니라 함께 읽습니다. 보이는 라벨이 없다면 semanticLabel을 주세요. placeholder는 이름이 아닙니다.
  • focus ring은 편집기가 아니라 shell에 그려져서, 안쪽에 떠 있는 사각형이 아니라 유리의 가장자리를 따라갑니다. CSS가 :focus-visible이라고 부르는 것에서만 나타납니다.
  • shell의 여백을 누르면 네이티브 input 안을 눌렀을 때처럼 캐럿이 필드로 들어갑니다.
  • 선택은 드래그로 하고, 그 뒤에 조절할 손잡이는 없습니다. 터치 플랫폼이 선택 아래에 붙이는 드래그 손잡이는 Material과 Cupertino의 것이고, 이 패키지는 둘 다 가져오지 않습니다.

React 빌드와 다른 점

ReactFlutter이유
value / onChangecontroller / onChangedTextEditingController가 Flutter가 텍스트를 두는 자리이고, 호출자가 이미 들고 있는 것입니다.
type="email"keyboardType어떤 키보드를 올릴지 말하는 Flutter의 방식입니다.
resizetextarea의 크기 조절 손잡이는 브라우저의 것이고, 내놓을 대응물이 없습니다.
multiline에서의 <textarea>같은 위젯, 더 높을 뿐어느 쪽이든 편집기는 하나이므로, 여러 줄로 바꾸는 것이 정말로 높이 말고는 아무것도 바꾸지 않습니다.
aria-describedby 연결병합된 semantics 노드 하나경로가 다를 뿐 결과는 같습니다.
터치에서의 선택 손잡이Material과 Cupertino의 것이고, 이 패키지는 둘을 가져오지 않습니다.
className, style, 네이티브 속성전달할 클래스 목록도 style 속성도 없습니다.

Released under the MIT License