feat(component): new file-input component (#166)
* file input field * input file * empty lines * dropzone value update * dropzone value update * fix drag and drop * Update docs/components/fileInput.md Co-authored-by: Ilya Artamonov <ilya.sosidka@gmail.com> * Update docs/components/fileInput/examples/FileInpDropZone.vue Co-authored-by: Ilya Artamonov <ilya.sosidka@gmail.com> * Update src/components/FileInput/FileInput.vue Co-authored-by: Ilya Artamonov <ilya.sosidka@gmail.com> * Update src/components/FileInput/FileInput.vue Co-authored-by: Ilya Artamonov <ilya.sosidka@gmail.com> * Update src/components/FileInput/FileInput.vue Co-authored-by: Ilya Artamonov <ilya.sosidka@gmail.com> * Update src/components/FileInput/FileInput.vue Co-authored-by: Ilya Artamonov <ilya.sosidka@gmail.com> * multiple files and file type --------- Co-authored-by: Ilya Artamonov <ilya.sosidka@gmail.com>
This commit is contained in:
@@ -65,6 +65,7 @@ function getComponents() {
|
|||||||
function getFormComponents() {
|
function getFormComponents() {
|
||||||
return [
|
return [
|
||||||
{ text: 'Input', link: 'components/input' },
|
{ text: 'Input', link: 'components/input' },
|
||||||
|
{ text: 'FileInput', link: 'components/fileInput' },
|
||||||
{ text: 'Checkbox', link: 'components/checkbox' },
|
{ text: 'Checkbox', link: 'components/checkbox' },
|
||||||
{ text: 'Select', link: 'components/select' },
|
{ text: 'Select', link: 'components/select' },
|
||||||
{ text: 'Toggle', link: 'components/toggle' },
|
{ text: 'Toggle', link: 'components/toggle' },
|
||||||
|
|||||||
103
docs/components/fileInput.md
Normal file
103
docs/components/fileInput.md
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
<script setup>
|
||||||
|
import FileInpDefault from './fileInput/examples/FileInpDefault.vue'
|
||||||
|
import FileInpHelper from './fileInput/examples/FileInpHelper.vue'
|
||||||
|
import FileInpSize from './fileInput/examples/FileInpSize.vue'
|
||||||
|
import FileInpDropZone from './fileInput/examples/FileInpDropZone.vue'
|
||||||
|
import MultipleFile from './fileInput/examples/MultipleFile.vue'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
# Vue FileInput - Flowbite
|
||||||
|
|
||||||
|
#### Get started with the file input component to let the user to upload one or more files from their device storage based on multiple styles and sizes
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
Original reference: [https://flowbite.com/docs/forms/file-input/](https://flowbite.com/docs/forms/file-input/)
|
||||||
|
:::
|
||||||
|
|
||||||
|
## File upload example
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<FileInput v-model="file" label="Upload file" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import FileInput from 'flowbite-vue'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
const file = ref()
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
<FileInpDefault />
|
||||||
|
|
||||||
|
## Multiple File upload
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<FileInput multiple v-model="file" label="Multiple upload" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import FileInput from 'flowbite-vue'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
const file = ref([])
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
<MultipleFile />
|
||||||
|
|
||||||
|
|
||||||
|
## Helper text
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<FileInput label="Upload file">
|
||||||
|
<p class='!mt-1 text-sm text-gray-500 dark:text-gray-300'>SVG, PNG, JPG or GIF (MAX. 800x400px).</p>
|
||||||
|
</FileInput>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import FileInput from 'flowbite-vue'
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
<FileInpHelper />
|
||||||
|
|
||||||
|
## Sizes
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<FileInput size="xs" label="Small size" />
|
||||||
|
<FileInput size="sm" label="Default size" />
|
||||||
|
<FileInput size="lg" label="Large size" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import FileInput from 'flowbite-vue'
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
<FileInpSize />
|
||||||
|
|
||||||
|
## Dropone
|
||||||
|
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<FileInput :dropzone="true">
|
||||||
|
<p class="!mt-1 text-xs text-gray-500 dark:text-gray-400">
|
||||||
|
SVG, PNG, JPG or GIF (MAX. 800x400px)
|
||||||
|
</p>
|
||||||
|
</FileInput>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import FileInput from 'flowbite-vue'
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
<FileInpDropZone />
|
||||||
13
docs/components/fileInput/examples/FileInpDefault.vue
Normal file
13
docs/components/fileInput/examples/FileInpDefault.vue
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<FileInput v-model="file" label="Upload file" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import FileInput from '../../../../src/components/FileInput/FileInput.vue'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
const file = ref()
|
||||||
|
|
||||||
|
</script>
|
||||||
14
docs/components/fileInput/examples/FileInpDropZone.vue
Normal file
14
docs/components/fileInput/examples/FileInpDropZone.vue
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<template>
|
||||||
|
<FileInput v-model="file" :dropzone="true">
|
||||||
|
<p class="!mt-1 text-xs text-gray-500 dark:text-gray-400">
|
||||||
|
SVG, PNG, JPG or GIF (MAX. 800x400px)
|
||||||
|
</p>
|
||||||
|
</FileInput>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import FileInput from '../../../../src/components/FileInput/FileInput.vue'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
const file = ref('')
|
||||||
|
</script>
|
||||||
14
docs/components/fileInput/examples/FileInpHelper.vue
Normal file
14
docs/components/fileInput/examples/FileInpHelper.vue
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<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>
|
||||||
|
</FileInput>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import FileInput from '../../../../src/components/FileInput/FileInput.vue'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
const file = ref('')
|
||||||
|
</script>
|
||||||
12
docs/components/fileInput/examples/FileInpSize.vue
Normal file
12
docs/components/fileInput/examples/FileInpSize.vue
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<template>
|
||||||
|
<div class='flex flex-col gap-5'>
|
||||||
|
<FileInput size="xs" label="Small size" />
|
||||||
|
<FileInput size="sm" label="Default size" />
|
||||||
|
<FileInput size="lg" label="Large size" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import FileInput from '../../../../src/components/FileInput/FileInput.vue'
|
||||||
|
|
||||||
|
</script>
|
||||||
19
docs/components/fileInput/examples/MultipleFile.vue
Normal file
19
docs/components/fileInput/examples/MultipleFile.vue
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<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 v-for="file in files" :id="file">
|
||||||
|
{{ file.name }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import FileInput from '../../../../src/components/FileInput/FileInput.vue'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
const files = ref([])
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
96
src/components/FileInput/FileInput.vue
Normal file
96
src/components/FileInput/FileInput.vue
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div v-if="!dropzone">
|
||||||
|
<label>
|
||||||
|
<span :class="labelClasses">{{ label }}</span>
|
||||||
|
<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">
|
||||||
|
<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>
|
||||||
|
<div v-if="!model">
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
<input type="file" class="hidden" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import { useFileInputClasses } from '@/components/FileInput/composables/useFileInputClasses'
|
||||||
|
|
||||||
|
interface FileInputProps {
|
||||||
|
modelValue?: any;
|
||||||
|
label?: string;
|
||||||
|
size?: string;
|
||||||
|
dropzone?: boolean;
|
||||||
|
multiple?: boolean;
|
||||||
|
}
|
||||||
|
const props = withDefaults(defineProps<FileInputProps>(), {
|
||||||
|
value: null,
|
||||||
|
label: '',
|
||||||
|
size: 'sm',
|
||||||
|
dropzone: false,
|
||||||
|
multiple: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
const model = computed({
|
||||||
|
get() {
|
||||||
|
return props.modelValue
|
||||||
|
},
|
||||||
|
set(val) {
|
||||||
|
emit('update:modelValue', val)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleChange = (event: Event) => {
|
||||||
|
const target = event.target as HTMLInputElement
|
||||||
|
if(props.multiple) { model.value = target.files }
|
||||||
|
else { model.value = target.files?.[0] }
|
||||||
|
}
|
||||||
|
|
||||||
|
const dropFileHandler = (event: any) => {
|
||||||
|
event.preventDefault()
|
||||||
|
|
||||||
|
if (event.dataTransfer.items) {
|
||||||
|
[...event.dataTransfer.items].forEach((item, i) => {
|
||||||
|
if (item.kind === 'file') {
|
||||||
|
const file = item.getAsFile()
|
||||||
|
model.value = file
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
[...event.dataTransfer.files].forEach((file, i) => {
|
||||||
|
model.value = file
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const dragOverHandler = (event: Event) => {
|
||||||
|
event.preventDefault()
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
fileInpClasses,
|
||||||
|
labelClasses,
|
||||||
|
dropzoneLabelClasses,
|
||||||
|
dropzoneWrapClasses,
|
||||||
|
dropzoneTextClasses,
|
||||||
|
} = useFileInputClasses(props.size)
|
||||||
|
</script>
|
||||||
38
src/components/FileInput/composables/useFileInputClasses.ts
Normal file
38
src/components/FileInput/composables/useFileInputClasses.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
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 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 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 labelClasses = computed(() => {
|
||||||
|
return fileInpLabelClasses
|
||||||
|
})
|
||||||
|
|
||||||
|
const dropzoneLabelClasses = computed(() => {
|
||||||
|
return fileInpDropzoneClasses
|
||||||
|
})
|
||||||
|
|
||||||
|
const dropzoneWrapClasses = computed(() => {
|
||||||
|
return fileDropzoneWrapClasses
|
||||||
|
})
|
||||||
|
|
||||||
|
const dropzoneTextClasses = computed(() => {
|
||||||
|
return fileDropzoneDefaultTextClasses
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
fileInpClasses,
|
||||||
|
labelClasses,
|
||||||
|
dropzoneLabelClasses,
|
||||||
|
dropzoneWrapClasses,
|
||||||
|
dropzoneTextClasses,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -62,6 +62,6 @@ export { default as Range } from './components/Range/Range.vue'
|
|||||||
|
|
||||||
export { default as Radio } from './components/Radio/Radio.vue'
|
export { default as Radio } from './components/Radio/Radio.vue'
|
||||||
|
|
||||||
export { default as Textarea } from './components/Textarea/Textarea.vue'
|
export { default as FileInput } from './components/FileInput/FileInput.vue'
|
||||||
|
|
||||||
export * from './composables'
|
export * from './composables'
|
||||||
|
|||||||
Reference in New Issue
Block a user