@shipstatic/drop
Version:
Headless React hook for file dropping, processing, ZIP extraction, and validation - purpose-built for Ship SDK
207 lines (199 loc) • 6.92 kB
TypeScript
import { StaticFile } from '@shipstatic/types';
import { Ship, formatFileSize as formatFileSize$1 } from '@shipstatic/ship';
/**
* Core types for @shipstatic/dropzone
* Imports types from @shipstatic/types (single source of truth)
* and defines dropzone-specific types
*/
declare const FILE_STATUSES: {
readonly PENDING: "pending";
readonly PROCESSING: "processing";
readonly VALIDATION_FAILED: "validation_failed";
readonly PROCESSING_ERROR: "processing_error";
readonly EMPTY_FILE: "empty_file";
readonly READY: "ready";
readonly UPLOADING: "uploading";
readonly COMPLETE: "complete";
readonly ERROR: "error";
};
type FileStatus = (typeof FILE_STATUSES)[keyof typeof FILE_STATUSES];
/**
* Client-side error structure
* Matches ValidationError from @shipstatic/ship for consistency
*/
interface ClientError {
error: string;
details: string;
errors?: string[];
isClientError: true;
}
/**
* Processed file entry ready for upload
* Extends StaticFile from SDK, adding UI-specific properties
* This means ProcessedFile IS a StaticFile - can be passed directly to ship.deployments.create()
*/
interface ProcessedFile extends StaticFile {
/** Unique identifier for React keys and tracking */
id: string;
/** Original File object (alias for 'content' from StaticFile for better DX) */
file: File;
/** Filename without path */
name: string;
/** MIME type for UI icons/previews */
type: string;
/** Last modified timestamp */
lastModified: number;
/** Current processing/upload status */
status: FileStatus;
/** Human-readable status message for UI */
statusMessage?: string;
/** Upload progress (0-100) - only set during upload */
progress?: number;
}
/**
* State machine values for the drop hook
*/
type DropStateValue = 'idle' | 'dragging' | 'processing' | 'ready' | 'error';
/**
* Status information with title and details
*/
interface DropStatus {
title: string;
details: string;
}
/**
* State machine state for the drop hook
*/
interface DropState {
value: DropStateValue;
files: ProcessedFile[];
sourceName: string;
status: DropStatus | null;
}
interface DropOptions {
/** Ship SDK instance (required for validation) */
ship: Ship;
/** Callback when files are processed and ready */
onFilesReady?: (files: ProcessedFile[]) => void;
/** Callback when validation fails */
onValidationError?: (error: ClientError) => void;
/** Whether to strip common directory prefix from paths (default: true) */
stripPrefix?: boolean;
}
interface DropReturn {
/** Current state of the drop hook */
state: DropState;
/** Whether currently processing files (ZIP extraction, etc.) */
isProcessing: boolean;
/** Whether user is currently dragging over the dropzone */
isDragging: boolean;
/** Get props to spread on dropzone element (handles drag & drop) */
getDropzoneProps: () => {
onDragOver: (e: React.DragEvent) => void;
onDragLeave: (e: React.DragEvent) => void;
onDrop: (e: React.DragEvent) => void;
onClick: () => void;
};
/** Get props to spread on hidden file input element */
getInputProps: () => {
ref: React.RefObject<HTMLInputElement | null>;
type: 'file';
style: {
display: string;
};
multiple: boolean;
webkitdirectory: string;
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
};
/** Programmatically trigger file picker */
open: () => void;
/** Manually process files (for advanced usage) */
processFiles: (files: File[]) => Promise<void>;
/** Clear all files and reset state */
clearAll: () => void;
/** Get only valid files ready for upload */
getValidFiles: () => ProcessedFile[];
/** Update upload state for a specific file (status, progress, message) */
updateFileStatus: (fileId: string, state: {
status: FileStatus;
statusMessage?: string;
progress?: number;
}) => void;
}
/**
* Headless drop hook for file upload workflows
*
* @example
* ```tsx
* const drop = useDrop({ ship });
*
* return (
* <div {...drop.getDropzoneProps()} style={{...}}>
* <input {...drop.getInputProps()} />
* {drop.isDragging ? "📂 Drop" : "📁 Click"}
* </div>
* );
* ```
*/
declare function useDrop(options: DropOptions): DropReturn;
/**
* Unified file processing utilities
* Converts Files directly to ProcessedFiles
*/
/**
* Format file size to human-readable string
* Re-exported from Ship SDK for convenience
*/
declare const formatFileSize: typeof formatFileSize$1;
/**
* Create a ProcessedFile from a File object
* This is the single conversion point from File to ProcessedFile
*
* Note: MD5 calculation is handled by Ship SDK during deployment.
* Drop focuses on file processing, path normalization, and UI state management.
*
* Path resolution priority:
* 1. options.path (if provided)
* 2. file.webkitRelativePath (if non-empty, preserves folder structure)
* 3. file.name (fallback)
*/
declare function createProcessedFile(file: File, options?: {
/** Custom path (defaults to webkitRelativePath or file.name) */
path?: string;
}): Promise<ProcessedFile>;
/**
* Get only the valid files (status: READY) from a list
* Re-exported from Ship SDK for convenience
*/
declare const getValidFiles: (files: ProcessedFile[]) => ProcessedFile[];
/**
* Strip common directory prefix from file paths
* Only strips if ALL files share the same prefix
*/
declare function stripCommonPrefix(files: ProcessedFile[]): ProcessedFile[];
interface ZipExtractionResult {
/** Extracted files as regular File objects */
files: File[];
/** Any errors encountered during extraction */
errors: string[];
}
/**
* Extracts all files from a ZIP archive
* Returns regular File objects that can be processed like any other file
*/
declare function extractZipToFiles(zipFile: File): Promise<ZipExtractionResult>;
/**
* Sanitize and normalize a file path to prevent directory traversal attacks
* Removes: .., ., leading/trailing slashes, absolute paths, and empty segments
*
* @example
* normalizePath('../../etc/passwd') → 'etc/passwd'
* normalizePath('foo/./bar/../baz.txt') → 'foo/baz.txt'
* normalizePath('/absolute/path.txt') → 'absolute/path.txt'
*/
declare function normalizePath(path: string): string;
/**
* Check if a file is a ZIP file based on MIME type or extension
*/
declare function isZipFile(file: File): boolean;
export { type ClientError, type DropOptions, type DropReturn, type DropState, type DropStateValue, type DropStatus, FILE_STATUSES, type FileStatus, type ProcessedFile, type ZipExtractionResult, createProcessedFile, extractZipToFiles, formatFileSize, getValidFiles, isZipFile, normalizePath, stripCommonPrefix, useDrop };