FileInput | CoUI
LogoCoUI

FileInput

파일 입력 컴포넌트

FileInput#

파일 선택/업로드를 위한 점선 테두리 드롭존 컴포넌트입니다. Flutter/Web 양쪽에서 동일한 API(FileInput)를 제공합니다.

Live Preview#

사용 시기 (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 필드#

필드타입설명
paddingCoreEdgeInsets?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)#

CoreFileInputThemestyle 슬롯 하나를 가지며, 프로젝트 공통 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)
  • TextField: 텍스트 입력. FileInput은 파일 선택 전용
  • Form: 폼 컨테이너. FileInput을 Form 내에서 사용
  • Button: 내부 Browse 버튼으로 Button(variant: outline) 사용