Skip to content

Latest commit

Β 

History

History
335 lines (273 loc) Β· 12.3 KB

README.md

File metadata and controls

335 lines (273 loc) Β· 12.3 KB

Next.js Multi-Image Upload

A reusable, responsive multi-image upload component built with Next.js, TypeScript, Tailwind CSS, shadcn/ui, and react-hook-form. This project provides a simple, type-safe solution for uploading multiple images with progress tracking, deletion feedback, and form validation integration.

Demo - Next.js Multi-Image Upload

Features

  • Multi-Image Upload: Upload multiple files with a configurable maximum limit (default: 5).
  • Responsive Design: Flex-based layout that adapts to different screen sizes using Tailwind CSS.
  • Progress Tracking: Displays upload progress as a percentage overlay on each image.
  • Deletion Feedback: Glow-and-dim animation during image deletion to indicate the action is in progress.
  • Form Integration: Fully compatible with react-hook-form for controlled form validation and error handling.
  • Type-Safe: Written in TypeScript with proper type definitions for props and internal state.
  • Customizable: Easily styled via Tailwind CSS classes and extensible through props.
  • API Support: Integrates with signed URL generation and file upload/deletion APIs (server-side logic included).

Demo

Prerequisites

  • Node.js (v18+ recommended)
  • Bun (or npm/yarn/pnpm) as the package manager
  • Next.js project with TypeScript configured
  • Tailwind CSS installed
  • shadcn/ui components (button, form, label) installed
  • react-hook-form and @hookform/resolvers/zod for form validation
  • zod for schema validation
  • lucide-react for icons

Installation

  1. Clone the Repository (optional, if not copying files directly):

    git clone https://github.com/jacksonkasi0/nextjs-multi-image-upload.git
    cd nextjs-multi-image-upload
  2. Install Dependencies:

    bun install
  3. Set Up Environment Variables:

    • Copy example.env to .env and configure your API keys or endpoints (e.g., for signed URL generation).

    • Example .env:

      NODE_ENV="production"
      
      # S3
      AWS_ACCESS_KEY_ID="xxxxx" # your access key
      AWS_SECRET_ACCESS_KEY="xxxxxxx" # your secret key
      AWS_REGION="xxxxxx" # example: ap-south-1, us-east-1
      AWS_BUCKET_NAME="xxxxx" # your bucket name

    πŸ‘€ Check the Secure_S3_Bucket_Setup.md guide for setting up a secure S3 bucket. πŸ›‘οΈ

  4. Run the Development Server:

    bun dev

    Open http://localhost:3000 to see the app.

Usage

Integrating into Your Project

To use the MultiImageUpload component in your own Next.js project, follow these steps:

1. Copy Required Files

Copy the following files from this repository into your project:

  • src/components/multi-image-upload.tsx: The core component with upload and deletion logic.
  • src/api/upload-api.ts: Client-side API functions for signed URL generation, uploading, and deletion.
  • src/app/api/upload/signed-url/route.ts: Server-side route to generate signed URLs.
  • src/app/api/upload/delete/route.ts: Server-side route to delete files.
  • src/components/background.tsx: Optional background component for the radial gradient effect.

2. Install Dependencies

Ensure your project has the required dependencies. Add them to your package.json:

bun add react-hook-form @hookform/resolvers/zod zod lucide-react
bun add -D tailwindcss postcss autoprefixer @types/react @types/node typescript

If you’re using shadcn/ui, initialize it and add the necessary components:

npx shadcn-ui@latest init
npx shadcn-ui@latest add button form label

3. Configure Tailwind CSS

To ensure the MultiImageUpload component works correctly with its glow-and-dim animation during deletion, configure your tailwind.config.ts with the shadcn/ui color variables (e.g., --muted, --primary) and the custom glow-effect animation. Below is an example configuration:

/** @type {import('tailwindcss').Config} */
module.exports = {
  darkMode: ["class"],
  content: [
    "./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
    "./src/components/**/*.{js,ts,jsx,tsx,mdx}",
    "./src/app/**/*.{js,ts,jsx,tsx,mdx}",
  ],
  theme: {
    extend: {
      colors: {
        // shadcn/ui color definitions...
      },
      borderRadius: {
        // shadcn/ui radius definitions...
      },
      animation: {
        "glow-effect": "glow-effect 1.5s infinite ease-in-out",
      },
      keyframes: {
        "glow-effect": {
          "0%, 100%": {
            boxShadow:
              "0 0 10px var(--muted-foreground), 0 0 20px var(--muted)",
            opacity: "1",
          },
          "50%": {
            boxShadow:
              "0 0 20px var(--primary), 0 0 40px var(--primary-foreground)",
            opacity: "0.5",
          },
        },
      },
    },
  },
  plugins: [require("tailwindcss-animate")],
};

Important Notes:

  • Animation: The glow-effect animation is critical for deletion feedback in MultiImageUpload. You must include the animation and keyframes definitions as shown above. It uses box-shadow and opacity, relying on shadcn/ui color variables (--muted, --muted-foreground, --primary, --primary-foreground).

  • Plugin: Add the tailwindcss-animate plugin to enable custom animations. Install it with:

    bun add -D tailwindcss-animate
  • Colors: Ensure your shadcn/ui setup defines the required CSS variables in globals.css (e.g., :root { --muted: 210 40% 96.1%; }). If not, replace the variables with specific HSL or hex values.

4. Example Usage

Here’s how to use MultiImageUpload with react-hook-form in a page (e.g., src/app/page.tsx):

"use client";

import React from "react";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import * as z from "zod";
import { Button } from "@/components/ui/button";
import {
  Form,
  FormControl,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
} from "@/components/ui/form";
import { MultiImageUpload } from "@/components/multi-image-upload";
import Background from "@/components/background";

const schema = z.object({
  images: z
    .array(z.string().url())
    .min(1, "At least 1 image is required")
    .max(5, "Maximum 5 images allowed"),
});

type FormData = z.infer<typeof schema>;

export default function Page() {
  const form = useForm<FormData>({
    resolver: zodResolver(schema),
    defaultValues: { images: [] },
  });

  const onSubmit = (data: FormData) => {
    console.log("Submitted:", data.images);
  };

  return (
    <Background>
      <div className="min-h-screen flex items-center justify-center p-4">
        <Form {...form}>
          <form
            onSubmit={form.handleSubmit(onSubmit)}
            className="space-y-6 w-full max-w-lg"
          >
            <FormField
              control={form.control}
              name="images"
              render={({ field }) => (
                <FormItem>
                  <FormLabel>Images</FormLabel>
                  <FormControl>
                    <MultiImageUpload
                      value={field.value}
                      onChange={field.onChange}
                      maxImages={5}
                      className="my-4"
                      imageRegex={/\.(jpeg|jpg|png|gif|webp|avif)$/i}
                      accept="image/*"
                    />
                  </FormControl>
                  <FormMessage />
                </FormItem>
              )}
            />
            <Button type="submit" className="w-full">
              Submit
            </Button>
          </form>
        </Form>
      </div>
    </Background>
  );
}

5. Set Up API Routes

  • Place signed-url/route.ts and delete/route.ts in your src/app/api/upload/ directory.
  • Update the API logic in upload-api.ts to point to your backend (e.g., AWS S3, Cloudinary) if different from the example.

Props for MultiImageUpload

Prop Type Default Description
value string[] [] Controlled array of file URLs
onChange (images: string[]) => void - Callback to update the value
maxImages number - Optional maximum number of files allowed
className string - Additional Tailwind CSS classes for styling
name string - Optional field name for React Hook Form integration
imageRegex RegExp /\.(jpeg|jpg|png|gif|webp|avif)$/i Regex to detect image formats for initial value previews
accept string "image/*" MIME types or file extensions accepted by the file input (e.g., "image/png, image/jpeg")

Notes:

  • imageRegex determines which initial URLs are treated as images for preview rendering. Customize it to support additional formats (e.g., .svg).
  • accept restricts the file picker to specific types during uploads. Use MIME types or extensions (e.g., "image/*,application/pdf").

File Structure

nextjs-multi-image-upload/
β”œβ”€β”€ public/                     # Static assets
β”‚   β”œβ”€β”€ file.svg
β”‚   β”œβ”€β”€ globe.svg
β”‚   β”œβ”€β”€ next.svg
β”‚   β”œβ”€β”€ vercel.svg
β”‚   └── window.svg
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ api/
β”‚   β”‚   └── upload-api.ts       # Client-side API functions
β”‚   β”œβ”€β”€ app/
β”‚   β”‚   β”œβ”€β”€ api/upload/
β”‚   β”‚   β”‚   β”œβ”€β”€ delete/route.ts # DELETE route for file removal
β”‚   β”‚   β”‚   └── signed-url/route.ts # GET/POST route for signed URLs
β”‚   β”‚   β”œβ”€β”€ favicon.ico
β”‚   β”‚   β”œβ”€β”€ layout.tsx
β”‚   β”‚   └── page.tsx           # Example page using MultiImageUpload
β”‚   β”œβ”€β”€ components/
β”‚   β”‚   β”œβ”€β”€ ui/                # shadcn/ui components
β”‚   β”‚   β”‚   β”œβ”€β”€ button.tsx
β”‚   β”‚   β”‚   β”œβ”€β”€ form.tsx
β”‚   β”‚   β”‚   └── label.tsx
β”‚   β”‚   β”œβ”€β”€ background.tsx     # Radial gradient background
β”‚   β”‚   └── multi-image-upload.tsx # Main component
β”‚   β”œβ”€β”€ config/
β”‚   β”‚   └── env.ts             # Environment variable config
β”‚   β”œβ”€β”€ helpers/
β”‚   β”‚   └── upload.ts          # Upload-related utilities
β”‚   β”œβ”€β”€ lib/
β”‚   β”‚   └── utils.ts           # Utility functions (e.g., cn)
β”‚   └── styles/
β”‚       └── globals.css        # Global styles
β”œβ”€β”€ .env                       # Environment variables
β”œβ”€β”€ .gitignore
β”œβ”€β”€ README.md
β”œβ”€β”€ bun.lock                   # Bun lockfile
β”œβ”€β”€ components.json            # shadcn/ui config
β”œβ”€β”€ eslint.config.mjs
β”œβ”€β”€ example.env
β”œβ”€β”€ next-env.d.ts
β”œβ”€β”€ next.config.ts
β”œβ”€β”€ package.json
β”œβ”€β”€ postcss.config.mjs
β”œβ”€β”€ tailwind.config.ts
└── tsconfig.json

Contributing

  1. Fork the repository.
  2. Create a feature branch (git checkout -b feature/new-feature).
  3. Commit changes (git commit -m "Add new feature").
  4. Push to the branch (git push origin feature/new-feature).
  5. Open a pull request.

License

MIT License. See LICENSE for details.

Credits

Created by jacksonkasi0 & jacksonkasi1.