npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

dudu-file-upload-components

v0.1.31

Published

A highly customizable, config-driven file upload component library built with React, TailwindCSS, and class-variance-authority

Readme

Dudu File Upload Components

A highly customizable, config-driven file upload component library built with React, TailwindCSS, and class-variance-authority. Inspired by shadcn/ui's API patterns with support for multiple variants, themes, and JSON-based configuration.

✨ Features

  • 🎨 Multiple Variants: Button, dropzone, preview, and compact layouts
  • 🎯 Config-Driven: JSON-based configuration for easy customization
  • 🎭 Theme Support: Built-in themes with TailwindCSS integration
  • Accessible: Full keyboard navigation and screen reader support
  • 📱 Responsive: Works seamlessly across all device sizes
  • 🔧 TypeScript: Full type safety and IntelliSense support
  • 🎪 Drag & Drop: Native drag and drop with keyboard fallback
  • 🖼️ Preview Support: Image previews and file type icons
  • 📏 Validation: File size, type, and count validation
  • 🎛️ Flexible API: Use as components or config-driven renderers

📦 Installation

1. Install the Package

npm install dudu-file-upload-components
# or
yarn add dudu-file-upload-components
# or
pnpm add dudu-file-upload-components

2. Install Required Peer Dependencies

Important: You must install the required peer dependencies manually to avoid version conflicts and reduce bundle size.

npm install react react-dom lucide-react class-variance-authority clsx @radix-ui/react-slot

3. Import Styles

Important: Import the CSS file in your main application file (e.g., main.tsx, App.tsx, or _app.tsx):

import "dudu-file-upload-components/dist/style.css";

This CSS file contains all the necessary styles including:

  • CSS variables for theming
  • All required TailwindCSS utility classes
  • Component-specific styles
  • Works standalone - No TailwindCSS required in your project!

4. Configure TailwindCSS (Optional)

If you're using TailwindCSS in your project, you can optionally add the component paths to your tailwind.config.js for better integration:

module.exports = {
  content: [
    "./src/**/*.{js,ts,jsx,tsx}",
    "./node_modules/dudu-file-upload-components/**/*.{js,ts,jsx,tsx}",
  ],
  // ... rest of your config
};

Note: This step is optional. The components work perfectly without TailwindCSS in your project.

Why Install Peer Dependencies Manually?

  1. Version Flexibility: Use your preferred versions of React and other dependencies
  2. Bundle Size: Prevents duplicate dependencies in your project
  3. Compatibility: Ensures the component works with your existing React setup
  4. Control: You maintain control over which versions are used in your project

🚀 Quick Start

Basic Usage

import { FileUpload } from "dudu-file-upload-components";
import "dudu-file-upload-components/dist/style.css";

function App() {
  const handleFilesChange = (files) => {
    console.log("Files:", files);
  };

  return <FileUpload variant="dropzone" onFilesChange={handleFilesChange} />;
}

Using Presets

import { FileUploadPresetRenderer } from "dudu-file-upload-components";

function ImageUpload() {
  return (
    <FileUploadPresetRenderer
      preset="image-upload"
      onFilesChange={(files) => console.log(files)}
    />
  );
}

Config-Driven Approach

import { FileUploadRenderer } from "dudu-file-upload-components";

const config = {
  variant: "dropzone",
  multiple: true,
  maxFiles: 5,
  accept: "image/*",
  labels: {
    dropzone: "Drop your images here",
  },
};

function ConfigDrivenUpload() {
  return (
    <FileUploadRenderer
      config={config}
      onFilesChange={(files) => console.log(files)}
    />
  );
}

Example Usage Code

Complete Component Example

import React, { useState } from "react";
import { FileUpload, type FileWithPreview } from "dudu-file-upload-components";

function MyFileUploader() {
  const [files, setFiles] = useState<FileWithPreview[]>([]);
  const [errors, setErrors] = useState<string[]>([]);

  const handleFilesChange = (newFiles: FileWithPreview[]) => {
    setFiles(newFiles);
    console.log("Files updated:", newFiles);
  };

  const handleError = (errorMessages: string[]) => {
    setErrors(errorMessages);
    console.error("Upload errors:", errorMessages);
  };

  return (
    <div className="max-w-md mx-auto p-6">
      <h2 className="text-xl font-semibold mb-4">Upload Your Files</h2>

      <FileUpload
        variant="dropzone"
        size="md"
        multiple={true}
        maxFiles={5}
        maxSize={10 * 1024 * 1024} // 10MB
        accept="image/*,application/pdf"
        onFilesChange={handleFilesChange}
        onError={handleError}
        labels={{
          dropzone: "Drop your files here or click to browse",
          dragActive: "Release to upload files",
        }}
      />

      {errors.length > 0 && (
        <div className="mt-4 p-3 bg-red-50 border border-red-200 rounded">
          <h3 className="text-red-800 font-medium">Upload Errors:</h3>
          <ul className="text-red-700 text-sm mt-1">
            {errors.map((error, index) => (
              <li key={index}>• {error}</li>
            ))}
          </ul>
        </div>
      )}

      {files.length > 0 && (
        <div className="mt-4">
          <h3 className="font-medium mb-2">Uploaded Files:</h3>
          <ul className="space-y-1">
            {files.map((file) => (
              <li key={file.id} className="text-sm text-gray-600">
                {file.file.name} ({Math.round(file.file.size / 1024)}KB)
              </li>
            ))}
          </ul>
        </div>
      )}
    </div>
  );
}

export default MyFileUploader;

Using with Form Libraries

import { useForm, Controller } from "react-hook-form";
import { FileUpload } from "dudu-file-upload-components";

function FormWithFileUpload() {
  const { control, handleSubmit } = useForm();

  const onSubmit = (data) => {
    console.log("Form data:", data);
    // Handle form submission with files
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <Controller
        name="files"
        control={control}
        render={({ field }) => (
          <FileUpload
            variant="dropzone"
            multiple
            onFilesChange={field.onChange}
          />
        )}
      />
      <button type="submit">Submit</button>
    </form>
  );
}

Custom Hook Usage

import { useFileUpload } from "dudu-file-upload-components";

function CustomFileUploader() {
  const [state, actions] = useFileUpload({
    multiple: true,
    maxSize: 5 * 1024 * 1024, // 5MB
    accept: "image/*",
  });

  return (
    <div>
      <input {...actions.getInputProps()} />
      <button onClick={actions.openFileDialog}>
        Choose Files ({state.files.length} selected)
      </button>

      {state.files.map((file) => (
        <div key={file.id}>
          <span>{file.file.name}</span>
          <button onClick={() => actions.removeFile(file.id)}>Remove</button>
        </div>
      ))}

      {state.errors.map((error, index) => (
        <div key={index} className="text-red-500">
          {error}
        </div>
      ))}
    </div>
  );
}

API Reference

FileUpload Component

| Prop | Type | Default | Description | | --------------- | -------------------------------------------------- | ------------ | ----------------------------- | | variant | "button" \| "dropzone" \| "preview" \| "compact" | "dropzone" | Upload component variant | | size | "sm" \| "md" \| "lg" | "md" | Component size | | radius | "none" \| "sm" \| "md" \| "lg" \| "full" | "md" | Border radius | | multiple | boolean | false | Allow multiple file selection | | maxFiles | number | 1 | Maximum number of files | | maxSize | number | 10MB | Maximum file size in bytes | | accept | string | "*" | Accepted file types | | disabled | boolean | false | Disable the component | | showPreview | boolean | true | Show file previews | | allowRemove | boolean | true | Allow file removal | | onFilesChange | (files: FileWithPreview[]) => void | - | Files change callback | | onFilesAdded | (files: FileWithPreview[]) => void | - | Files added callback | | onError | (errors: string[]) => void | - | Error callback |

Available Presets

  • default - Basic dropzone upload
  • image-upload - Optimized for images with preview
  • document-upload - For documents with file list
  • multi-file - Multiple file upload with previews
  • compact - Minimal button-style upload

📋 JSON Configuration Documentation

The component supports JSON-based configuration for dynamic behavior. This makes it easy to configure file upload components through external configuration files, APIs, or content management systems.

Complete Configuration Schema

Here's the complete configuration schema with all available options:

{
  "variant": "dropzone",
  "size": "md",
  "radius": "lg",
  "multiple": true,
  "maxFiles": 10,
  "maxSize": 52428800,
  "accept": "image/*,application/pdf",
  "disabled": false,
  "showPreview": true,
  "showProgress": false,
  "allowRemove": true,
  "labels": {
    "dropzone": "Drop files here or click to browse",
    "button": "Choose Files",
    "dragActive": "Drop files here",
    "maxFiles": "Maximum {count} files allowed",
    "maxSize": "File size must be less than {size}",
    "fileType": "File type not supported",
    "remove": "Remove file",
    "clear": "Clear all files"
  },
  "icons": {
    "upload": "upload",
    "file": "file",
    "image": "image",
    "remove": "x",
    "error": "alert-circle"
  }
}

Configuration Properties

| Property | Type | Default | Description | | -------------- | -------------------------------------------------- | ------------ | ----------------------------------------------- | | variant | "button" \| "dropzone" \| "preview" \| "compact" | "dropzone" | Upload component variant | | size | "sm" \| "md" \| "lg" | "md" | Component size | | radius | "none" \| "sm" \| "md" \| "lg" \| "full" | "md" | Border radius | | multiple | boolean | false | Allow multiple file selection | | maxFiles | number | 1 | Maximum number of files (when multiple is true) | | maxSize | number | 10485760 | Maximum file size in bytes (default: 10MB) | | accept | string | "*" | Accepted file types (MIME types or extensions) | | disabled | boolean | false | Disable the component | | showPreview | boolean | true | Show file previews | | showProgress | boolean | false | Show upload progress bars | | allowRemove | boolean | true | Allow file removal |

Labels Configuration

All labels support placeholder replacement:

| Label | Placeholders | Description | | ------------ | ------------ | -------------------------------------------- | | dropzone | - | Text shown in dropzone area | | button | - | Text shown on upload button | | dragActive | - | Text shown when dragging files over dropzone | | maxFiles | {count} | Error message for too many files | | maxSize | {size} | Error message for file too large | | fileType | - | Error message for invalid file type | | remove | - | Tooltip text for remove button | | clear | - | Text for clear all button |

📝 Example Configurations

Image Gallery Upload

{
  "variant": "dropzone",
  "size": "lg",
  "multiple": true,
  "maxFiles": 20,
  "maxSize": 5242880,
  "accept": "image/*",
  "showPreview": true,
  "allowRemove": true,
  "labels": {
    "dropzone": "Drop your photos here or click to browse",
    "dragActive": "Release to add photos",
    "maxFiles": "You can upload up to {count} photos",
    "maxSize": "Each photo must be smaller than {size}"
  }
}

Document Upload

{
  "variant": "button",
  "size": "md",
  "multiple": true,
  "maxFiles": 10,
  "maxSize": 26214400,
  "accept": ".pdf,.doc,.docx,.txt",
  "showPreview": false,
  "labels": {
    "button": "Select Documents",
    "maxFiles": "Maximum {count} documents allowed",
    "fileType": "Only PDF, Word, and text files are supported"
  }
}

Single Avatar Upload

{
  "variant": "preview",
  "size": "sm",
  "multiple": false,
  "maxFiles": 1,
  "maxSize": 2097152,
  "accept": "image/jpeg,image/png,image/webp",
  "showPreview": true,
  "allowRemove": true,
  "labels": {
    "dropzone": "Upload your avatar",
    "maxSize": "Image must be smaller than {size}",
    "fileType": "Only JPEG, PNG, and WebP images are supported"
  }
}

Compact File Upload

{
  "variant": "compact",
  "size": "sm",
  "multiple": true,
  "maxFiles": 5,
  "maxSize": 10485760,
  "accept": "*",
  "showPreview": false,
  "labels": {
    "button": "Attach Files",
    "maxFiles": "Up to {count} files",
    "maxSize": "Max {size} per file"
  }
}

Using JSON Configuration

import { FileUploadJSONRenderer } from "dudu-file-upload-components";

// Load configuration from API, file, or CMS
const config = await fetch("/api/upload-config").then((res) => res.json());

function DynamicFileUpload() {
  return (
    <FileUploadJSONRenderer
      configJSON={JSON.stringify(config)}
      onFilesChange={(files) => console.log(files)}
      onConfigError={(error) => console.error("Config error:", error)}
      fallbackPreset="default"
    />
  );
}

Advanced Usage

Custom Hook

import { useFileUpload } from "dudu-file-upload-components";

function CustomUpload() {
  const [state, actions] = useFileUpload({
    multiple: true,
    maxSize: 5 * 1024 * 1024, // 5MB
    accept: "image/*",
  });

  return (
    <div>
      <input {...actions.getInputProps()} />
      <button onClick={actions.openFileDialog}>Upload Files</button>
      {state.files.map((file) => (
        <div key={file.id}>
          {file.file.name}
          <button onClick={() => actions.removeFile(file.id)}>Remove</button>
        </div>
      ))}
    </div>
  );
}

Live Configuration

import {
  useFileUploadConfig,
  FileUploadRenderer,
} from "dudu-file-upload-components";

function LiveConfigExample() {
  const { config, updateConfig, exportConfig } = useFileUploadConfig();

  return (
    <div>
      <button onClick={() => updateConfig({ multiple: !config.multiple })}>
        Toggle Multiple
      </button>
      <FileUploadRenderer config={config} />
      <pre>{exportConfig()}</pre>
    </div>
  );
}

Styling

The components use TailwindCSS classes and CSS custom properties. You can customize the appearance by:

  1. Using the built-in variants and sizes
  2. Overriding CSS custom properties
  3. Passing custom className props
  4. Using the configuration system

TypeScript Support

Full TypeScript support with comprehensive type definitions:

import type {
  FileUploadConfig,
  FileWithPreview,
  FileUploadPreset,
} from "dudu-file-upload-components";

Publishing to NPM

Building the Package

Before publishing, build the package for distribution:

# Build the library for distribution
npm run build:lib

# Build the demo site (for development/testing)
npm run build

# Run the development server
npm run dev

The build process:

  1. TypeScript Compilation: Compiles TypeScript to JavaScript with type definitions
  2. Bundling: Uses Rollup to create optimized bundles for both CommonJS and ES modules
  3. CSS Processing: Processes and optimizes CSS/TailwindCSS styles
  4. Tree Shaking: Removes unused code for smaller bundle sizes

Publishing to NPM

  1. Prepare for Publishing

    # Update version in package.json
    npm version patch  # or minor/major
    
    # Build the package
    npm run build:lib
  2. Publish to NPM

    # Login to NPM (first time only)
    npm login
    
    # Publish the package
    npm publish
    
    # For scoped packages (like dudu-file-upload-components)
    npm publish --access public
  3. Verify Publication

    # Check if package is published
    npm view dudu-file-upload-components

Using in Your Project

Once published to NPM, you can use the package in any React project:

1. Install the Package

npm install dudu-file-upload-components

2. Install Required Peer Dependencies

npm install react react-dom lucide-react class-variance-authority clsx @radix-ui/react-slot

3. Configure TailwindCSS

Add the package path to your tailwind.config.js:

module.exports = {
  content: [
    "./src/**/*.{js,ts,jsx,tsx}",
    "./node_modules/dudu-file-upload-components/**/*.{js,ts,jsx,tsx}",
  ],
  // ... rest of your config
};

4. Use in Your Components

import { FileUpload } from "dudu-file-upload-components";

function MyApp() {
  return (
    <FileUpload
      variant="dropzone"
      multiple
      onFilesChange={(files) => console.log(files)}
    />
  );
}

Package Distribution

The package includes:

  • ES Modules: dist/index.esm.js
  • CommonJS: dist/index.js
  • TypeScript Definitions: dist/index.d.ts
  • Styles: dist/styles.css (if needed)

Local Development

For local development and testing:

# Pack the package locally
npm pack

# Install in another project
cd /path/to/your-project
npm install /path/to/dudu-file-upload-components-0.1.0.tgz

Testing

The component library includes comprehensive testing to ensure reliability and functionality.

Running Tests

# Run all tests
npm test

# Run tests in watch mode
npm run test:watch

# Run tests with coverage
npm run test:coverage

Test Structure

The test suite covers:

  • Unit Tests: Individual component functionality
  • Integration Tests: Component interactions and workflows
  • Accessibility Tests: Screen reader and keyboard navigation
  • Visual Regression Tests: UI consistency across changes

Writing Tests

When contributing, please include tests for:

// Example test structure
import { render, screen, fireEvent } from "@testing-library/react";
import { FileUpload } from "dudu-file-upload-components";

describe("FileUpload", () => {
  it("should handle file selection", () => {
    const handleFilesChange = jest.fn();
    render(<FileUpload onFilesChange={handleFilesChange} />);

    const input = screen.getByRole("button");
    // Test file upload functionality
  });
});

Contributing

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

License

MIT © dudu

Demo

Visit the demo at /demo route when running the development server to see all variants and configuration options in action.

npm run dev
# Open http://localhost:3000/demo

🔧 Troubleshooting

Common Issues

Styles Not Applied

  1. Import the CSS file in your main application file (this is required):
import "dudu-file-upload-components/dist/style.css";
  1. If using TailwindCSS, add the package path to your tailwind.config.js:
module.exports = {
  content: [
    "./src/**/*.{js,ts,jsx,tsx}",
    "./node_modules/dudu-file-upload-components/**/*.{js,ts,jsx,tsx}",
  ],
  // ... rest of config
};

Note: The CSS file contains all necessary styles and will work even without TailwindCSS in your project.

TypeScript Errors

Ensure you have the correct type definitions installed:

npm install @types/react @types/react-dom

Peer Dependency Warnings

Install all required peer dependencies:

npm install react react-dom lucide-react class-variance-authority clsx @radix-ui/react-slot

Component Not Rendering

  1. Check that you've imported the CSS file
  2. Verify all peer dependencies are installed
  3. Ensure your React version is 16.8 or higher
  4. Check the browser console for any error messages

Best Practices

  1. File Validation: Always validate files on both client and server side
  2. Error Handling: Implement proper error handling for upload failures
  3. Progress Tracking: Use the built-in progress features for better UX
  4. Accessibility: Test with screen readers and keyboard navigation
  5. Performance: Consider lazy loading for large file previews

Browser Support

  • Modern Browsers: Chrome 90+, Firefox 88+, Safari 14+, Edge 90+
  • File API: Required for drag & drop functionality
  • ES2017: Minimum JavaScript support level

✅ Testing Your Installation

After installing the package, you can test it with this simple component:

import React from "react";
import { FileUpload } from "dudu-file-upload-components";
import "dudu-file-upload-components/dist/style.css";

function TestFileUpload() {
  const handleFilesChange = (files) => {
    console.log("Files selected:", files);
  };

  return (
    <div className="p-8">
      <h2 className="text-xl font-semibold mb-4">Test File Upload</h2>
      <FileUpload
        variant="dropzone"
        multiple={true}
        maxFiles={3}
        onFilesChange={handleFilesChange}
      />
    </div>
  );
}

export default TestFileUpload;

If the component renders correctly and you can select files, your installation is working properly!