Headless React hook for file dropping, processing, ZIP extraction, and validation - purpose-built for Ship SDK
npm install @shipstatic/dropHeadless file processing toolkit for Ship SDK deployments.
A focused React hook for preparing files for deployment with @shipstatic/ship. Handles ZIP extraction, path normalization, folder structure preservation, and validation.
``bash`
npm install @shipstatic/drop @shipstatic/ship
`tsx
import { useDrop } from '@shipstatic/drop';
import Ship from '@shipstatic/ship';
const ship = new Ship({ deployToken: 'token-xxxx' });
function Uploader() {
const drop = useDrop({ ship });
const handleUpload = async () => {
const files = drop.getFilesForUpload();
await ship.deployments.create(files);
};
return (
{drop.status &&
{drop.status.title}: {drop.status.details}
}
Features
- Prop Getters API - Spread props on your elements (like
react-dropzone)
- Built-in Drag & Drop - Folder support with webkitGetAsEntry API
- ZIP Support - Automatic extraction and processing
- Ship SDK Integration - Validation via ship.getConfig()
- Headless - No visual components, full styling control
- TypeScript - Complete type definitionsState Machine
`
idle → dragging → processing → ready/error
`Use semantic booleans for clean rendering:
`tsx
{drop.isProcessing && }
{drop.hasError && }
{drop.isInteractive && }
`Or use
phase for switch-case logic:`tsx
switch (drop.phase) {
case 'idle': return 'Drop files here';
case 'dragging': return 'Drop now!';
case 'processing': return 'Processing...';
case 'ready': return ${drop.validFiles.length} files ready;
case 'error': return drop.status?.details;
}
`API
$3
`typescript
interface DropOptions {
ship: Ship; // Ship SDK instance (required)
onFilesReady?: (files: ProcessedFile[]) => void;
onValidationError?: (error: ClientError) => void;
stripPrefix?: boolean; // Strip common path prefix (default: true)
}
`$3
`typescript
interface DropReturn {
// State
phase: 'idle' | 'dragging' | 'processing' | 'ready' | 'error';
isProcessing: boolean;
isDragging: boolean;
isInteractive: boolean; // true when idle, dragging, or ready
hasError: boolean; // true when in error state
files: ProcessedFile[];
validFiles: ProcessedFile[];
sourceName: string;
status: { title: string; details: string; errors?: string[] } | null; // Prop getters
getDropzoneProps: (options?: { clickable?: boolean }) => {...};
getInputProps: () => {...};
// Actions
open: () => void; // Trigger file picker
processFiles: (files: File[]) => Promise;
reset: () => void; // Clear all files and reset state
// Helpers
getFilesForUpload: () => File[]; // Get raw File objects for SDK
}
`$3
`tsx
// Default: clickable dropzone (click opens file picker)
// Drag-only dropzone (no click behavior)
`Ship SDK Integration
Drop uses Ship SDK's validation automatically:
`tsx
const drop = useDrop({ ship });// Behind the scenes: ship.getConfig() → validateFiles()
// Client validation matches server limits
`Pass files to Ship SDK:
`tsx
const files = drop.getFilesForUpload();
await ship.deployments.create(files);
`Testing
The
/testing subpath provides mock utilities for testing components that use useDrop:`typescript
import {
createMockDrop,
createMockDropWithSpies,
createMockProcessedFile,
} from '@shipstatic/drop/testing';
`$3
`tsx
import { render, screen } from '@testing-library/react';
import { createMockDrop, createMockProcessedFile } from '@shipstatic/drop/testing';it('shows file count when ready', () => {
const drop = createMockDrop({
phase: 'ready',
files: [
createMockProcessedFile('index.html'),
createMockProcessedFile('style.css'),
],
});
render( );
expect(screen.getByText('2 files ready')).toBeInTheDocument();
});
`$3
`tsx
import userEvent from '@testing-library/user-event';
import { createMockDropWithSpies, createMockProcessedFile } from '@shipstatic/drop/testing';it('calls reset when Clear is clicked', async () => {
const { drop, spies } = createMockDropWithSpies({
phase: 'ready',
files: [createMockProcessedFile('index.html')],
});
render( );
await userEvent.click(screen.getByText('Clear'));
expect(spies.reset.toHaveBeenCalled()).toBe(true);
});
`$3
| Function | Purpose |
|----------|---------|
|
createMockDrop(options?) | Mock DropReturn for rendering tests |
| createMockDropWithSpies(options?) | Mock with call tracking for interaction tests |
| createMockProcessedFile(name, options?) | Mock ProcessedFile |
| createMockFile(name, content?, type?) | Mock File object |
| createMockFileWithPath(name, path, ...) | Mock File with webkitRelativePath |
| createMockErrorStatus(title?, details?, errors?) | Mock error status |
| createMockProcessingStatus(title?, details?) | Mock processing status |
| createMockReadyStatus(count)` | Mock ready status |Requirements
- React 18+ or 19+
- Modern browsers (Chrome, Edge, Safari 11.1+, Firefox 50+)
License
MIT