refactor: Some improvements for File input component

This commit is contained in:
Ilya Artamonov
2023-07-15 22:06:26 +03:00
parent 3501e946d3
commit 2490053dd5
7 changed files with 91 additions and 74 deletions

View File

@@ -8,6 +8,5 @@
import FileInput from '../../../../src/components/FileInput/FileInput.vue' import FileInput from '../../../../src/components/FileInput/FileInput.vue'
import { ref } from 'vue' import { ref } from 'vue'
const file = ref() const file = ref(null)
</script> </script>

View File

@@ -1,8 +1,6 @@
<template> <template>
<FileInput v-model="file" :dropzone="true"> <FileInput v-model="file" :dropzone="true">
<p class="!mt-1 text-xs text-gray-500 dark:text-gray-400"> <p class="!mt-1 text-xs text-gray-500 dark:text-gray-400">SVG, PNG, JPG or GIF (MAX. 800x400px)</p>
SVG, PNG, JPG or GIF (MAX. 800x400px)
</p>
</FileInput> </FileInput>
</template> </template>
@@ -10,5 +8,5 @@
import FileInput from '../../../../src/components/FileInput/FileInput.vue' import FileInput from '../../../../src/components/FileInput/FileInput.vue'
import { ref } from 'vue' import { ref } from 'vue'
const file = ref('') const file = ref(null)
</script> </script>

View File

@@ -1,7 +1,7 @@
<template> <template>
<div> <div>
<FileInput v-model="file" label="Upload file"> <FileInput v-model="file" label="Upload file">
<p class='!mt-1 text-sm text-gray-500 dark:text-gray-300'>SVG, PNG, JPG or GIF (MAX. 800x400px).</p> <p class="!mt-1 text-sm text-gray-500 dark:text-gray-300">SVG, PNG, JPG or GIF (MAX. 800x400px).</p>
</FileInput> </FileInput>
</div> </div>
</template> </template>
@@ -10,5 +10,5 @@
import FileInput from '../../../../src/components/FileInput/FileInput.vue' import FileInput from '../../../../src/components/FileInput/FileInput.vue'
import { ref } from 'vue' import { ref } from 'vue'
const file = ref('') const file = ref(null)
</script> </script>

View File

@@ -1,12 +1,13 @@
<template> <template>
<div class='flex flex-col gap-5'> <div class="flex flex-col gap-5">
<FileInput size="xs" label="Small size" /> <FileInput size="xs" v-model="file" label="Small size" />
<FileInput size="sm" label="Default size" /> <FileInput size="sm" v-model="file" label="Default size" />
<FileInput size="lg" label="Large size" /> <FileInput size="lg" v-model="file" label="Large size" />
</div> </div>
</template> </template>
<script setup> <script setup>
import FileInput from '../../../../src/components/FileInput/FileInput.vue' import FileInput from '../../../../src/components/FileInput/FileInput.vue'
import { ref } from 'vue'
const file = ref(null)
</script> </script>

View File

@@ -2,7 +2,7 @@
<div> <div>
<FileInput multiple v-model="files" label="Upload file" /> <FileInput multiple v-model="files" label="Upload file" />
<div class="mt-4 -mb-7 border-[1px] border-gray-300 dark:border-gray-600 p-2 rounded-md" v-if="files.length !== 0"> <div class="mt-4 -mb-7 border-[1px] border-gray-300 dark:border-gray-600 p-2 rounded-md" v-if="files.length !== 0">
<div v-for="file in files" :id="file"> <div v-for="file in files" :key="file">
{{ file.name }} {{ file.name }}
</div> </div>
</div> </div>
@@ -14,6 +14,4 @@
import { ref } from 'vue' import { ref } from 'vue'
const files = ref([]) const files = ref([])
</script> </script>

View File

@@ -3,26 +3,30 @@
<div v-if="!dropzone"> <div v-if="!dropzone">
<label> <label>
<span :class="labelClasses">{{ label }}</span> <span :class="labelClasses">{{ label }}</span>
<input :class="fileInpClasses" :multiple="multiple" @change="handleChange" type="file"> <input :class="fileInpClasses" :multiple="multiple" @change="handleChange" type="file" />
</label> </label>
<slot /> <slot />
</div> </div>
<div v-else @change="handleChange" @drop="dropFileHandler" @dragover="dragOverHandler" <div v-else @change="handleChange" @drop="dropFileHandler" @dragover="dragOverHandler" class="flex items-center justify-center">
class="flex items-center justify-center">
<label :class="dropzoneLabelClasses"> <label :class="dropzoneLabelClasses">
<div :class="dropzoneWrapClasses"> <div :class="dropzoneWrapClasses">
<svg class="w-8 h-8 text-gray-500 dark:text-gray-400" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" <svg class="w-8 h-8 text-gray-500 dark:text-gray-400" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 16">
fill="none" viewBox="0 0 20 16"> <path
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" stroke="currentColor"
d="M13 13h3a3 3 0 0 0 0-6h-.025A5.56 5.56 0 0 0 16 6.5 5.5 5.5 0 0 0 5.207 5.021C5.137 5.017 5.071 5 5 5a4 4 0 0 0 0 8h2.167M10 15V6m0 0L8 8m2-2 2 2" /> stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M13 13h3a3 3 0 0 0 0-6h-.025A5.56 5.56 0 0 0 16 6.5 5.5 5.5 0 0 0 5.207 5.021C5.137 5.017 5.071 5 5 5a4 4 0 0 0 0 8h2.167M10 15V6m0 0L8 8m2-2 2 2"
/>
</svg> </svg>
<div v-if="!model"> <div v-if="!model">
<p :class="dropzoneTextClasses"><span class="font-semibold">Click to upload</span> or <p :class="dropzoneTextClasses">
drag and drop <span class="font-semibold"> Click to upload </span>
or drag and drop
</p> </p>
<slot /> <slot />
</div> </div>
<p v-else>File: {{ model.name }}</p> <p v-else>File: {{ dropZoneText }}</p>
</div> </div>
<input type="file" class="hidden" /> <input type="file" class="hidden" />
</label> </label>
@@ -33,21 +37,35 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue' import { computed } from 'vue'
import { useFileInputClasses } from '@/components/FileInput/composables/useFileInputClasses' import { useFileInputClasses } from '@/components/FileInput/composables/useFileInputClasses'
import { isArray } from 'lodash-es'
interface FileInputProps { interface FileInputProps {
modelValue?: any; modelValue?: File | File[] | null
label?: string; label?: string
size?: string; size?: string
dropzone?: boolean; dropzone?: boolean
multiple?: boolean; multiple?: boolean
} }
const props = withDefaults(defineProps<FileInputProps>(), { const props = withDefaults(defineProps<FileInputProps>(), {
value: null, modelValue: null,
label: '', label: '',
size: 'sm', size: 'sm',
dropzone: false, dropzone: false,
multiple: false, multiple: false,
}) })
const dropZoneText = computed(() => {
if (isArray(props.modelValue)) {
return props.modelValue.map((el) => el.name).join(', ')
} else if (props.modelValue instanceof FileList) {
return Array.from(props.modelValue)
.map((el) => el.name)
.join(',')
} else if (props.modelValue instanceof File) {
return props.modelValue.name || ''
} else {
return ''
}
})
const emit = defineEmits(['update:modelValue']) const emit = defineEmits(['update:modelValue'])
const model = computed({ const model = computed({
@@ -61,22 +79,29 @@ const model = computed({
const handleChange = (event: Event) => { const handleChange = (event: Event) => {
const target = event.target as HTMLInputElement const target = event.target as HTMLInputElement
if(props.multiple) { model.value = target.files } if (props.multiple) {
else { model.value = target.files?.[0] } model.value = Array.from(target.files ?? [])
} else {
model.value = target.files?.[0] ?? null
}
} }
const dropFileHandler = (event: any) => { const dropFileHandler = (event: DragEvent) => {
event.preventDefault() event.preventDefault()
const arr: File[] = []
if (event.dataTransfer.items) { if (event.dataTransfer?.items) {
[...event.dataTransfer.items].forEach((item, i) => { Object.values(event.dataTransfer.items).forEach((item: DataTransferItem) => {
if (item.kind === 'file') { if (item.kind === 'file') {
const file = item.getAsFile() arr.push(item.getAsFile() as File)
model.value = file
} }
}) })
if (props.multiple) {
model.value = arr
} else { } else {
[...event.dataTransfer.files].forEach((file, i) => { model.value = arr[0]
}
} else if (event.dataTransfer?.files) {
Object.values(event.dataTransfer.files).forEach((file: File) => {
model.value = file model.value = file
}) })
} }
@@ -86,11 +111,5 @@ const dragOverHandler = (event: Event) => {
event.preventDefault() event.preventDefault()
} }
const { const { fileInpClasses, labelClasses, dropzoneLabelClasses, dropzoneWrapClasses, dropzoneTextClasses } = useFileInputClasses(props.size)
fileInpClasses,
labelClasses,
dropzoneLabelClasses,
dropzoneWrapClasses,
dropzoneTextClasses,
} = useFileInputClasses(props.size)
</script> </script>

View File

@@ -1,9 +1,11 @@
import { simplifyTailwindClasses } from '@/utils/simplifyTailwindClasses' import { simplifyTailwindClasses } from '@/utils/simplifyTailwindClasses'
import { computed } from 'vue' import { computed } from 'vue'
const fileInpDefaultClasses = 'block w-full text-sm text-gray-900 border-[1px] border-gray-300 rounded-lg cursor-pointer bg-gray-50 dark:text-gray-400 focus:outline-none dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400' const fileInpDefaultClasses =
'block w-full text-sm text-gray-900 border-[1px] border-gray-300 rounded-lg cursor-pointer bg-gray-50 dark:text-gray-400 focus:outline-none dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400'
const fileInpLabelClasses = 'block mb-2 text-sm font-medium text-gray-900 dark:text-white' const fileInpLabelClasses = 'block mb-2 text-sm font-medium text-gray-900 dark:text-white'
const fileInpDropzoneClasses = 'flex flex-col items-center justify-center w-full h-64 border-2 border-gray-300 border-dashed 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' const fileInpDropzoneClasses =
'flex flex-col items-center justify-center w-full h-64 border-2 border-gray-300 border-dashed 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'
const fileDropzoneWrapClasses = 'flex flex-col items-center justify-center pt-5 pb-6' const fileDropzoneWrapClasses = 'flex flex-col items-center justify-center pt-5 pb-6'
const fileDropzoneDefaultTextClasses = '!-mb-2 text-sm text-gray-500 dark:text-gray-400' const fileDropzoneDefaultTextClasses = '!-mb-2 text-sm text-gray-500 dark:text-gray-400'