Command Palette

Search for a command to run...

File Trigger

File Trigger is a component that allows users to trigger a file upload.

No image uploaded

Installation

Follow these simple steps to add the File Trigger component to your project:

Install Dependencies

pnpm add react-dropzone 

Create a new file components/ui/file-trigger.tsx and copy the code below:

"use client";
 
import { useImageUpload } from "@/hooks/use-image-upload";
import { Button } from "@/components/ui/button";
import { CircleUserRoundIcon } from "lucide-react";
import Image from "next/image";
 
export default function Component() {
    const {
        previewUrl,
        fileInputRef,
        handleThumbnailClick: handleButtonClick,
        handleFileChange,
        handleRemove,
        fileName,
    } = useImageUpload();
 
    return (
        <div>
            <div className="inline-flex items-center gap-2 align-top">
                <div
                    className="relative flex size-9 shrink-0 items-center justify-center overflow-hidden rounded-md border border-input"
                    aria-label={
                        previewUrl
                            ? "Preview of uploaded image"
                            : "Default user avatar"
                    }
                >
                    {previewUrl ? (
                        <Image
                            className="h-full w-full object-cover"
                            src={previewUrl}
                            alt="Preview of uploaded image"
                            width={32}
                            height={32}
                        />
                    ) : (
                        <div aria-hidden="true">
                            <CircleUserRoundIcon
                                className="opacity-60"
                                size={16}
                            />
                        </div>
                    )}
                </div>
                <div className="relative inline-block">
                    <Button onClick={handleButtonClick} aria-haspopup="dialog">
                        {fileName ? "Change image" : "Upload image"}
                    </Button>
                    <input
                        type="file"
                        ref={fileInputRef}
                        onChange={handleFileChange}
                        className="hidden"
                        accept="image/*"
                        aria-label="Upload image file"
                    />
                </div>
            </div>
            {fileName && (
                <div className="mt-2">
                    <div className="inline-flex gap-2 text-xs">
                        <p
                            className="truncate text-muted-foreground"
                            aria-live="polite"
                        >
                            {fileName}
                        </p>{" "}
                        <button
                            onClick={handleRemove}
                            className="font-medium text-red-500 hover:underline"
                            aria-label={`Remove ${fileName}`}
                        >
                            Remove
                        </button>
                    </div>
                </div>
            )}
            <div className="sr-only" aria-live="polite" role="status">
                {previewUrl
                    ? "Image uploaded and preview available"
                    : "No image uploaded"}
            </div>
        </div>
    );
}

Create a new file hooks/use-file-trigger.ts and copy the code below:

"use client";
 
import { useCallback, useEffect, useRef, useState } from "react";
 
interface UseImageUploadProps {
    onUpload?: (url: string) => void;
}
 
export function useImageUpload({ onUpload }: UseImageUploadProps = {}) {
    const previewRef = useRef<string | null>(null);
    const fileInputRef = useRef<HTMLInputElement>(null);
    const [previewUrl, setPreviewUrl] = useState<string | null>(null);
    const [fileName, setFileName] = useState<string | null>(null);
 
    const handleThumbnailClick = useCallback(() => {
        fileInputRef.current?.click();
    }, []);
 
    const handleFileChange = useCallback(
        (event: React.ChangeEvent<HTMLInputElement>) => {
            const file = event.target.files?.[0];
            if (file) {
                setFileName(file.name);
                const url = URL.createObjectURL(file);
                setPreviewUrl(url);
                previewRef.current = url;
                onUpload?.(url);
            }
        },
        [onUpload],
    );
 
    const handleRemove = useCallback(() => {
        if (previewUrl) {
            URL.revokeObjectURL(previewUrl);
        }
        setPreviewUrl(null);
        setFileName(null);
        previewRef.current = null;
        if (fileInputRef.current) {
            fileInputRef.current.value = "";
        }
    }, [previewUrl]);
 
    useEffect(() => {
        return () => {
            if (previewRef.current) {
                URL.revokeObjectURL(previewRef.current);
            }
        };
    }, []);
 
    return {
        previewUrl,
        fileName,
        fileInputRef,
        handleThumbnailClick,
        handleFileChange,
        handleRemove,
    };
}

Adjust the import paths in both files according to your project's structure.