refactor: Some improvements for File input component
This commit is contained in:
@@ -3,26 +3,30 @@
|
||||
<div v-if="!dropzone">
|
||||
<label>
|
||||
<span :class="labelClasses">{{ label }}</span>
|
||||
<input :class="fileInpClasses" :multiple="multiple" @change="handleChange" type="file">
|
||||
<input :class="fileInpClasses" :multiple="multiple" @change="handleChange" type="file" />
|
||||
</label>
|
||||
<slot />
|
||||
</div>
|
||||
<div v-else @change="handleChange" @drop="dropFileHandler" @dragover="dragOverHandler"
|
||||
class="flex items-center justify-center">
|
||||
<div v-else @change="handleChange" @drop="dropFileHandler" @dragover="dragOverHandler" class="flex items-center justify-center">
|
||||
<label :class="dropzoneLabelClasses">
|
||||
<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"
|
||||
fill="none" viewBox="0 0 20 16">
|
||||
<path stroke="currentColor" 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 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">
|
||||
<path
|
||||
stroke="currentColor"
|
||||
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>
|
||||
<div v-if="!model">
|
||||
<p :class="dropzoneTextClasses"><span class="font-semibold">Click to upload</span> or
|
||||
drag and drop
|
||||
<p :class="dropzoneTextClasses">
|
||||
<span class="font-semibold"> Click to upload </span>
|
||||
or drag and drop
|
||||
</p>
|
||||
<slot />
|
||||
</div>
|
||||
<p v-else>File: {{ model.name }}</p>
|
||||
<p v-else>File: {{ dropZoneText }}</p>
|
||||
</div>
|
||||
<input type="file" class="hidden" />
|
||||
</label>
|
||||
@@ -33,21 +37,35 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useFileInputClasses } from '@/components/FileInput/composables/useFileInputClasses'
|
||||
import { isArray } from 'lodash-es'
|
||||
|
||||
interface FileInputProps {
|
||||
modelValue?: any;
|
||||
label?: string;
|
||||
size?: string;
|
||||
dropzone?: boolean;
|
||||
multiple?: boolean;
|
||||
modelValue?: File | File[] | null
|
||||
label?: string
|
||||
size?: string
|
||||
dropzone?: boolean
|
||||
multiple?: boolean
|
||||
}
|
||||
const props = withDefaults(defineProps<FileInputProps>(), {
|
||||
value: null,
|
||||
modelValue: null,
|
||||
label: '',
|
||||
size: 'sm',
|
||||
dropzone: 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 model = computed({
|
||||
@@ -61,22 +79,29 @@ const model = computed({
|
||||
|
||||
const handleChange = (event: Event) => {
|
||||
const target = event.target as HTMLInputElement
|
||||
if(props.multiple) { model.value = target.files }
|
||||
else { model.value = target.files?.[0] }
|
||||
if (props.multiple) {
|
||||
model.value = Array.from(target.files ?? [])
|
||||
} else {
|
||||
model.value = target.files?.[0] ?? null
|
||||
}
|
||||
}
|
||||
|
||||
const dropFileHandler = (event: any) => {
|
||||
const dropFileHandler = (event: DragEvent) => {
|
||||
event.preventDefault()
|
||||
|
||||
if (event.dataTransfer.items) {
|
||||
[...event.dataTransfer.items].forEach((item, i) => {
|
||||
const arr: File[] = []
|
||||
if (event.dataTransfer?.items) {
|
||||
Object.values(event.dataTransfer.items).forEach((item: DataTransferItem) => {
|
||||
if (item.kind === 'file') {
|
||||
const file = item.getAsFile()
|
||||
model.value = file
|
||||
arr.push(item.getAsFile() as File)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
[...event.dataTransfer.files].forEach((file, i) => {
|
||||
if (props.multiple) {
|
||||
model.value = arr
|
||||
} else {
|
||||
model.value = arr[0]
|
||||
}
|
||||
} else if (event.dataTransfer?.files) {
|
||||
Object.values(event.dataTransfer.files).forEach((file: File) => {
|
||||
model.value = file
|
||||
})
|
||||
}
|
||||
@@ -86,11 +111,5 @@ const dragOverHandler = (event: Event) => {
|
||||
event.preventDefault()
|
||||
}
|
||||
|
||||
const {
|
||||
fileInpClasses,
|
||||
labelClasses,
|
||||
dropzoneLabelClasses,
|
||||
dropzoneWrapClasses,
|
||||
dropzoneTextClasses,
|
||||
} = useFileInputClasses(props.size)
|
||||
const { fileInpClasses, labelClasses, dropzoneLabelClasses, dropzoneWrapClasses, dropzoneTextClasses } = useFileInputClasses(props.size)
|
||||
</script>
|
||||
|
||||
@@ -1,20 +1,22 @@
|
||||
import { simplifyTailwindClasses } from '@/utils/simplifyTailwindClasses'
|
||||
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 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 fileDropzoneDefaultTextClasses = '!-mb-2 text-sm text-gray-500 dark:text-gray-400'
|
||||
|
||||
export function useFileInputClasses(size: string) {
|
||||
const fileInpClasses = computed(() => {
|
||||
return simplifyTailwindClasses(fileInpDefaultClasses, 'text-' + size)
|
||||
})
|
||||
const fileInpClasses = computed(() => {
|
||||
return simplifyTailwindClasses(fileInpDefaultClasses, 'text-' + size)
|
||||
})
|
||||
|
||||
const labelClasses = computed(() => {
|
||||
return fileInpLabelClasses
|
||||
})
|
||||
const labelClasses = computed(() => {
|
||||
return fileInpLabelClasses
|
||||
})
|
||||
|
||||
const dropzoneLabelClasses = computed(() => {
|
||||
return fileInpDropzoneClasses
|
||||
@@ -28,11 +30,11 @@ export function useFileInputClasses(size: string) {
|
||||
return fileDropzoneDefaultTextClasses
|
||||
})
|
||||
|
||||
return {
|
||||
fileInpClasses,
|
||||
labelClasses,
|
||||
return {
|
||||
fileInpClasses,
|
||||
labelClasses,
|
||||
dropzoneLabelClasses,
|
||||
dropzoneWrapClasses,
|
||||
dropzoneTextClasses,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user