본문으로 건너뛰기

PlFilePicker

파일을 골라 넣는 상자입니다. 들어온 파일을 accept, maxSize, maxFiles로 검사하고, 돌려보낸 것을 전부 알려 줍니다.

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

<PlFilePicker label="Attachments" multiple maxFiles={4} value={files} onFilesChange={setFiles} />;
dart
import 'package:plass_ui/plass_ui.dart';

PlFilePicker(
  label: const Text('Attachments'),
  multiple: true,
  maxFiles: 4,
  value: files,
  onBrowse: () async => myPickerPlugin.pick(),
  onFilesChanged: (List<PlFile> next) => setState(() => files = next),
);

picker는 고르지 않습니다. 이 패키지에는 의존성이 없고, 파일 시스템에 닿는 것은 그 일을 하는 모든 Flutter 앱에서 플러그인의 몫입니다. 그래서 앱 자신의 picker가 실행되는 자리가 onBrowse입니다. 컴포넌트가 쥐는 것은 그 이후의 전부입니다. 규칙, 목록, 삭제, 그리고 상자 자체.

Props

Prop타입기본값설명
variant공통'solid' | 'glass' | 'ghost''glass'상자의 재질. 셋 다 점선 테두리를 씁니다 — 드롭을 받는 영역이라는 뜻의 관습이기 때문입니다
size공통'xs' | 'sm' | 'md' | 'lg' | 'xl''md'상자의 여백과 안쪽 글자의 타입 스케일
color공통'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info''primary'의미론적 색 역할. 임의 색상값은 받지 않습니다
density공통'default' | 'compact''default'여백만 바꿉니다. 높이와 글자 크기는 그대로
elevation공통0 | 1 | 2 | 30그림자 깊이. 0은 그림자 없음
acceptstring브라우저 파일 대화상자가 제공할 형식 ('image/*,.pdf'). 드롭된 파일도 이 목록으로 검사합니다 — 속성만으로는 검사되지 않습니다
multiplebooleanfalse파일을 여러 개 고를 수 있는지
maxSizenumber파일 하나의 최대 크기 (바이트)
maxFilesnumber동시에 쥘 수 있는 파일 수. 한 번의 드롭이 아니라 이미 쥔 것과 합쳐 검사합니다
valuereadonly File[]선택된 파일들. onFilesChange와 함께 controlled로 씁니다
defaultValuereadonly File[]uncontrolled일 때 처음 선택된 파일들
onFilesChange(files: File[]) => void목록이 바뀔 때 호출됩니다
onReject(rejections: PlFileRejection[]) => void거절된 파일과 그 이유를 받습니다. 없으면 거절된 파일이 조용히 사라집니다 — dropzone이 저지르는 최악의 일
label · description · error · invalidReactNode · ReactNode · ReactNode · boolean상자 위 라벨, 아래 보조 설명, 오류 메시지. error의 존재가 invalid 상태를 만듭니다
titleReactNode'Drop files here, or click to browse'상자 안의 문장
hintReactNode그 아래 한 줄 — 무엇을, 얼마나 크게, 몇 개까지
iconReactNode제목 위의 글리프. null을 주면 그림 없는 상자가 됩니다
showListbooleantrue상자 아래에 선택된 파일을 지우기 버튼과 함께 나열합니다
removeLabel(name: string) => string`Remove {name}`파일 지우기 버튼의 접근 가능한 이름
fullWidthbooleantrue컨테이너 너비만큼 확장
readOnlybooleanfalse파일은 보이지만 추가도 삭제도 할 수 없습니다
disabledbooleanfalse사용 불가
name · required · idstring · boolean · string네이티브 form 제출과 라벨 연결을 위한 것들
Prop타입기본값설명
value * List<PlFile>선택된 파일들. onFilesChange와 함께 controlled로 씁니다
onFilesChangedValueChanged<List<PlFile>>?다음에 쥐고 있어야 할 목록으로 불립니다 — 파일이 더해졌거나, 목록에서 하나가 지워졌거나
onBrowseFuture<List<PlFile>> Function()?앱 자신의 file picker를 실행하고 찾은 것을 돌려줍니다. 돌아온 것이 규칙에 걸린 뒤 살아남은 것이 onFilesChanged로 보고됩니다
onRejectedValueChanged<List<PlFileRejection>>?거절된 파일과 그 이유를 받습니다. 없으면 거절된 파일이 조용히 사라집니다 — dropzone이 저지르는 최악의 일
acceptString?어떤 파일을 받을지 ('image/*,.pdf'). onBrowse가 돌려준 것에 적용됩니다 — 말해 놓고 강제하지 않는 규칙은 규칙이 아닙니다
multipleboolfalse파일을 여러 개 고를 수 있는지
maxSizeint?파일 하나의 최대 크기 (바이트)
maxFilesint?동시에 쥘 수 있는 파일 수. 한 번의 드롭이 아니라 이미 쥔 것과 합쳐 검사합니다
draggingboolfalse파일이 상자 위에 있는지. 플러그인 없이는 Flutter에 OS 수준의 드래그가 없으므로 앱이 알려 주고, 상자는 그에 맞게 밝아집니다
label · description · error · invalidWidget? · Widget? · Widget? · bool?상자 위 라벨, 아래 보조 설명, 오류 메시지. error의 존재가 invalid 상태를 만듭니다
titleWidget?Text('Choose files')상자 안의 문장
hintWidget?그 아래 한 줄 — 무엇을, 얼마나 크게, 몇 개까지
iconWidget?제목 위의 글리프. 생략하면 업로드 표식이 쓰입니다
showIconbooltrue글리프를 그릴지. Dart에는 null도 위젯도 아닌 값이 없으니 "치워라"가 자기 이름을 가집니다
showListbooltrue상자 아래에 선택된 파일을 지우기 버튼과 함께 나열합니다
removeLabelString Function(String name)'Remove {name}'파일 지우기 버튼의 접근 가능한 이름
variant공통PlassVariantPlassVariant.glass상자의 재질. 셋 다 점선 테두리를 씁니다 — 드롭을 받는 영역이라는 뜻의 관습이기 때문입니다
size공통PlassSizePlassSize.md상자의 여백과 안쪽 글자의 타입 스케일
color공통PlassColorPlassColor.primary의미론적 색 역할. 임의 색상값은 받지 않습니다
density공통PlassDensityPlassDensity.standard여백만 바꿉니다. 높이와 글자 크기는 그대로
elevation공통int0그림자 깊이. 0은 그림자 없음
fullWidthbooltrue컨테이너 너비만큼 확장
readOnlyboolfalse파일은 보이지만 추가도 삭제도 할 수 없습니다
disabledboolfalse사용 불가

네이티브 <div> 속성은 wrapper로 그대로 전달됩니다. color, defaultValue, title, children은 넷 다 여기서는 Plass의 prop이라 제외됩니다.

formatFileSize도 함께 export되므로, 목록을 직접 그리는 쪽에서도 같은 단위로 크기를 찍을 수 있습니다.

Controlled입니다. picker를 움직이는 방법은 언제나 valueonFilesChanged입니다.

PlFile

React 패키지에는 아직 PlFile가 없습니다.

Prop타입기본값설명
name * String파일 이름, 확장자까지
size * int몇 바이트인지
mimeTypeString?종류 — image/png. 생략하면 accept는 확장자만 봅니다
sourceObject?앱 자신의 picker가 건넨 것. picker는 들여다보지 않습니다
readableSizeString파일 목록을 읽는 사람이 기대하는 단위의 1.4 MB. 1024가 아니라 1000 기준입니다
matchesbool Function(String accept)accept 문자열과 맞는지. .ext, type/subtype, type/* 세 형태 모두

PlFile은 일부러 dart:ioFile도, 그것을 감싼 무언가도 아닙니다. 상자가 그리는 것은 이름과 크기이고 규칙이 읽는 것은 이름과 크기와 종류이므로, 물어보는 것도 그것뿐입니다. source는 앱 자신의 객체를 손대지 않고 실어 나르므로, 반대쪽에서 그대로 돌려받습니다.

readableSize는 파일 목록을 읽는 사람이 기대하는 단위로 1.4 MB를 찍고, matchesaccept 검사입니다. 목록을 직접 그리는 쪽에서도 둘 다 쓸 수 있습니다.

라이브러리 전체에서 공유 축(variant size color density elevation)이 뜻하는 바는 prop 규칙에 있습니다.

Examples

variant

셋 다 점선 테두리를 쓰고, 라이브러리에서 실선이 아닌 선을 긋는 유일한 자리입니다. 장식이 아닙니다. 점선 사각형은 "여기에 떨어뜨릴 수 있다"는 뜻으로 굳어진 관습이고, PlCard처럼 생긴 dropzone은 아무도 떨어뜨려 보지 않는 PlCard입니다.

테두리는 쉬고 있을 때 중립색이고, 포인터가 올라온 뒤에야 색 계열을 입습니다. glass PlButton과 같은 방식입니다.

React

accept · maxSize · maxFiles

`accept`는 input에 붙기도 하고 드롭에도 **적용**됩니다. 브라우저는 이 속성을 자기 대화상자에만 강제하고 그 밖에는 아무 데도 적용하지 않으므로, 속성만 걸어 둔 dropzone은 드래그로 들어오는 순간 무엇이든 받아들입니다.`accept`는 `onBrowse`가 돌려준 것에 적용됩니다. 그것을 찾아 준 플러그인이 같은 말을 들었는지와는 무관합니다. 말해 놓고 강제하지 않는 규칙은 규칙이 아닙니다.

maxFiles는 한 번의 드롭이 아니라 이미 쥐고 있는 것과 합쳐 셉니다. "파일 다섯 개를 떨어뜨릴 수 있다"와 "파일 다섯 개까지 가질 수 있다"의 차이이고, 이 prop이 뜻하는 것은 후자입니다.

거절은 onRejectonRejected로 나갑니다. 이것이 없으면 거절된 파일이 조용히 사라지는데, 그것이 dropzone이 저지르는 가장 나쁜 일입니다.

React

한 번에 한 파일

multiple이 없으면 상자는 정확히 파일 하나를 쥐고, 새 파일이 들어오면 count로 거절되는 대신 그것을 대체합니다. 아바타 선택기가 원하는 동작입니다.

React

size

상자의 여백과 안쪽 글자를 함께 움직입니다. 여백이 시트의 사다리가 아니라 자기 사다리를 쓰는 이유는, dropzone의 크기를 정하는 것이 안에 쓰인 글이 아니라 받아 내야 할 동작이기 때문입니다. 글자 한 줄 높이의 과녁은 빗나가는 과녁입니다.

React

disabled · error

React

Accessibility

  • 누를 수 있는 영역은 진짜 <button>입니다. 포커스 순서에 들어가고 EnterSpace에 반응합니다. 드래그 앤 드롭은 거기에 더해진 것이지 유일한 통로가 아닙니다.
  • <input type="file">display: none이 아니라 화면 밖으로 잘려 DOM에 남습니다. 전자는 일부 브라우저에서 focus를 받을 수 없게 만들고, 네이티브 form 검증에서도 빠지게 합니다.
  • descriptionerroraria-describedby로 버튼에 연결되고, error는 aria-invalid도 함께 세웁니다.
  • 파일 목록은 browse 버튼 바깥의 진짜 <ul>입니다. 지우기 버튼을 다른 버튼 안에 넣을 수는 없기 때문입니다.
  • 각 지우기 버튼은 지우는 파일 이름을 포함한 접근 가능한 이름을 가집니다. 스크린리더가 "Remove" 세 개가 아니라 서로 다른 버튼 셋을 읽습니다.
  • 파일이 위에 있는 동안 영역이 움직이지 않습니다. 색과 테두리만 바뀌고 커지거나 떠오르지 않습니다. 조준하는 동안 움직이는 과녁은 빗나가는 과녁입니다.
  • 상자는 버튼으로 읽힙니다. focus 순서에 들어가고 EnterSpace에 반응합니다. 앱이 덧붙이는 드롭 처리는 거기에 더해진 것이지 유일한 통로가 아닙니다.
  • 파일 목록은 상자 바깥에 있습니다. 버튼 안의 지우기 버튼은 한 번 누르면 두 번 발생하는 누름이기 때문입니다.
  • 각 지우기 버튼은 지우는 파일 이름을 포함한 이름을 가집니다. 스크린리더가 "Remove" 세 개가 아니라 서로 다른 버튼 셋을 읽습니다.
  • 파일이 위에 있는 동안 상자가 움직이지 않습니다. 색과 테두리만 바뀌고 커지거나 떠오르지 않습니다. 조준하는 동안 움직이는 과녁은 빗나가는 과녁입니다.
  • error는 색 계열 전체를 danger로 돌려세웁니다. 테두리와 ring, 메시지가 함께 넘어갑니다.

React 빌드와 다른 점

ReactFlutter이유
스스로 파일 대화상자를 엶onBrowse가 앱의 picker를 실행플러그인 없이는 Flutter에 파일 대화상자가 없고, 이 패키지에는 의존성이 없습니다. 규칙은 여기 남고, picker는 앱의 것입니다.
드래그 앤 드롭앱이 세워 주는 draggingOS 수준의 드래그도 없습니다. 그 상태의 생김새는 컴포넌트의 것이고, 감지는 앱의 것입니다.
FilePlFile이름과 크기, 종류, 그리고 실려 오는 앱 자신의 객체. 패키지는 아무것도 열지 않습니다.
export되는 formatFileSizePlFile.readableSize같은 숫자를, 그것이 붙은 것 위에서.
value / defaultValue / onFilesChangevalue / onFilesChangedFlutter의 컨트롤은 controlled이고, 콜백 이름도 Flutter의 것입니다.
icon={null}showIcon: falseDart에는 null도 위젯도 아닌 값이 없으니, "치워라"가 자기 이름을 갖습니다.
숨은 input, name, required함께 제출될 네이티브 form이 없습니다.
id, aria-describedby, aria-invalid여기서는 무엇도 id로 다른 것을 가리키지 않습니다. 라벨과 메시지는 컴포넌트의 일부입니다.
className, style, 네이티브 속성전달할 클래스 목록도 style 속성도 없습니다.

Released under the MIT License