FileInput#
파일 선택/업로드를 위한 점선 테두리 드롭존 컴포넌트입니다. Flutter/Web 양쪽에서 동일한 API(FileInput)를 제공합니다.
Live Preview#
class FileInputDefaultExample extends StatefulComponent {
const FileInputDefaultExample({super.key});
@override
State<FileInputDefaultExample> createState() =>
_FileInputDefaultExampleState();
}
class _FileInputDefaultExampleState extends State<FileInputDefaultExample> {
List<String> _selectedFiles = const [];
@override
Component build(BuildContext context) {
return FileInput(
onFilesSelected: (files) {
setState(() {
_selectedFiles = [
for (var i = 0; i < files.length; i++) files.item(i)!.name,
];
});
},
multiple: true,
hint: 'PNG, JPG, PDF up to 10MB',
selectedFiles: _selectedFiles,
);
}
}
class FileInputDefaultExample extends StatefulWidget {
const FileInputDefaultExample({super.key});
@override
State<FileInputDefaultExample> createState() =>
_FileInputDefaultExampleState();
}
class _FileInputDefaultExampleState extends State<FileInputDefaultExample> {
List<String> _selectedFiles = const [];
void handleFilesSelected(List<XFile> files) {
setState(() {
_selectedFiles = files.map((f) => f.name).toList();
});
}
@override
Widget build(BuildContext context) {
return FileInput(
onFilesSelected: handleFilesSelected,
multiple: true,
hint: 'PNG, JPG, PDF up to 10MB',
selectedFiles: _selectedFiles,
);
}
}
class FileInputChainExample extends StatefulComponent {
const FileInputChainExample({super.key});
@override
State<FileInputChainExample> createState() => _FileInputChainExampleState();
}
class _FileInputChainExampleState extends State<FileInputChainExample> {
List<String> _selectedFiles = const [];
@override
Component build(BuildContext context) {
return div(
[
// 토큰-정확 getter combo — 이름이 곧 값 (getter 이름 = Core 토큰 상수 1:1).
FileInput(
onFilesSelected: (files) {
setState(() {
_selectedFiles = [
for (var i = 0; i < files.length; i++) files.item(i)!.name,
];
});
},
multiple: true,
hint: 'PNG, JPG, PDF up to 10MB',
selectedFiles: _selectedFiles,
).radius16.primary,
const Gap.space12(),
// withStyle full-control — getter 가 없는 필드(padding)까지 한 번에.
FileInput(
onFilesSelected: (files) {
setState(() {
_selectedFiles = [
for (var i = 0; i < files.length; i++) files.item(i)!.name,
];
});
},
multiple: true,
hint: 'PNG, JPG, PDF up to 10MB',
selectedFiles: _selectedFiles,
).withStyle(
const CoreFileInputStyle(
backgroundColor: CoreColor.token(CoreColors.tertiary),
borderRadius: CoreBorderRadius.all(CoreRadius.radius24),
borderWidth: CoreStrokeWidth.stroke2,
padding: CoreEdgeInsets.symmetric(
horizontal: CoreSpace.space24,
vertical: CoreSpace.space8,
),
),
),
],
classes: 'flex flex-col items-start',
);
}
}
class FileInputChainExample extends StatefulWidget {
const FileInputChainExample({super.key});
@override
State<FileInputChainExample> createState() => _FileInputChainExampleState();
}
class _FileInputChainExampleState extends State<FileInputChainExample> {
List<String> _selectedFiles = const [];
void handleFilesSelected(List<XFile> files) {
setState(() {
_selectedFiles = files.map((f) => f.name).toList();
});
}
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 토큰-정확 getter combo — 이름이 곧 값 (getter 이름 = Core 토큰 상수 1:1).
FileInput(
onFilesSelected: handleFilesSelected,
multiple: true,
hint: 'PNG, JPG, PDF up to 10MB',
selectedFiles: _selectedFiles,
).radius16.primary,
const Gap.space12(),
// withStyle full-control — getter 가 없는 필드(padding)까지 한 번에.
FileInput(
onFilesSelected: handleFilesSelected,
multiple: true,
hint: 'PNG, JPG, PDF up to 10MB',
selectedFiles: _selectedFiles,
).withStyle(
const CoreFileInputStyle(
backgroundColor: CoreColor.token(CoreColors.tertiary),
borderRadius: CoreBorderRadius.all(CoreRadius.radius24),
borderWidth: CoreStrokeWidth.stroke2,
padding: CoreEdgeInsets.symmetric(
horizontal: CoreSpace.space24,
vertical: CoreSpace.space8,
),
),
),
],
);
}
}
사용 시기 (When to Use)#
이 컴포넌트를 사용하세요:
- 사용자가 파일을 업로드해야 할 때 (이미지, 문서 등)
- 프로필 사진, 첨부파일 등 파일 선택이 필요할 때
- 폼에서 파일 필드를 포함해야 할 때
대신 다른 컴포넌트를 사용하세요:
TextField: 텍스트 입력일 때Button: 파일 업로드 트리거만 필요하고 커스텀 UI를 구성할 때
기본 사용법 (Basic Usage)#
// 기본 파일 입력
FileInput(
onFilesSelected: (files) => print('Selected: ${files.length}'),
)
// 이미지만 허용
FileInput(
accept: ['image/*'],
onFilesSelected: handleImageSelected,
)
// 다중 파일 + 선택 목록 표시
FileInput(
multiple: true,
accept: ['.pdf', '.doc', '.docx'],
hint: 'PDF, DOC up to 10MB',
selectedFiles: selectedFileNames,
onFilesSelected: handleFilesSelected,
)
// 기본 파일 입력
FileInput(
onFilesSelected: (fileList) => print('Files: ${fileList.length}'),
)
// 이미지만 허용
FileInput(
accept: ['image/*'],
onFilesSelected: handleImageSelected,
)
// 다중 파일 + 힌트 + 선택 목록
FileInput(
multiple: true,
accept: ['.pdf', '.doc', '.docx'],
hint: 'PDF, DOC up to 10MB',
selectedFiles: selectedFileNames,
onFilesSelected: handleFilesSelected,
)
빠른 오버라이드 (Chain)#
이미 만든 FileInput 인스턴스에 스타일을 빠르게 덧붙이고 싶다면 withStyle 체인을 쓸 수 있습니다. 생성자의 style 슬롯 인자와 동일하게 동작하지만, 이미 구성된 위젯 위에서 바로 이어 쓸 수 있습니다.
.radius16처럼 Core 토큰 상수 이름과 똑같은 이름의 getter도 있습니다 — withStyle을 한 번 더 줄인 sugar로, 이름이 곧 값이라(radius16 ==
CoreRadius.radius16) 어느 컴포넌트에서 써도 뜻이 갈리지 않습니다.
class FileInputChainExample extends StatefulWidget {
const FileInputChainExample({super.key});
@override
State<FileInputChainExample> createState() => _FileInputChainExampleState();
}
class _FileInputChainExampleState extends State<FileInputChainExample> {
List<String> _selectedFiles = const [];
void handleFilesSelected(List<XFile> files) {
setState(() {
_selectedFiles = files.map((f) => f.name).toList();
});
}
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 토큰-정확 getter combo — 이름이 곧 값 (getter 이름 = Core 토큰 상수 1:1).
FileInput(
onFilesSelected: handleFilesSelected,
multiple: true,
hint: 'PNG, JPG, PDF up to 10MB',
selectedFiles: _selectedFiles,
).radius16.primary,
const Gap.space12(),
// withStyle full-control — getter 가 없는 필드(padding)까지 한 번에.
FileInput(
onFilesSelected: handleFilesSelected,
multiple: true,
hint: 'PNG, JPG, PDF up to 10MB',
selectedFiles: _selectedFiles,
).withStyle(
const CoreFileInputStyle(
backgroundColor: CoreColor.token(CoreColors.tertiary),
borderRadius: CoreBorderRadius.all(CoreRadius.radius24),
borderWidth: CoreStrokeWidth.stroke2,
padding: CoreEdgeInsets.symmetric(
horizontal: CoreSpace.space24,
vertical: CoreSpace.space8,
),
),
),
],
);
}
}
class FileInputChainExample extends StatefulComponent {
const FileInputChainExample({super.key});
@override
State<FileInputChainExample> createState() => _FileInputChainExampleState();
}
class _FileInputChainExampleState extends State<FileInputChainExample> {
List<String> _selectedFiles = const [];
@override
Component build(BuildContext context) {
return div(
[
// 토큰-정확 getter combo — 이름이 곧 값 (getter 이름 = Core 토큰 상수 1:1).
FileInput(
onFilesSelected: (files) {
setState(() {
_selectedFiles = [
for (var i = 0; i < files.length; i++) files.item(i)!.name,
];
});
},
multiple: true,
hint: 'PNG, JPG, PDF up to 10MB',
selectedFiles: _selectedFiles,
).radius16.primary,
const Gap.space12(),
// withStyle full-control — getter 가 없는 필드(padding)까지 한 번에.
FileInput(
onFilesSelected: (files) {
setState(() {
_selectedFiles = [
for (var i = 0; i < files.length; i++) files.item(i)!.name,
];
});
},
multiple: true,
hint: 'PNG, JPG, PDF up to 10MB',
selectedFiles: _selectedFiles,
).withStyle(
const CoreFileInputStyle(
backgroundColor: CoreColor.token(CoreColors.tertiary),
borderRadius: CoreBorderRadius.all(CoreRadius.radius24),
borderWidth: CoreStrokeWidth.stroke2,
padding: CoreEdgeInsets.symmetric(
horizontal: CoreSpace.space24,
vertical: CoreSpace.space8,
),
),
),
],
classes: 'flex flex-col items-start',
);
}
}
Props / Parameters#
FileInput은 Flutter/Web에서 동일한 파라미터 이름을 사용합니다. 타입만 플랫폼에 맞게 다릅니다.
| 속성 | Flutter 타입 | Web 타입 | 기본값 | 설명 |
|---|---|---|---|---|
onFilesSelected |
ValueChanged<List<XFile>>? |
void Function(FileList)? |
null |
파일 선택 콜백 |
accept |
List<String>? |
List<String>? |
null |
허용 파일 타입 (예: ['image/*', '.pdf']) |
multiple |
bool |
bool |
false |
다중 파일 선택 허용 |
enabled |
bool |
bool |
true |
상호작용 활성화 여부 |
label |
String? |
String? |
null (로케일 문자열로 폴백) |
드롭존 안내 문구 |
hint |
String? |
String? |
null |
보조 힌트 텍스트 |
icon |
Widget? |
Component? |
null (기본 업로드 아이콘) |
커스텀 아이콘 |
browseButtonText |
String? |
String? |
null (로케일 문자열로 폴백) |
파일 선택 버튼 레이블 |
selectedFiles |
List<String>? |
List<String>? |
null |
선택된 파일 목록 표시 |
name |
String? |
String? |
null |
폼 필드 이름 |
fileInputStyle |
CoreFileInputStyle? |
CoreFileInputStyle? |
null |
패딩 · 점선 보더 · 색 · 타이포 등 모든 chrome 의 단일 진입점 |
CoreFileInputStyle 필드#
| 필드 | 타입 | 설명 |
|---|---|---|
padding | CoreEdgeInsets? | Inner padding. |
itemSpacing |
double? |
Spacing between vertical elements (icon / label / button / hint) — drives
Column.spacing
(Flutter) and CSS
gap
on the flex column root (Web).
|
borderRadius |
CoreBorderRadius? |
Outer border radius. |
borderWidth |
double? |
Outer border stroke width (logical px). |
dashLength |
double? |
Dash segment length of the dashed drop-zone border (logical px).
null
→ [defaultDashLength].
|
dashSpacing |
double? |
Gap between dash segments (logical px). null → [defaultDashSpacing]. |
backgroundColor |
CoreColor? |
Container background colour. |
borderColor |
CoreColor? |
Outer border stroke colour. |
fileItemSpacing |
double? |
Spacing (and bottom margin) between selected file items — drives
Row.spacing
(Flutter) and CSS
gap
on each per-file row (Web), plus the bottom margin of each row.
|
transitionDuration |
Duration? |
Transition duration for the hover / focus colour crossfade on the drop-zone container. |
iconStyle |
CoreIconStyle? |
Icon slot style (size + colour). |
fileIconStyle |
CoreIconStyle? |
Selected-file icon slot style (size + colour) — drives the per-file row icon glyph. |
labelTextStyle |
CoreTextStyle? |
Primary label text style override. |
hintTextStyle |
CoreTextStyle? |
Hint text style override. |
clickableStyle |
CoreClickableStyle? |
Nested style for the composed drop-zone
Clickable
— carries any press / focus-ring / cursor chrome overrides for the drop-zone affordance. Merged on top of [defaultClickableStyle] and raw-forwarded to
Clickable(clickableStyle:)
.
|
contentColor |
CoreColor? |
Primary-label content colour override. null → [defaultContentColor]. |
disabledContentColor |
CoreColor? |
Disabled content colour override. null → [defaultDisabledContentColor]. |
hintColor |
CoreColor? |
Hint content colour override. null → [defaultHintColor]. |
fileNameColor |
CoreColor? |
Selected-file name colour override. null → [defaultFileNameColor]. |
subtleIconColor |
CoreColor? |
Subtle upload-icon colour override (also the disabled upload-icon tone).
null
→ [defaultSubtleIconColor].
|
fileIconColor |
CoreColor? |
Selected-file icon colour override (enabled). null → [defaultFileIconColor]. |
disabledBorderColor |
CoreColor? |
Disabled dashed-border colour override. null → [defaultDisabledBorderColor]. |
dragOverBorderColor |
CoreColor? |
Drag-over dashed-border colour override. null → [defaultDragOverBorderColor]. |
dragActiveBackgroundColor |
CoreColor? |
Drag-active background tint override. null → [defaultDragActiveBackgroundColor]. |
fileTextStyle |
CoreTextStyle? |
Selected-file name text style override. null → [defaultFileTextStyle]. |
테마 커스터마이징 (Theme)#
CoreFileInputTheme은 style 슬롯 하나를 가지며, 프로젝트 공통 chrome 이 그 슬롯으로 흐릅니다.
CoreComponentTheme(
fileInput: CoreFileInputTheme(
style: CoreFileInputStyle(
borderRadius: CoreBorderRadius.all(CoreRadius.radius12),
padding: CoreEdgeInsets.all(CoreSpace.space32),
itemSpacing: CoreSpace.space16,
iconStyle: CoreIconStyle(size: CoreIconSize.size24),
),
),
)
Resolve 우선순위: widget.fileInputStyle > CoreFileInputTheme.style
> CoreFileInputStyle.default*.
동작 스펙 (Behavior)#
파일 선택#
- 드롭존 전체 영역을 클릭하면 브라우저 파일 선택 다이얼로그가 열립니다
accept로 허용 파일 타입 필터링multiple: true시 여러 파일 동시 선택 가능- 선택된 파일은
onFilesSelected콜백으로 전달
시각 피드백#
- 호버 시: 테두리 색상이
primary로 강조되고, 배경에primary/5틴트가 적용됩니다 - 비활성 시: 반투명 + pointer-events 차단
- 점선 테두리:
CoreDashedBorder토큰으로 Flutter/Web 동일한 시각 렌더링
사용 가이드라인 (Usage Guidelines)#
✅ Do#
accept 속성으로 허용 파일 타입을 명시하세요.
// 이미지만
FileInput(accept: ['image/*'], onFilesSelected: handleImage)
// 특정 문서 형식
FileInput(accept: ['.pdf', '.doc', '.docx'], onFilesSelected: handleDoc)
사용자가 잘못된 파일을 선택하는 실수를 줄입니다.
❌ Don't#
파일 크기 검증 없이 업로드를 허용하지 마세요.
onFilesSelected 콜백에서 파일 크기를 확인하고, 초과 시 사용자에게 안내하세요.
✅ Do#
선택된 파일 목록을 selectedFiles로 표시하세요.
FileInput(
multiple: true,
selectedFiles: selectedFileNames,
onFilesSelected: (files) {
setState(() {
selectedFileNames = files.map((f) => f.name).toList();
});
},
)
사용자가 어떤 파일을 선택했는지 즉시 확인할 수 있습니다.
접근성 (Accessibility)#
키보드 인터랙션#
| 키 | 동작 |
|---|---|
Tab | 파일 선택 버튼으로 포커스 이동 |
Enter / Space | 파일 선택 다이얼로그 열기 |
스크린 리더#
- Web: 내부에 숨겨진
<input type="file">로 네이티브 시맨틱 제공 accept속성이 허용 파일 타입 정보를 전달enabled: false시 비활성 상태 전달
라벨#
name속성으로 폼 내 필드 식별 (Web)