FileField

Data Input

폼 필드 상태에 연결해 파일을 입력받는 필드.

Usage

폼 값으로 첨부 파일 목록을 제출해야 할 때 `form.AppField` 안에서 사용한다. Form 문맥 없이 독립적으로 파일 선택 UI가 필요하면 `FileFieldBase`를 사용한다. 이미지 전용 업로드나 다중 선택처럼 파일 선택 제약이 필요할 때 사용한다.

import

import

import { FileField } from '@mildang/design-system/FileField';

예제를 복사해 쓸 때 필요한 준비

@mildang/styled-system 은 이 저장소에서 Panda 가 생성하는 산출물이다. 저장소 안에서는 turbo run ship 이후 쓸 수 있고, 패키지 소비자는 자기 Panda 산출물이나 다른 레이아웃 수단으로 바꿔야 한다.

API Reference

FileField Props

Prop

Type

Default

accept

string

지정 안 함

buttonDisabled

boolean

지정 안 함

chipProps

Partial<Omit<ChipProps, "label" | "onDelete">>

지정 안 함

disabled

boolean

지정 안 함

endAdornment

ReactNode

지정 안 함

error

boolean

지정 안 함

errorText

ReactNode

지정 안 함

fileClickable

boolean

지정 안 함

fullWidth

boolean

지정 안 함

helperText

ReactNode

지정 안 함

id

string

지정 안 함

loading

boolean

지정 안 함

maxChipLabelLength

number

지정 안 함

maxVisibleChips

number

지정 안 함

multiple

boolean

지정 안 함

name

string

지정 안 함

onChange

(next: FileType[]) => void

지정 안 함

onFileClick

(file: FileType) => void

지정 안 함

onFileDelete

(file: FileType) => void

지정 안 함

textFieldProps

Omit<InputFieldBaseProps, "onChange" | "value">

지정 안 함

uploadButtonProps

Partial<Omit<ButtonProps, "onClick" | "as" | "type" | "href">>

지정 안 함

value

FileType[]

지정 안 함

FileChip

Prop

Type

Default

file
필수

FileType

지정 안 함

chipProps

Partial<Omit<ChipProps, "label" | "onClick" | "onDelete">>

{ type: 'text', color: 'blue', size: 'sm' }

clickable

boolean

true

disabled

boolean

false

maxLabelLength

number

지정 안 함

onDelete

(event: KeyboardEvent<HTMLElement> | MouseEvent<HTMLButtonElement, MouseEvent>) => void

지정 안 함

onFileClick

(file: FileType) => void

지정 안 함

사용 가이드

권장

  • FileFieldform.AppField가 제공하는 필드 문맥 안에서 사용한다.
  • 독립 예제나 로컬 상태 연결에는 FileFieldBasevalueonChange를 사용한다.
  • acceptmultiple은 실제 업로드 정책과 동일하게 설정한다.

지양

  • FileFieldvalueonChange를 직접 전달해 Form 문맥 밖에서 제어하지 않는다.
  • 클라이언트의 accept만으로 파일 형식 검증을 끝내지 않는다.

예제

Demo

import type { ComponentProps } from 'react';
import { FileFieldBase } from '@mildang/design-system/FileField';

const STORY_DEFAULT_ARGS = { ...({
    multiple: false,
    maxVisibleChips: 3,
    maxChipLabelLength: 20,
  }), ...({}) } as ComponentProps<typeof FileFieldBase>;

const FileFieldDemoExampleRender = (args: ComponentProps<typeof FileFieldBase>) => {
    return <FileFieldBase {...args} />;
  };

export default function FileFieldDemoExample(props: Partial<ComponentProps<typeof FileFieldBase>>) {
  const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ComponentProps<typeof FileFieldBase>;
  return FileFieldDemoExampleRender(mergedProps);
}

WithState

import type { ComponentProps } from 'react';
import { FileFieldBase, FileType } from '@mildang/design-system/FileField';
import React from 'react';

const STORY_DEFAULT_ARGS = { ...({
    multiple: false,
    maxVisibleChips: 3,
    maxChipLabelLength: 20,
  }), ...({}) } as ComponentProps<typeof FileFieldBase>;

const FileFieldWithStateExampleRender = (args: ComponentProps<typeof FileFieldBase>) => {
    const [value, setValue] = React.useState<FileType[]>([]);
    return <FileFieldBase {...args} value={value} onChange={setValue} />;
  };

export default function FileFieldWithStateExample(props: Partial<ComponentProps<typeof FileFieldBase>>) {
  const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ComponentProps<typeof FileFieldBase>;
  return FileFieldWithStateExampleRender(mergedProps);
}

WithPresetValue

import type { ComponentProps } from 'react';
import { FileFieldBase, FileType } from '@mildang/design-system/FileField';
import React from 'react';

const STORY_DEFAULT_ARGS = { ...({
    multiple: false,
    maxVisibleChips: 3,
    maxChipLabelLength: 20,
  }), ...({
    value: [
      { url: 'https://example.com/sample.pdf', name: 'sample.pdf' },
      { url: 'https://example.com/image.png', name: 'image.png' },
    ],
  }) } as ComponentProps<typeof FileFieldBase>;

const FileFieldWithPresetValueExampleRender = (args: ComponentProps<typeof FileFieldBase>) => {
    const [value, setValue] = React.useState<FileType[]>(args.value || []);
    return <FileFieldBase {...args} value={value} onChange={setValue} />;
  };

export default function FileFieldWithPresetValueExample(props: Partial<ComponentProps<typeof FileFieldBase>>) {
  const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ComponentProps<typeof FileFieldBase>;
  return FileFieldWithPresetValueExampleRender(mergedProps);
}

MultipleAccept

import type { ComponentProps } from 'react';
import { FileFieldBase, FileType } from '@mildang/design-system/FileField';
import React from 'react';

const STORY_DEFAULT_ARGS = { ...({
    multiple: false,
    maxVisibleChips: 3,
    maxChipLabelLength: 20,
  }), ...({
    multiple: true,
  }) } as ComponentProps<typeof FileFieldBase>;

const FileFieldMultipleAcceptExampleRender = (args: ComponentProps<typeof FileFieldBase>) => {
    const [value, setValue] = React.useState<FileType[]>([]);

    return <FileFieldBase {...args} value={value} onChange={setValue} />;
  };

export default function FileFieldMultipleAcceptExample(props: Partial<ComponentProps<typeof FileFieldBase>>) {
  const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ComponentProps<typeof FileFieldBase>;
  return FileFieldMultipleAcceptExampleRender(mergedProps);
}

WithTanstackForm

import type { ComponentProps } from 'react';
import { FileFieldBase, FileType } from '@mildang/design-system/FileField';
import { Flex } from '@mildang/styled-system/jsx';
import { useAppForm } from '@mildang/design-system/Form';
import { Button } from '@mildang/design-system/Button';

type FormValues = { files: FileType[] };

const STORY_DEFAULT_ARGS = { ...({
    multiple: false,
    maxVisibleChips: 3,
    maxChipLabelLength: 20,
  }), ...({}) } as ComponentProps<typeof FileFieldBase>;

const FileFieldWithTanstackFormExampleRender = (args: ComponentProps<typeof FileFieldBase>) => {
    const form = useAppForm({
      defaultValues: {
        files: [],
      } as FormValues,
      onSubmit: async ({ value }) => {
        console.log(value);
      },
    });
    return (
      <form
        onSubmit={(e) => {
          e.preventDefault();
          form.handleSubmit();
        }}
      >
        <Flex direction="column" gap={8}>
          <form.AppField name="files" children={(field) => <field.FileField {...args} />} />
          <Button variant="primary" type="submit" loading={form.state.isSubmitting}>
            전송
          </Button>
        </Flex>
      </form>
    );
  };

export default function FileFieldWithTanstackFormExample(props: Partial<ComponentProps<typeof FileFieldBase>>) {
  const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ComponentProps<typeof FileFieldBase>;
  return FileFieldWithTanstackFormExampleRender(mergedProps);
}