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

@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/* and video/* 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/details endpoint
  • 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, or compact layouts
  • Light/dark mode support - Theme switching with light or dark modes
  • 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 orientation and mode props
  • 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 token prop changes

📤 Publishing to npm

To publish this package as sh-upload:

  1. Log in to npm (one-time):

    npm login
  2. Bump version (optional):

    npm version patch   # or minor / major
  3. Publish:

    npm publish

    The prepublishOnly script runs npm run build automatically 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' or import { defineCustomElements } from 'sh-upload/loader' then defineCustomElements().
  • Tree-shaking: import 'sh-upload/sh-upload' to load only the custom element (auto-defined).