@asim3bird/sh-upload
v0.0.2
Published
Drag-and-drop media upload web component for S3 (works with any bundler or vanilla JS)
Readme
sh-upload
A drag-and-drop media upload web component built with StencilJS that uploads images and videos directly to Amazon S3 using signed upload policies.
<sh-upload> validates an API token, fetches upload policies, handles expiration, retries, and emits upload events — all inside a fully encapsulated Shadow DOM component.
✨ Features
- Drag & drop + file picker uploads - Intuitive file selection interface
- Image & video support - Accepts
image/*andvideo/*file types (jpg, png, gif, mp4, mov, avi, wmv) - Direct browser → S3 uploads - Files upload directly to Amazon S3 using signed upload policies
- Automatic upload policy refresh - Policies are automatically refreshed before expiration
- Token validation - Validates API token on component load and when token changes via
/v1/house/detailsendpoint - Error & retry handling - Visual error states with retry button for failed validations or expired policies
- Individual file status tracking - Each file shows its own upload status (uploading, success, error) with visual indicators
- File previews - Image previews with status overlays (loading spinner, success checkmark, error icon)
- Multiple file uploads - Upload multiple files simultaneously with configurable maximum limit
- Multiple orientation options -
vertical,horizontal, orcompactlayouts - Light/dark mode support - Theme switching with
lightordarkmodes - Social House branding - Logo display that adapts based on orientation and mode, plus "Powered by Social House" footer
- Shadow DOM encapsulated styles - Fully isolated component styles
- Framework-agnostic - Works with any bundler (Vite, Webpack, Rollup, etc.) or vanilla JS (no build step)
📦 Installation
npm install sh-upload🚀 Integration
Vanilla (no bundler)
Use the component from a CDN with a single script tag (no install required):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>sh-upload Example</title>
<script type="module" src="https://unpkg.com/sh-upload"></script>
</head>
<body>
<sh-upload
api-key="your-api-key"
base-url="https://api.example.com/"
house-id="your-house-id"
max-files="10"
orientation="compact"
mode="light">
</sh-upload>
<script>
document.querySelector('sh-upload').addEventListener('uploadSuccess', (e) => {
console.log('Upload successful:', e.detail.key);
});
</script>
</body>
</html>With npm and a local build, use the same ESM entry:
<script type="module" src="node_modules/sh-upload/dist/sh-upload/sh-upload.esm.js"></script>React
1. Import the component loader
import { defineCustomElements } from 'sh-upload/loader';
// Call this once in your app initialization
defineCustomElements();2. Use the component in your React component
import React, { useRef, useEffect } from 'react';
function UploadComponent() {
const uploadRef = useRef<HTMLShUploadElement>(null);
useEffect(() => {
const uploadElement = uploadRef.current;
if (!uploadElement) return;
const handleSuccess = (event: CustomEvent<{ key: string }>) => {
console.log('Upload successful:', event.detail.key);
// Handle successful upload
};
const handleError = (event: CustomEvent<any>) => {
console.error('Upload error:', event.detail);
// Handle upload error
};
uploadElement.addEventListener('uploadSuccess', handleSuccess);
uploadElement.addEventListener('uploadError', handleError);
return () => {
uploadElement.removeEventListener('uploadSuccess', handleSuccess);
uploadElement.removeEventListener('uploadError', handleError);
};
}, []);
return (
<sh-upload
ref={uploadRef}
token="your-api-token"
baseUrl="https://api.example.com/"
eventName="your-event-name"
maxFiles={10}
orientation="compact"
mode="light"
/>
);
}
export default UploadComponent;3. TypeScript support (optional)
Add type definitions to your tsconfig.json:
{
"compilerOptions": {
"types": ["sh-upload/dist/types"]
}
}Angular
1. Import the component loader in main.ts or app.module.ts
import { defineCustomElements } from 'sh-upload/loader';
// Call this once in your app initialization
defineCustomElements();2. Use the component in your Angular template
<!-- app.component.html -->
<sh-upload
[token]="apiToken"
[baseUrl]="baseUrl"
[eventName]="eventName"
[maxFiles]="10"
orientation="compact"
mode="light"
(uploadSuccess)="onUploadSuccess($event)"
(uploadError)="onUploadError($event)">
</sh-upload>3. Handle events in your component
// app.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
apiToken = 'your-api-token';
baseUrl = 'https://api.example.com/';
eventName = 'your-event-name';
onUploadSuccess(event: CustomEvent<{ key: string }>) {
console.log('Upload successful:', event.detail.key);
// Handle successful upload
}
onUploadError(event: CustomEvent<any>) {
console.error('Upload error:', event.detail);
// Handle upload error
}
}4. Add CUSTOM_ELEMENTS_SCHEMA to your module
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule],
providers: [],
bootstrap: [AppComponent],
schemas: [CUSTOM_ELEMENTS_SCHEMA] // Add this
})
export class AppModule { }TypeScript (Plain HTML/TypeScript)
1. Include the component script
Use the CDN (see Vanilla above) or your bundler’s public path to node_modules/sh-upload/dist/sh-upload/sh-upload.esm.js.
2. TypeScript with type definitions
import type { HTMLShUploadElement } from 'sh-upload';
const uploadElement = document.querySelector<HTMLShUploadElement>('sh-upload');
if (uploadElement) {
uploadElement.token = 'your-api-token';
uploadElement.baseUrl = 'https://api.example.com/';
uploadElement.eventName = 'your-event-name';
uploadElement.maxFiles = 10;
uploadElement.orientation = 'compact';
uploadElement.mode = 'light';
uploadElement.addEventListener('uploadSuccess', (event: CustomEvent<{ key: string }>) => {
console.log('Upload successful:', event.detail.key);
});
uploadElement.addEventListener('uploadError', (event: CustomEvent<any>) => {
console.error('Upload error:', event.detail);
});
}📋 Component Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| token | string | required | API authentication token |
| baseUrl | string | required | Base URL for API requests |
| eventName | string | 'bubty' | Event name for upload policy |
| maxFiles | number | 10 | Maximum number of files to upload |
| orientation | 'vertical' \| 'horizontal' \| 'compact' | 'compact' | Component orientation |
| mode | 'dark' \| 'light' | 'light' | Color theme |
📡 Events
| Event | Payload | Description |
|-------|---------|-------------|
| uploadSuccess | { key: string } | Emitted when a file upload succeeds. The key is the S3 object key for the uploaded file. |
| uploadError | any | Emitted when a file upload fails. Contains error details from the upload request. |
🎨 UI Components
The component includes several visual elements:
- Logo - Social House logo displayed at the top, automatically selected based on
orientationandmodeprops - Dropzone - Main upload area with drag-and-drop functionality and visual icons
- File Previews - Thumbnail previews for images with status indicators:
- Loading spinner during upload
- Green checkmark on success
- Red error icon on failure
- Upload Button - File picker button to select files
- Error State - Displays error messages with a retry button when validation fails or policies expire
- Footer - "Powered by Social House" branding at the bottom
🔄 Upload Behavior
- Files are uploaded sequentially (one after another) to ensure proper policy management
- Each file's upload status is tracked independently
- Upload policies are automatically refreshed if they're close to expiring (within 10 seconds)
- Failed uploads are marked with an error state but don't block subsequent uploads
- The component validates the API token on mount and whenever the
tokenprop changes
📤 Publishing to npm
To publish this package as sh-upload:
Log in to npm (one-time):
npm loginBump version (optional):
npm version patch # or minor / majorPublish:
npm publishThe
prepublishOnlyscript runsnpm run buildautomatically before publish.
The package is built so it works with every bundler (Vite, Webpack, Rollup, Parcel, etc.) and vanilla JS:
- Vanilla: Use
<script type="module" src="https://unpkg.com/sh-upload"></script>(unpkg resolves to the ESM bundle). - Bundlers:
import 'sh-upload'orimport { defineCustomElements } from 'sh-upload/loader'thendefineCustomElements(). - Tree-shaking:
import 'sh-upload/sh-upload'to load only the custom element (auto-defined).
