socialhouse-stencil
v0.0.95
Published
Stencil Component Starter
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 time-limited upload policies, handles expiration and retries, and emits events on success or failure — all inside a fully encapsulated Shadow DOM component that works in any framework.
Features
- Drag-and-drop and file-picker uploads for images and videos
- Direct browser-to-S3 uploads via signed policies (no server proxy needed)
- Automatic upload policy refresh before expiration
- API token validation on mount and on change
- Per-file status tracking with thumbnail previews, loading spinners, success/error icons
- Configurable max file count
- Three layout orientations:
compact,horizontal,vertical - Light and dark mode
- Shadow DOM style encapsulation — no CSS conflicts with your app
- Framework-agnostic: works with React, Angular, Vue, or plain HTML
Props
| Prop | Type | Default | Required | Description |
|------|------|---------|----------|-------------|
| token | string | — | Yes | API authentication token. Validated against the Social House API on mount and whenever it changes. |
| baseUrl | string | — | Yes | Base URL for API requests (must include trailing slash, e.g. https://api.socialhouse.media/). |
| fullName | string | — | No | User's full name. Sent with the upload policy request. |
| email | string | — | No | User's email. Sent with the upload policy request. |
| maxFiles | number | 10 | No | Maximum number of files that can be uploaded at once. |
| orientation | 'vertical' \| 'horizontal' \| 'compact' | 'compact' | No | Component layout orientation. |
| mode | 'dark' \| 'light' | 'light' | No | Color theme. |
Events
| Event | Payload | Description |
|-------|---------|-------------|
| uploadSuccess | { key: string } | Emitted for each file that uploads successfully. key is the S3 object key. |
| uploadError | any | Emitted for each file that fails to upload. Contains the error response. |
TypeScript Types
The package exports the following types from socialhouse-stencil:
import type { HTMLShUploadElement, ShUploadCustomEvent } from 'socialhouse-stencil';| Type | Description |
|------|-------------|
| HTMLShUploadElement | Typed element interface for <sh-upload>. Use with querySelector or React refs to get autocompletion on props and methods. |
| ShUploadCustomEvent<T> | Typed custom event with detail: T and target: HTMLShUploadElement. Use instead of plain CustomEvent for type-safe event handlers. |
| Components.ShUpload | Props interface (available via the Components namespace export). |
Integration
Plain HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script type="module" src="node_modules/socialhouse-stencil/dist/socialhouse-stencil/socialhouse-stencil.esm.js"></script>
</head>
<body>
<sh-upload
token="your-api-token"
base-url="https://api.socialhouse.media/"
full-name="Jane Doe"
email="[email protected]"
max-files="5"
orientation="compact"
mode="light"
></sh-upload>
<script>
const el = document.querySelector('sh-upload');
el.addEventListener('uploadSuccess', (e) => {
console.log('Uploaded:', e.detail.key);
});
el.addEventListener('uploadError', (e) => {
console.error('Failed:', e.detail);
});
</script>
</body>
</html>Note: In HTML attributes, camelCase props become kebab-case:
baseUrl→base-url,maxFiles→max-files,fullName→full-name.
TypeScript usage
If your project uses TypeScript, import the typed element and event interfaces for full autocompletion and type safety:
import type { HTMLShUploadElement, ShUploadCustomEvent } from 'socialhouse-stencil';
const el = document.querySelector<HTMLShUploadElement>('sh-upload');
el?.addEventListener('uploadSuccess', (e: ShUploadCustomEvent<{ key: string }>) => {
console.log('Uploaded:', e.detail.key);
});
el?.addEventListener('uploadError', (e: ShUploadCustomEvent<any>) => {
console.error('Failed:', e.detail);
});React
1. Register the component loader once (e.g. in main.tsx or index.tsx)
import { defineCustomElements } from 'socialhouse-stencil/loader';
defineCustomElements();2. Define type definitions in your app (e.g. src/stencil-jsx.d.ts)
Add a declaration file so TypeScript knows sh-upload and its props in JSX:
import type { JSX as StencilJSX } from 'socialhouse-stencil';
declare module 'react' {
namespace JSX {
interface IntrinsicElements extends StencilJSX.IntrinsicElements {
'sh-upload': StencilJSX.ShUpload;
}
}
}Ensure the file is included by TypeScript (e.g. under src/ with "include": ["src"] in tsconfig.json).
3. Use in a component
import { useRef, useEffect } from 'react';
import type { HTMLShUploadElement, ShUploadCustomEvent } from 'socialhouse-stencil';
function UploadWidget() {
const ref = useRef<HTMLShUploadElement>(null);
useEffect(() => {
const el = ref.current;
if (!el) return;
const onSuccess = (e: ShUploadCustomEvent<{ key: string }>) => {
console.log('Uploaded:', e.detail.key);
};
const onError = (e: ShUploadCustomEvent<any>) => {
console.error('Failed:', e.detail);
};
el.addEventListener('uploadSuccess', onSuccess);
el.addEventListener('uploadError', onError);
return () => {
el.removeEventListener('uploadSuccess', onSuccess);
el.removeEventListener('uploadError', onError);
};
}, []);
return (
<sh-upload
ref={ref}
token="your-api-token"
baseUrl="https://api.socialhouse.media/"
fullName="Jane Doe"
email="[email protected]"
maxFiles={5}
orientation="compact"
mode="light"
/>
);
}Angular
1. Register the loader in main.ts
import { defineCustomElements } from 'socialhouse-stencil/loader';
defineCustomElements();2. Add CUSTOM_ELEMENTS_SCHEMA to your module
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
@NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
// ...
})
export class AppModule {}3. Use in a template
<sh-upload
[attr.token]="apiToken"
[attr.base-url]="baseUrl"
[attr.full-name]="fullName"
[attr.email]="email"
[attr.max-files]="maxFiles"
orientation="compact"
mode="light"
(uploadSuccess)="onSuccess($event)"
(uploadError)="onError($event)"
></sh-upload>import type { ShUploadCustomEvent } from 'socialhouse-stencil';
onSuccess(event: ShUploadCustomEvent<{ key: string }>) {
console.log('Uploaded:', event.detail.key);
}
onError(event: ShUploadCustomEvent<any>) {
console.error('Failed:', event.detail);
}Vue
1. Register the loader in main.ts
import { defineCustomElements } from 'socialhouse-stencil/loader';
defineCustomElements();2. Tell Vue to skip custom elements starting with sh-
// vite.config.ts
import vue from '@vitejs/plugin-vue';
export default {
plugins: [
vue({
template: {
compilerOptions: {
isCustomElement: (tag) => tag.startsWith('sh-'),
},
},
}),
],
};3. Use in a component
<template>
<sh-upload
token="your-api-token"
base-url="https://api.socialhouse.media/"
full-name="Jane Doe"
email="[email protected]"
max-files="5"
orientation="compact"
mode="light"
@uploadSuccess="onSuccess"
@uploadError="onError"
/>
</template>
<script setup lang="ts">
import type { ShUploadCustomEvent } from 'socialhouse-stencil';
function onSuccess(e: ShUploadCustomEvent<{ key: string }>) {
console.log('Uploaded:', e.detail.key);
}
function onError(e: ShUploadCustomEvent<any>) {
console.error('Failed:', e.detail);
}
</script>Upload Behavior
- Files are uploaded sequentially to ensure the upload policy stays valid.
- Each file's status is tracked independently (uploading, success, or error).
- Upload policies are automatically refreshed if they will expire within 10 seconds.
- A failed file does not block subsequent uploads.
- The component validates the API token on mount and re-validates whenever the
tokenprop changes. - Supported file types: images (jpg, png, gif) and videos (mp4, mov, avi, wmv).
