파일타입의 Input은 Browser에서 정해놓은 defualt 스타일이 적용되어 있으며,
일반적으로 파일 선택 버튼이나, placeholder를 수정할 수가 없다.
이번 포스트에서는 내가 원하는대로 file type의 input의 스타일을 수정하고, 버튼과 placeholder 텍스트를 수정하는 방법에 대해 정리해보고자 한다.
완성 Input Component
위 사진은 커스텀해서 만들 Input의 모습이다.
드래그엔 드랍이 가능하며, 버튼 색, 버튼 Text, Placeholder 까지 모두 수정이 가능하다.
(참고로 디자인은 tailwind의 input을 벤치마크했다.)
참고로, 이번 포스트에서 나는 편의상 vue3를 사용할 예정이지만 자신이 편한 프레임워크를 사용해도 상관없다.
전체 로직
전체로직은 아래와 같다.
1. 전체 HTML 구조는 아래처럼 구성한다.
- span은 버튼Text,
- p는 placeholder과 업로드된 파일명을 표시하기 위함이다.
<label>
<span></span>
<p></p>
<input />
</label>
2. Input은 display: none을 먹여 스타일이 안보이도록 한다.
3. label이 클릭되었을 때, 작동하는 함수를 <input @change="fileHandler"/> 형식으로 붙여준다.
4. fileHandler는 파일명을 p태그에 업데이트 및 부모 컴포넌트에 file을 업데이트 해준다.
바로 전체 코드를 보면서 설명해보도록 하자.
전체코드 - FileInputComponent
<template>
<div class="atom w-full relative">
<label
:for="uuid"
@dragover.prevent
@dragenter.prevent
@drop.prevent="onDrop"
class="relative z-1 flex w-full items-center h-11 border border-gray-300 rounded-lg cursor-pointer bg-gray-50 dark:hover:bg-bray-800 dark:bg-gray-700 hover:bg-gray-100 dark:border-gray-600 dark:hover:border-gray-500 dark:hover:bg-gray-600"
>
<span
class="h-full bg-gray-800 text-white text-sm px-4 py-2 rounded-l-lg flex items-center font-medium"
>
{{ buttonText || '파일 선택' }}
</span>
<p class="pl-2 text-sm text-gray-500 dark:text-gray-400">
{{ uploadedFile.length === 0 ? (placeholder || '선택된 파일이 없습니다.') : uploadedFile }}
</p>
<input
:id="uuid"
class="hidden"
type='file'
@change="(e) => fileUploadHandler(e)"
/>
</label>
</div>
</template>
<script setup lang="ts">
interface FileInputProps{
buttonText?: string
placeholder?: string
}
import { onMounted, ref } from 'vue';
const props = defineProps<FileInputProps>()
const emit = defineEmits(["update:modelValue"]);
const uploadedFile = ref('');
const uuid = crypto.randomUUID();
function onDrop(e) {
fileUploadHandler(e, 'drop')
}
function fileUploadHandler(e, command?: string) {
let files: FileList | null = null;
if (command === 'drop') {
files = e.dataTransfer.files;
if (files && files.length > 0) {
uploadedFile.value = files[0].name
}
}
else {
const target = e.target as HTMLInputElement;
files = target.files;
if (files && files.length > 0) {
uploadedFile.value = files[0].name
} else {
//취소한 경우,
uploadedFile.value = '';
}
}
emit('update:modelValue', files)
}
</script>
주의해서 볼 점은 아래와 같다.
1. label의 for과 input의 id는 유니크해야 한다.
물론, props를 이용해 개발자가 넘겨준 유니크한 값을 사용해도 되지만, 실수를 방지하기 위해 그냥 uuid를 사용했다.
(참고로, 한 페이지내에서 같은 id를 가진 input이 2개이상 있다면 항상 첫 번째에 있는 input에만 업데이트된다.)
2. Drop 이벤트를 label에 걸어준다.
앞서 말한 것처럼 Input은 display:none; 우리가 파일을 drop하는 위치는 label이다.
이때 주의할 점은 dragover, dragenter 모두 e.preventDefault()를 실행시켜줘야 한다. (vue에서는 .prevent로 대체할 수 있다.)
3. Drop Event시 파일위치
일반적으로 버튼을 클릭 해, 파일을 선택하였을 때에는 event.target.files에 업로드한 파일들이 위치하지만,
Drop 이벤트일 경우에는 event.dataTransfer.files에 위치한다.
따라서 fileUploadHandler에서 적절한 분기처리가 필요하다.
4. props, emit
위 코드에서 볼 수 있듯이, 현재는 buttonText와 placeholder만을 props로 받고 있다. 만약 버튼 색상과 같이 추가적인 custom이 필요하다면 위 코드에 추가하면 된다.
emit을 통해 선택한 file을 Parent 컴포넌트로 전달해준다.
어떻게 emit을 넘겨주는지는 아래 전체코드 - 사용 컴포넌트(Parent)를 참고한다.
5. props default 값 설정
default 값은 삼항연산자를 이용하여 inline으로 설정해주었다.
6. 복수의 파일 업로드가 필요한 경우, multiple 을 input에 추가한다.
상기 코드는 단일 파일 업로드를 가정하고 있다. 복수의 파일 업로드가 필요한 경우, input에 multiple을 추가하고, fileUploadHandler부분 역시 약간의 수정이 필요하다.
전체코드 - 사용 컴포넌트(Parent)
부모컴포넌트에서 FileInput Component를 어떻게 사용하는지 정리한다.
<FileInput @update:model-value="selectFileHandler" :button-text="select file"/>
<script setup lang="ts">
//...
const selectedFile = ref(null as File | null);
function selectFileHandler(files: FileList | null) {
if (files) {
selectedFile = files[0]
} else {
selectedFile = files
}
}
</script>
사용방법은 매우 간단하다. @를 이용해 emit을 FileInput Component에 전달해주고,
인자로 전달받은 files를 내부 변수에 담아준다.
여기에서는 props로 button-text를 넘겨주는 예제를 보여주고 있다. 만약 더 필요한 props가 있다면 같은 방식으로 넘겨주면 된다.
'Vue' 카테고리의 다른 글
[Frontend]이미지 로드 시간 줄이기 (0) | 2023.12.11 |
---|---|
[Vue3] 'defineProps' is not defined 해결 (0) | 2023.11.27 |
[Vue] Toggle Switch 만들기 (feat. Atomic Design) (0) | 2023.11.08 |
Vue3 Snippet 만들기 (0) | 2023.11.06 |
Vue에서 Svg사용하기 (0) | 2023.10.27 |