File Upload
File upload component is used to upload multiple files.
The native input file element is quite difficult to style and doesn’t provide a drag-n-drop version.
The file upload component doesn’t handle the actual file uploading process. It only handles the UI and the state of the file upload.
Drag and drop files here
or
Drag and drop files here
or
Features
Install
Install the component from your command line.
Anatomy
Import all parts and piece them together.
<script setup lang="ts"> import * as fileUpload from "@destyler/file-upload" import { normalizeProps, useMachine } from "@destyler/vue" import { computed,useId } from "vue"
const [state, send] = useMachine(fileUpload.machine({ id: useId() }))
const api = computed(() => fileUpload.connect(state.value, send, normalizeProps), )</script>
<template> <div v-bind="api.getRootProps()"> <div v-bind="api.getDropzoneProps()"> <input v-bind="api.getHiddenInputProps()" /> <button v-bind="api.getTriggerProps()"></button> </div>
<ul v-bind="api.getItemGroupProps()"> <li v-for="file in api.acceptedFiles" :key="file.name" v-bind="api.getItemProps({ file })" > <div v-bind="api.getItemNameProps({ file })"></div> <button v-bind="api.getItemDeleteTriggerProps({ file })" ></button> </li> </ul> </div></template>import * as fileUpload from '@destyler/file-upload'import { normalizeProps, useMachine } from '@destyler/react'import { useId } from 'react'
export default function FileUpload() {
const [state, send] = useMachine(fileUpload.machine({ id: useId() }))
const api = fileUpload.connect(state, send, normalizeProps)
return ( <div {...api.getRootProps()}> <div {...api.getDropzoneProps()}> <input {...api.getHiddenInputProps()} /> <button {...api.getTriggerProps()}> </button> </div>
<ul {...api.getItemGroupProps()} > {api.acceptedFiles.map(file => ( <li key={file.name} {...api.getItemProps({ file })} > <div {...api.getItemNameProps({ file })} ></div> <button {...api.getItemDeleteTriggerProps({ file })} ></button> </li> ))} </ul> </div> )}<script lang="ts"> import * as fileUpload from "@destyler/file-upload" import { normalizeProps, useMachine } from "@destyler/svelte"
const id = $props.id()
const [state, send] = useMachine(fileUpload.machine({ id }))
const api = $derived(fileUpload.connect(state, send, normalizeProps))</script>
<div {...api.getRootProps()}> <div {...api.getDropzoneProps()}> <input {...api.getHiddenInputProps()} />
<button {...api.getTriggerProps()} ></button> </div>
<ul {...api.getItemGroupProps()}> {#each api.acceptedFiles as file (file.name)} <li {...api.getItemProps({ file })} > <div {...api.getItemNameProps({ file })} ></div> <button {...api.getItemDeleteTriggerProps({ file })} ></button> </li> {/each} </ul></div>import * as fileUpload from '@destyler/file-upload'import { normalizeProps, useMachine } from '@destyler/solid'import { createMemo, createUniqueId, For } from 'solid-js'
export default function FileUpload() {
const [state, send] = useMachine(fileUpload.machine({ id: createUniqueId() }))
const api = createMemo(() => fileUpload.connect(state, send, normalizeProps))
return ( <div {...api().getRootProps()}> <div {...api().getDropzoneProps()}> <input {...api().getHiddenInputProps()} /> <button {...api().getTriggerProps()} ></button> </div>
<ul {...api().getItemGroupProps()} > <For each={api().acceptedFiles}> {file => ( <li {...api().getItemProps({ file })} > <div {...api().getItemNameProps({ file })}></div> <button {...api().getItemDeleteTriggerProps({ file })} ></button> </li> )} </For> </ul> </div> )}Setting the accepted file types
Use the accept attribute to set the accepted file types.
const [state, send] = useMachine( fileUpload.machine({ accept: "image/*", }),)Alternatively, you can provide an object with a MIME type and an array of file extensions.
const [state, send] = useMachine( fileUpload.machine({ accept: { "image/png": [".png"], "text/html": [".html", ".htm"], }, }),)Setting the maximum number of files
Use the maxFiles attribute to set the maximum number of files that can be uploaded.
This will set the multiple attribute on the underlying input element.
const [state, send] = useMachine( fileUpload.machine({ maxFiles: 5, }),)Setting the maximum size per file
Use the maxFileSize attribute to set the maximum size per file that can be uploaded.
const [state, send] = useMachine( fileUpload.machine({ maxFileSize: 1024 * 1024 * 10, // 10MB }),)Listening to file changes
When files are uploaded, the onFileChange callback is invoked with the details of the accepted and rejected files.
const [state, send] = useMachine( fileUpload.machine({ onFileChange(details) { // details => { acceptedFiles: File[], rejectedFiles: { file: File, errors: [] }[] } console.log(details.acceptedFiles) console.log(details.rejectedFiles) }, }),)Usage within a form
To use the file upload within a form, set the name attribute in the machine’s context,
and ensure you render the input element api.getHiddenInputProps()
const [state, send] = useMachine( fileUpload.machine({ name: "word", }),)Displaying image preview
To display a preview of the uploaded image,
use the built-in FileReader API to read the file and set the src attribute of an image element.
const [state, send] = useMachine( fileUpload.machine({ onFileChange(details) { const reader = new FileReader() reader.onload = (event) => { const image = event.target.result // set the image as the src of an image element } reader.readAsDataURL(details.acceptedFiles[0]) }, }),)Applying custom validation
To apply custom validation, set the validate attribute to a function that returns an array of error strings.
The returned array can contain any string as an error message.
While destyler supports default errors such as TOO_MANY_FILES, FILE_INVALID_TYPE, FILE_TOO_LARGE, or FILE_TOO_SMALL,
you can return any string that represents your custom validation errors.
Return null if no validation errors are detected.
const [state, send] = useMachine( fileUpload.machine({ validate(file) { // Check if file size exceeds 10MB if (file.size > 1024 * 1024 * 10) { return ["FILE_TOO_LARGE"] } return null }, }),)Apply multiple validation errors:
const [state, send] = useMachine( fileUpload.machine({ validate(file) { const errors = []
// Check file size if (file.size > 10 * 1024 * 1024) { errors.push("FILE_TOO_LARGE") // Default error enum }
// Ensure file is a PDF if (!file.name.endsWith(".pdf")) { errors.push("ONLY_PDF_ALLOWED") // Custom error }
// Custom check: Reject duplicate files const isDuplicate = details.acceptedFiles.some( (acceptedFile) => acceptedFile.name === file.name, ) if (isDuplicate) { errors.push("FILE_EXISTS") }
return errors.length > 0 ? errors : null }, }),)Disabling drag and drop
To disable the drag and drop functionality, set the allowDrop context property to false.
const [state, send] = useMachine( fileUpload.machine({ allowDrop: false, }),)Allowing directory selection
Set the directory property to true to enable selecting directories instead of files.
This maps to the native input webkitdirectory HTML attribute and allows users to select directories and their contents.
const [state, send] = useMachine( fileUpload.machine({ directory: true, }),)Supporting media capture on mobile devices
Set the capture property to specify the media capture mechanism to capture media on the spot. The value can be:
-
userfor capturing media from the user-facing camera -
environmentfor the outward-facing camera
This behavior only works on mobile devices. On desktop devices, it will open the file system like normal.
const [state, send] = useMachine( fileUpload.machine({ capture: "user", }),)Styling guide
Earlier, we mentioned that each file upload part has a data-part attribute added to them to
select and style them in the DOM.
[data-part="root"] { /* styles for root element*/}
[data-part="dropzone"] { /* styles for root element*/}
[data-part="trigger"] { /* styles for file picker trigger */}
[data-part="label"] { /* styles for the input's label */}Dragging State
When the user drags a file over the file upload, the data-dragging attribute is added to the root and dropzone parts.
[data-part="root"][data-dragging] { /* styles for when the user is dragging a file over the file upload */}
[data-part="dropzone"][data-dragging] { /* styles for when the user is dragging a file over the file upload */}Disabled State
When the file upload is disabled, the data-disabled attribute is added to the component parts.
[data-part="root"][data-disabled] { /* styles for when the file upload is disabled */}
[data-part="dropzone"][data-disabled] { /* styles for when the file upload is disabled */}
[data-part="trigger"][data-disabled] { /* styles for when the file upload is disabled */}
[data-part="label"][data-disabled] { /* styles for when the file upload is disabled */}Methods and Properties
Machine Context
The file upload machine exposes the following context properties:
stringPartial<{ root: string; dropzone: string; hiddenInput: string; trigger: string; label: string; item: (id: string) => string; itemName: (id: string) => string; itemSizeText: (id: string) => string; itemPreview: (id: string) => string; }>IntlTranslationsRecord<string, string[]> | FileMimeType[]booleanbooleanbooleannumbernumbernumberboolean(file: File, details: FileValidateDetails) => FileError[](details: FileChangeDetails) => void(details: FileAcceptDetails) => void(details: FileRejectDetails) => void"user" | "environment"booleanbooleanstring"ltr" | "rtl"string() => ShadowRoot | Node | DocumentMachine API
The file upload api exposes the following methods:
booleanbooleanboolean() => void(file: File) => voidFile[]FileRejection[](files: File[]) => void() => void() => void(file: File) => string(file: File, cb: (url: string) => void) => VoidFunction(dt: DataTransfer) => booleanData Attributes
Root
data-scopedata-partdata-disableddata-draggingDropzone
data-scopedata-partdata-invaliddata-disableddata-draggingTrigger
data-scopedata-partdata-disableddata-invalidItem Group
data-scopedata-partdata-disabledItem
data-scopedata-partdata-disabledItem Name
data-scopedata-partdata-disabledItem Size Text
data-scopedata-partdata-disabledItem Preview
data-scopedata-partdata-disabledItem Preview Image
data-scopedata-partdata-disabledItem Delete Trigger
data-scopedata-partdata-disabledLabel
data-scopedata-partdata-disabledClear Trigger
data-scopedata-partdata-disabled