Uploader

A file uploader component for uploading files.

Usage

Note
For demonstration purposes, we will limit the file size to under 100MB for all the examples below. In real-world usage, you can increase this limit as needed.

Choose file or drag & drop to upload

Max file size: 100 MB

'use client'

import { Uploader } from '@workspace/ui/components/Uploader'
import { CustomUploaderAction } from './UploaderDemo.utils'

export function UploaderDemo() {
    return <Uploader action={new CustomUploaderAction()} maxFileSize={100 * 1024 * 1024} />
}

Custom Upload Action

There are situations where you may need to customize the Uploader's action:

  • You might want to add headers to the request or handle the backend response.
  • You need to perform some operations before uploading, such as obtaining a presigned URL.

To customize the upload action, you can extend and override the methods of the UploaderAction class. Alternatively, you can directly edit the UploaderAction class as it's part of your own codebase.

Choose file or drag & drop to upload

Max file size: 100 MB

'use client'

import { Uploader, UploaderAction } from '@workspace/ui/components/Uploader'
import { AxiosResponse } from 'axios'

interface TmpResponse {
    status: string
    data: {
        url: string
    }
}

/**
 *  In real-world usage, consider moving this class to
 *  shared folder to reuse throughout the application.
 *
 *  Alternatively, you can modify the UploaderAction class directly,
 *  since it is part of your own codebase.
 */
class CustomUploaderAction extends UploaderAction {
    constructor() {
        super('https://tmpfiles.org/api/v1/upload')
    }

    formatResponse(response: AxiosResponse<TmpResponse>) {
        const url = response.data.data.url
        const id = url.split('/').slice(-2).join('/')
        const downloadUrl = `https://tmpfiles.org/dl/${id}`

        return {
            id: response.data.data.url,
            url: downloadUrl,
        }
    }
}

export function UploaderCustom() {
    return <Uploader action={new CustomUploaderAction()} maxFileSize={100 * 1024 * 1024} />
}

Retry on Upload Failure

This demo shows how the retry button looks and works. We've intentionally used an endpoint that always returns an error, try uploading a file to see the retry feature in action.

Choose file or drag & drop to upload

Max file size: 100 MB

'use client'

import { Uploader, UploaderAction } from '@workspace/ui/components/Uploader'

export function UploaderRetry() {
    return (
        <Uploader
            action={new UploaderAction('https://tmpfiles.org/api/v1/wrong-url')}
            maxFileSize={100 * 1024 * 1024}
        />
    )
}

Disabled

Choose file or drag & drop to upload

Max file size: 100 MB

CSV
sample4.csv7.7 KB
DOC
sample2.doc32 KB
'use client'

import { Uploader } from '@workspace/ui/components/Uploader'
import { UploaderFile } from '@workspace/ui/components/UploaderItem'
import { CustomUploaderAction } from './UploaderDemo.utils'

const exampleFileList: Array<UploaderFile> = [
    {
        id: '1',
        name: 'sample4.csv',
        status: 'done',
        size: 7.7 * 1024,
        extension: 'csv',
        percent: 100,
        type: 'text/csv',
        url: 'https://filesamples.com/samples/document/csv/sample4.csv',
    },
    {
        id: '2',
        name: 'sample2.doc',
        status: 'done',
        size: 32 * 1024,
        extension: 'doc',
        percent: 100,
        type: 'application/msword',
        url: 'https://filesamples.com/samples/document/doc/sample2.doc',
    },
]
export function UploaderDisabled() {
    return (
        <Uploader
            isDisabled
            action={new CustomUploaderAction()}
            maxFileSize={100 * 1024 * 1024}
            defaultFileList={exampleFileList}
        />
    )
}

Uploader Variants

Choose different styles for the trigger and the file list

Card Style List

Choose file or drag & drop to upload

Max file size: 100 MB

CSV
DOC
'use client'

import { Uploader } from '@workspace/ui/components/Uploader'
import { UploaderFile } from '@workspace/ui/components/UploaderItem'
import { CustomUploaderAction } from './UploaderDemo.utils'

const exampleFileList: Array<UploaderFile> = [
    {
        id: '1',
        name: 'sample4.csv',
        status: 'done',
        size: 7.7 * 1024,
        extension: 'csv',
        percent: 100,
        type: 'text/csv',
        url: 'https://filesamples.com/samples/document/csv/sample4.csv',
    },
    {
        id: '2',
        name: 'sample2.doc',
        status: 'done',
        size: 32 * 1024,
        extension: 'doc',
        percent: 100,
        type: 'application/msword',
        url: 'https://filesamples.com/samples/document/doc/sample2.doc',
    },
]

export function UploaderVariantListType() {
    return (
        <Uploader
            action={new CustomUploaderAction()}
            maxFileSize={100 * 1024 * 1024}
            defaultFileList={exampleFileList}
            listType="card"
        />
    )
}

Button as Trigger

CSV
sample4.csv7.7 KB
DOC
sample2.doc32 KB
'use client'

import { Uploader } from '@workspace/ui/components/Uploader'
import { UploaderFile } from '@workspace/ui/components/UploaderItem'
import { CustomUploaderAction } from './UploaderDemo.utils'

const exampleFileList: Array<UploaderFile> = [
    {
        id: '1',
        name: 'sample4.csv',
        status: 'done',
        size: 7.7 * 1024,
        extension: 'csv',
        percent: 100,
        type: 'text/csv',
        url: 'https://filesamples.com/samples/document/csv/sample4.csv',
    },
    {
        id: '2',
        name: 'sample2.doc',
        status: 'done',
        size: 32 * 1024,
        extension: 'doc',
        percent: 100,
        type: 'application/msword',
        url: 'https://filesamples.com/samples/document/doc/sample2.doc',
    },
]

export function UploaderVariantTriggerType() {
    return (
        <Uploader
            action={new CustomUploaderAction()}
            maxFileSize={100 * 1024 * 1024}
            defaultFileList={exampleFileList}
            triggerType="button"
        />
    )
}

Validation

Choose file or drag & drop to upload

Supported file types: pdf, docx, png, csv

Max file size: 100 MB

'use client'

import { Uploader } from '@workspace/ui/components/Uploader'
import { CustomUploaderAction } from './UploaderDemo.utils'

export function UploaderValidation() {
    return (
        <Uploader
            action={new CustomUploaderAction()}
            maxFileSize={100 * 1024 * 1024}
            acceptedFileExtensions={['pdf', 'docx', 'png', 'csv']}
            maxFiles={3}
        />
    )
}

Form

Uploader

Choose file or drag & drop to upload

Supported file types: pdf, docx, png, csv

Max file size: 100 MB

'use client'

import { z } from '@workspace/lib/validation'
import { toast } from '@workspace/ui/components/Sonner'
import { useForm } from 'react-hook-form'

import { Button } from '@workspace/ui/components/Button'
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@workspace/ui/components/Form'
import { Input } from '@workspace/ui/components/Textfield'
import { Uploader } from '@workspace/ui/components/Uploader'
import { UploaderFile } from '@workspace/ui/components/UploaderItem'
import { CustomUploaderAction } from './UploaderDemo.utils'

interface FormValues {
    name: string
    attachments: Array<UploaderFile>
}

export function UploaderForm() {
    const form = useForm<FormValues>({
        defaultValues: {
            name: '',
            attachments: [],
        },
    })

    function onSubmit(data: FormValues) {
        toast.neutral({
            title: 'You submitted the following values',
            description: <code>{JSON.stringify(data)}</code>,
        })
    }

    return (
        <Form {...form}>
            <form onSubmit={form.handleSubmit(onSubmit)} className="w-full space-y-4">
                <h2 className="text-xl font-semibold">Uploader</h2>
                <FormField
                    control={form.control}
                    name="name"
                    rules={{ validate: z.string().min(1, 'Please enter your name').validateFn() }}
                    render={({ field }) => (
                        <FormItem>
                            <FormLabel>Name</FormLabel>
                            <FormControl>
                                <Input placeholder="John Doe" {...field} />
                            </FormControl>
                            <FormMessage />
                        </FormItem>
                    )}
                />
                <FormField
                    control={form.control}
                    name="attachments"
                    rules={{ validate: z.array(z.any()).min(1, 'Please upload at least one file').validateFn() }}
                    render={({ field }) => (
                        <FormItem>
                            <FormLabel>Attachments</FormLabel>
                            <FormControl>
                                <Uploader
                                    defaultFileList={field.value}
                                    onFileListChange={field.onChange}
                                    action={new CustomUploaderAction()}
                                    maxFileSize={100 * 1024 * 1024}
                                    acceptedFileExtensions={['pdf', 'docx', 'png', 'csv']}
                                />
                            </FormControl>
                            <FormMessage />
                        </FormItem>
                    )}
                />
                <Button type="submit">Submit</Button>
            </form>
        </Form>
    )
}