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

@plugable-io/react

v0.0.13

Published

React components and hooks for Plugable File Management API

Readme

@plugable-io/react

React components and hooks for the Plugable File Management API.

Installation

npm install @plugable-io/react

Components & Hooks

| Export | Type | Description | |--------|------|-------------| | PlugableProvider | Component | Context provider for configuration | | Dropzone | Component | Drag & drop file uploader | | FilePreview | Component | Universal file preview (images + file type icons) | | FileImage | Component | Image display with caching | | useFiles | Hook | File fetching with pagination | | usePlugable | Hook | Access client and context |

Quick Start

Provider Setup

Wrap your app with PlugableProvider. You can provide a getToken function or use a supported auth provider.

With Clerk

import { PlugableProvider } from '@plugable-io/react';

function App() {
  return (
    <PlugableProvider
      bucketId="your-bucket-id"
      authProvider="clerk"
      clerkJWTTemplate="plugable" // optional
    >
      <YourApp />
    </PlugableProvider>
  );
}

With Supabase

<PlugableProvider
  bucketId="your-bucket-id"
  authProvider="supabase"
>
  <YourApp />
</PlugableProvider>

With Custom getToken

<PlugableProvider
  bucketId="your-bucket-id"
  getToken={async () => {
    return await getAuthToken();
  }}
>
  <YourApp />
</PlugableProvider>

Theme Customization

The library comes with gorgeous default styling inspired by modern design principles. You can easily customize the appearance using theme props:

Dark Mode (Default)

<PlugableProvider
  bucketId="your-bucket-id"
  authProvider="clerk"
  theme="dark" // or omit - dark is default
>
  <YourApp />
</PlugableProvider>

Light Mode

<PlugableProvider
  bucketId="your-bucket-id"
  authProvider="clerk"
  theme="light"
>
  <YourApp />
</PlugableProvider>

Auto (System Preference)

<PlugableProvider
  bucketId="your-bucket-id"
  authProvider="clerk"
  theme="auto" // Follows user's system preference
>
  <YourApp />
</PlugableProvider>

Custom Accent Color

<PlugableProvider
  bucketId="your-bucket-id"
  authProvider="clerk"
  accentColor="#10b981" // Green accent
>
  <YourApp />
</PlugableProvider>

Custom Base Color

<PlugableProvider
  bucketId="your-bucket-id"
  authProvider="clerk"
  baseColor="#1e293b" // Custom background
  theme="dark"
>
  <YourApp />
</PlugableProvider>

Complete Custom Theme

<PlugableProvider
  bucketId="your-bucket-id"
  authProvider="clerk"
  theme="light"
  accentColor="#0ea5e9" // Cyan accent
  baseColor="#f8fafc" // Light gray background
>
  <YourApp />
</PlugableProvider>

CSS Variables

The library uses CSS variables for theming, which are scoped to prevent conflicts with your app's styles. You can override any variable:

.plugable-root {
  /* Accent colors */
  --plugable-accent-primary: #9333ea;
  --plugable-accent-secondary: #2563eb;
  --plugable-accent-hover: #7c3aed;
  
  /* Base colors */
  --plugable-base-bg: #0f172a;
  --plugable-base-surface: rgba(30, 41, 59, 0.5);
  --plugable-base-border: rgba(255, 255, 255, 0.1);
  
  /* Text colors */
  --plugable-text-primary: #f1f5f9;
  --plugable-text-secondary: #cbd5e1;
  --plugable-text-muted: #64748b;
  
  /* State colors */
  --plugable-success: #34d399;
  --plugable-error: #f87171;
  --plugable-warning: #fbbf24;
  
  /* Effects */
  --plugable-overlay: rgba(0, 0, 0, 0.3);
  --plugable-backdrop-blur: blur(24px);
}

Dropzone

File uploader with drag-and-drop support. Works with default UI or custom render function.

Default UI

import { Dropzone } from '@plugable-io/react';

function ProfileUploader() {
  return (
    <Dropzone
      metadata={{ category: 'avatar', userId: '123' }}
      accept="image/*"
      maxFiles={1}
      onUploadComplete={(files) => {
        console.log('Avatar uploaded:', files[0]);
      }}
      onUploadError={(error) => {
        console.error('Upload failed:', error);
      }}
    />
  );
}

Custom UI with Render Props

import { Dropzone } from '@plugable-io/react';

function DocumentUploader() {
  return (
    <Dropzone
      metadata={{ type: 'document' }}
      accept=".pdf,.doc,.docx"
      maxFiles={10}
      onUploadComplete={(files) => console.log('Uploaded:', files)}
    >
      {({ isDragActive, isUploading, uploadProgress, openFileDialog, uploadedFiles }) => (
        <div
          onClick={openFileDialog}
          className={`border-2 border-dashed rounded-lg p-8 text-center cursor-pointer
            ${isDragActive ? 'border-blue-500 bg-blue-50' : 'border-gray-300'}`}
        >
          {isUploading ? (
            <div className="space-y-2">
              <p className="font-medium">Uploading...</p>
              {Object.entries(uploadProgress).map(([name, progress]) => (
                <div key={name} className="text-sm">
                  <span>{name}</span>
                  <div className="w-full bg-gray-200 rounded h-2 mt-1">
                    <div
                      className="bg-blue-500 h-2 rounded transition-all"
                      style={{ width: `${progress}%` }}
                    />
                  </div>
                </div>
              ))}
            </div>
          ) : (
            <>
              <p className="font-medium">
                {isDragActive ? 'Drop files here' : 'Drag & drop documents'}
              </p>
              <p className="text-sm text-gray-500 mt-1">or click to browse</p>
            </>
          )}

          {uploadedFiles.length > 0 && (
            <div className="mt-4 text-sm text-green-600">
              ✓ {uploadedFiles.length} file(s) uploaded
            </div>
          )}
        </div>
      )}
    </Dropzone>
  );
}

Props

| Prop | Type | Description | |------|------|-------------| | bucketId | string | Override default bucket ID | | metadata | Record<string, any> | Metadata to attach to uploaded files | | accept | string | File types to accept (e.g., "image/*", ".pdf,.doc") | | maxFiles | number | Maximum number of files | | onUploadComplete | (files: FileObject[]) => void | Called when uploads complete | | onUploadError | (error: Error) => void | Called on upload error | | onProgressUpdate | (fileName: string, progress: number) => void | Progress callback | | children | (props: DropzoneRenderProps) => ReactNode | Custom render function | | className | string | CSS class name | | style | CSSProperties | Inline styles |


useFiles Hook

Fetch and paginate files with automatic refresh on uploads.

Basic Usage

import { useFiles, FilePreview } from '@plugable-io/react';

function InvoiceList() {
  const { files, isLoading, pagination, refresh } = useFiles({
    metadata: { type: 'invoice' },
    perPage: 10,
  });

  if (isLoading) return <div>Loading...</div>;

  return (
    <div>
      <div className="grid grid-cols-4 gap-4">
        {files.map((file) => (
          <div key={file.id} className="border rounded p-3">
            <FilePreview file={file} width={60} height={60} />
            <p className="text-sm mt-2 truncate">{file.name}</p>
            <div className="flex gap-2 mt-2">
              <button onClick={() => file.delete()}>Delete</button>
              <a href={file.download_url} download>Download</a>
            </div>
          </div>
        ))}
      </div>

      <div className="flex gap-2 mt-4">
        <button
          onClick={pagination.loadPreviousPage}
          disabled={!pagination.hasPrevious}
        >
          Previous
        </button>
        <span>Page {pagination.current}</span>
        <button
          onClick={pagination.loadNextPage}
          disabled={!pagination.hasNext}
        >
          Next
        </button>
      </div>
    </div>
  );
}

Filter by Media Type

function ImageGallery() {
  const { files, isLoading } = useFiles({
    mediaType: 'image',
    perPage: 20,
  });

  return (
    <div className="grid grid-cols-3 gap-4">
      {files.map((file) => (
        <FilePreview key={file.id} file={file} width="100%" height={200} objectFit="cover" />
      ))}
    </div>
  );
}

Ordering Files

function SortedFileList() {
  const [orderBy, setOrderBy] = useState<'created_at' | 'name' | 'byte_size'>('created_at');
  const [orderDirection, setOrderDirection] = useState<'asc' | 'desc'>('desc');

  const { files, isLoading } = useFiles({
    orderBy,
    orderDirection,
    perPage: 20,
  });

  return (
    <div>
      <select value={orderBy} onChange={(e) => setOrderBy(e.target.value as any)}>
        <option value="created_at">Date</option>
        <option value="name">Name</option>
        <option value="byte_size">Size</option>
      </select>
      <button onClick={() => setOrderDirection(orderDirection === 'asc' ? 'desc' : 'asc')}>
        {orderDirection === 'asc' ? '↑' : '↓'}
      </button>
      {files.map((file) => (
        <div key={file.id}>{file.name}</div>
      ))}
    </div>
  );
}

Manual Loading

function LazyFileList() {
  const { files, isLoading, refresh } = useFiles({
    metadata: { folder: 'reports' },
    autoLoad: false, // Don't load on mount
  });

  return (
    <div>
      <button onClick={refresh}>Load Files</button>
      {isLoading && <span>Loading...</span>}
      {files.map((file) => (
        <div key={file.id}>{file.name}</div>
      ))}
    </div>
  );
}

Options

| Option | Type | Default | Description | |--------|------|---------|-------------| | metadata | Record<string, any> | - | Filter files by metadata | | mediaType | string | - | Filter by media type (image, video, etc.) | | perPage | number | 20 | Files per page | | startPage | number | 1 | Initial page number | | autoLoad | boolean | true | Fetch on mount | | orderBy | 'created_at' \| 'name' \| 'byte_size' | 'created_at' | Field to order by | | orderDirection | 'asc' \| 'desc' | 'desc' | Sort direction |

Return Value

| Property | Type | Description | |----------|------|-------------| | files | FileObject[] | Array of files | | isLoading | boolean | Loading state | | pagination.current | number | Current page | | pagination.hasNext | boolean | Has next page | | pagination.hasPrevious | boolean | Has previous page | | pagination.loadNextPage | () => void | Go to next page | | pagination.loadPreviousPage | () => void | Go to previous page | | setPage | (page: number) => void | Jump to specific page | | refresh | () => Promise<void> | Refresh current page |


FilePreview

Universal file preview component. Displays images inline and shows file type icons for other files.

Basic Usage

import { FilePreview } from '@plugable-io/react';

function FileCard({ file }) {
  return (
    <div className="flex items-center gap-3 p-3 border rounded">
      <FilePreview file={file} width={48} height={48} />
      <div>
        <p className="font-medium">{file.name}</p>
        <p className="text-sm text-gray-500">{file.content_type}</p>
      </div>
    </div>
  );
}

Custom Non-Image Rendering

import { FilePreview } from '@plugable-io/react';

function CustomFilePreview({ file }) {
  return (
    <FilePreview
      file={file}
      width={80}
      height={80}
      objectFit="cover"
      renderNonImage={(file) => (
        <div className="flex flex-col items-center justify-center text-gray-500">
          <span className="text-2xl">📄</span>
          <span className="text-xs mt-1">{file.name.split('.').pop()}</span>
        </div>
      )}
    />
  );
}

Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | file | FileObject | required | File to preview | | width | number \| string | 80 | Preview width | | height | number \| string | 80 | Preview height | | objectFit | 'contain' \| 'cover' \| 'fill' \| 'none' \| 'scale-down' | 'cover' | Image fit mode | | className | string | - | CSS class name | | style | CSSProperties | - | Inline styles | | showExtension | boolean | true | Show file extension for non-images | | renderNonImage | (file: FileObject) => ReactNode | - | Custom renderer for non-image files |


FileImage

Display images with automatic URL fetching and caching.

import { FileImage } from '@plugable-io/react';

function Avatar({ file }) {
  return (
    <FileImage
      file={file}
      width={100}
      height={100}
      objectFit="cover"
      borderRadius={50}
      alt="User avatar"
      onLoad={() => console.log('Loaded')}
    />
  );
}

File Object Methods

Each FileObject provides built-in methods for updating, downloading, and deleting files:

// Update file metadata or name
await file.update(
  { tags: ['important', 'reviewed'] }, // metadata (optional)
  'new-filename.txt'                   // name (optional)
);

// Download the file (initiates browser download)
await file.download();

// Delete the file
await file.delete();

// Access properties
console.log(file.id);           // Unique ID
console.log(file.name);         // Filename
console.log(file.content_type); // MIME type
console.log(file.download_url); // Signed download URL
console.log(file.metadata);     // Custom metadata

Full Example

Complete file manager with upload, list, and preview:

import { 
  PlugableProvider, 
  Dropzone, 
  useFiles, 
  FilePreview 
} from '@plugable-io/react';

function FileManager() {
  const { files, isLoading, pagination, refresh } = useFiles({
    perPage: 12,
  });

  return (
    <div className="max-w-4xl mx-auto p-6">
      <h1 className="text-2xl font-bold mb-6">File Manager</h1>

      {/* Upload Section */}
      <Dropzone
        maxFiles={5}
        onUploadComplete={(uploaded) => {
          console.log('Uploaded:', uploaded);
          // Files list auto-refreshes via event system
        }}
        className="mb-8"
      />

      {/* Files Grid */}
      {isLoading ? (
        <div className="text-center py-8">Loading...</div>
      ) : (
        <div className="grid grid-cols-4 gap-4">
          {files.map((file) => (
            <div key={file.id} className="border rounded-lg p-3">
              <FilePreview 
                file={file} 
                width="100%" 
                height={120} 
                objectFit="cover" 
              />
              <p className="text-sm mt-2 truncate" title={file.name}>
                {file.name}
              </p>
              <div className="flex gap-2 mt-2">
                <a 
                  href={file.download_url} 
                  download
                  className="text-blue-500 text-sm"
                >
                  Download
                </a>
                <button 
                  onClick={() => file.delete()}
                  className="text-red-500 text-sm"
                >
                  Delete
                </button>
              </div>
            </div>
          ))}
        </div>
      )}

      {/* Pagination */}
      <div className="flex justify-center gap-4 mt-6">
        <button
          onClick={pagination.loadPreviousPage}
          disabled={!pagination.hasPrevious}
          className="px-4 py-2 border rounded disabled:opacity-50"
        >
          Previous
        </button>
        <span className="py-2">Page {pagination.current}</span>
        <button
          onClick={pagination.loadNextPage}
          disabled={!pagination.hasNext}
          className="px-4 py-2 border rounded disabled:opacity-50"
        >
          Next
        </button>
      </div>
    </div>
  );
}

// App wrapper
function App() {
  return (
    <PlugableProvider
      bucketId="your-bucket-id"
      authProvider="clerk"
    >
      <FileManager />
    </PlugableProvider>
  );
}

TypeScript

All types are exported:

import type {
  PlugableProviderProps,
  AuthProvider,
  DropzoneProps,
  DropzoneRenderProps,
  FilePreviewProps,
  FileImageProps,
  FileListProps,
  FileListRenderProps,
  UseFilesOptions,
  UseFilesResult,
  FileObject,
  SearchOptions,
  UpdateOptions,
} from '@plugable-io/react';

License

MIT