@get-set/gs-upload
v1.0.2
Published
Get-Set Upload
Maintainers
Readme
GSUpload
A dependency-free, feature-rich file upload / dropzone available in four usage modes from one codebase:
- Native / vanilla JS — a
window.GSUpload(selector | element, params)factory plus awindow.GSUploadConfigueinstance registry. - jQuery —
$(selector).GSUpload(params). - Prototype —
element.GSUpload(params)on anyHTMLElement. - React — a
<GSUpload />component with an imperative ref handle and a React-onlygsxscoped-style prop.
All four share the exact same engine (actions/, constants/, helpers/, types/), so behaviour is identical across every target.
Features
- Drag-and-drop zone with a beautiful drag-over highlight (border + lift + glow), click-to-browse, and paste-to-upload (Ctrl/Cmd+V of files/images).
multipleselection, or single-file mode that replaces the previous file.acceptfiltering by MIME (image/png), wildcard MIME (image/*) and extension (.pdf) — mixed lists welcome.maxSize(per file),maxFiles,minFiles, and duplicate detection (dedupe).- Image thumbnail previews (object URLs, auto-revoked) with a graceful extension badge fallback for non-images.
- Per-file progress bar with a smooth fill + shimmer animation, live percentage, and success/error states.
- Auto-upload to a
url(method/headers/fieldName/withCredentials/data) — or stage files and upload manually. - Remove and retry per file, with in-flight
XMLHttpRequestabort on remove/clear/destroy. - Client-side validation with a structured
onInvalid(file, reason, message)callback (accept/maxSize/maxFiles/duplicate). - Directory upload (
webkitdirectory). - List layout:
listorgrid. disabledstate.- Theming —
light/dark/auto(follows OS) plus a customaccentColortoken. - RTL, size presets (
sm/md/lg), and a React-onlygsxprop for scoped per-instance styles. - Full keyboard + ARIA a11y — the dropzone is a focusable
role="button"(Enter/Space open the picker), remove/retry buttons carryaria-labels. - Beautiful micro-interactions — add/remove pop, progress fill, drag-over lift — all respecting
prefers-reduced-motion. - Callbacks:
onAdd/onRemove/onProgress/onSuccess/onError/onComplete/onInvalid/onChange.
Installation
npm i @get-set/gs-uploadCompatibility
| Target | Requirement |
|---|---|
| React (the <GSUpload> component) | React 16.8+ (Hooks), and 17 / 18 / 19. React is an optional peer dependency — only needed for the component. |
| Native / vanilla / jQuery / prototype (window.GSUpload) | No framework. Any modern evergreen browser. |
| TypeScript | First-class — type declarations (.d.ts) ship in the package. |
| SSR / Next.js | Safe to import server-side (no DOM access at module load). Render inside a Client Component ('use client'). |
Package entry points
From package.json:
| Field | Value |
|---|---|
| name | @get-set/gs-upload |
| main | dist/components/GSUpload.js |
| module | dist/components/GSUpload.js |
| types | dist/components/GSUpload.d.ts |
| exports['.'].import | ./dist/components/GSUpload.js (React/ESM) |
| exports['.'].require | ./dist-js/bundle.js (native window bundle) |
| files | dist, dist-js, styles, example.html |
Project layout
GSUpload.ts # native entry (webpack -> dist-js/bundle.js, window global)
components/GSUpload.tsx # React component (tsc -> dist/, npm entry)
actions/ # shared engine (init / engine / render / destroy / refresh)
constants/ # defaultParams + sizes
helpers/ # validation, upload (XHR), files, format, icons, uihelpers
types/ # Params / Ref / Window augmentation
components/styles/ # SCSS + compiled CSS + CSS-as-TS (runtime injection for React)
styles/ # SCSS + compiled CSS (for <link> use by the native build)Build
npm install
npm run build # builds both targets
npm run build:js # native bundle -> dist-js/bundle.js
npm run build:react # React + types -> dist/
npm test # vitestVanilla / native usage
Include the bundle and the stylesheet, then call the factory. The factory accepts either a CSS selector string or a DOM element.
<link rel="stylesheet" href="node_modules/@get-set/gs-upload/styles/GSUpload.css" />
<script src="node_modules/@get-set/gs-upload/dist-js/bundle.js"></script>
<div id="dropzone"></div>
<!-- or enhance an existing file input -->
<input id="file-input" type="file" />
<script>
// By selector string
GSUpload('#dropzone', {
reference: 'gallery',
accept: 'image/*',
maxFiles: 10,
url: '/api/upload',
label: 'Drop images here',
hint: 'PNG, JPG, GIF up to 10 files',
onSuccess: (file, response) => console.log('uploaded', file.name, response),
onError: (file, err) => console.warn(err)
});
// …or by element (an input[type=file] is reused as the plugin input)
var el = document.getElementById('file-input');
GSUpload(el, { reference: 'single', multiple: false, accept: '.pdf' });
</script>Instance registry
Every instance registers under its reference in window.GSUploadConfigue:
const inst = GSUploadConfigue.instance('gallery');
inst.upload(); // upload all staged files
inst.open(); // open the native file browser
inst.addFiles(fileList); // add programmatically
inst.getFiles(); // snapshot of tracked files
inst.clear(); // remove everything
inst.destroy(); // teardown + restore host + unregisterjQuery usage
The bundle registers $.fn.GSUpload when jQuery is present. It initializes each matched element (passing the element, not the selector, to the factory):
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script src="node_modules/@get-set/gs-upload/dist-js/bundle.js"></script>
<div class="uploader"></div>
<script>
$('.uploader').GSUpload({
reference: 'jq',
accept: 'image/*',
url: '/api/upload'
});
// Reach the instance via the registry:
$('#go').on('click', () => GSUploadConfigue.instance('jq').upload());
</script>Prototype usage
The bundle also attaches HTMLElement.prototype.GSUpload, so any element can enhance itself:
<script src="node_modules/@get-set/gs-upload/dist-js/bundle.js"></script>
<input id="avatar" type="file" />
<script>
document.getElementById('avatar').GSUpload({
reference: 'avatar',
multiple: false,
accept: 'image/*',
maxSize: 1024 * 1024
});
</script>React usage
'use client';
import { useRef } from 'react';
import GSUpload, { GSUploadHandle, GSUploadFile } from '@get-set/gs-upload';
export default function Demo() {
const ref = useRef<GSUploadHandle>(null);
return (
<>
<GSUpload
ref={ref}
reference="react-demo"
accept="image/*"
maxFiles={8}
maxSize={5 * 1024 * 1024}
layout="grid"
theme="auto"
accentColor="#8b5cf6"
url="/api/upload"
auto={false}
label="Drop images here"
hint="Up to 8 images, 5 MB each"
onAdd={(added: GSUploadFile[]) => console.log('added', added)}
onProgress={(f, p) => console.log(f.name, p)}
onSuccess={(f, res) => console.log('done', f.name, res)}
onComplete={(all) => console.log('batch complete', all)}
// React-only: scoped per-instance styles (removed on unmount)
gsx={{
'.gs-upload-zone': { borderRadius: '20px' },
'.gs-upload-icon': { color: '#8b5cf6' }
}}
/>
<button onClick={() => ref.current?.open()}>Browse…</button>
<button onClick={() => ref.current?.upload()}>Upload all</button>
<button onClick={() => ref.current?.clear()}>Clear</button>
</>
);
}The GSUploadHandle (React ref)
| Method | Signature | Description |
|---|---|---|
| open | () => void | Open the native file browser dialog. |
| addFiles | (files: File[] \| FileList) => void | Add File objects programmatically (bypasses the picker; runs validation + auto-upload). |
| removeFile | (id: string) => void | Remove a tracked file by id (aborts its upload). |
| clear | () => void | Remove every file (aborts all in-flight uploads). |
| upload | (id?: string) => void | Upload all pending/failed files, or just one by id. |
| retry | (id: string) => void | Retry a failed / aborted file's upload. |
| getFiles | () => GSUploadFile[] | Snapshot of the tracked files. |
| isComplete | () => boolean | true when every non-invalid file uploaded successfully. |
| destroy | () => void | Abort uploads + revoke previews for this instance. |
Native instance methods (Ref / registry)
The native factory returns — and GSUploadConfigue.instance(ref) retrieves — an object exposing the same imperative surface, plus refresh():
open(), addFiles(files), removeFile(id), clear(), upload(id?), retry(id), getFiles(), isComplete(), refresh(), destroy().
Props / options
Every option below works identically across native params and React props (React additionally accepts gsx).
| Prop | Type | Default | Description |
|---|---|---|---|
| reference | string | auto GUID | Unique key used in the registry. |
| multiple | boolean | true | Allow selecting more than one file. |
| accept | string \| string[] | — | Accepted types: MIME, image/*, and/or .ext. |
| maxSize | number \| false | false | Max size per file in bytes. |
| maxFiles | number \| false | false | Max number of files kept. |
| minFiles | number | 0 | Minimum files required for upload() to proceed. |
| dedupe | boolean | true | Reject a file whose name+size already exists. |
| directory | boolean | false | Allow whole-directory selection (webkitdirectory). |
| paste | boolean | true | Enable paste-to-upload. |
| clickable | boolean | true | Enable click-to-browse on the dropzone. |
| dragDrop | boolean | true | Enable drag-and-drop. |
| auto | boolean | true | Auto-upload each file on add (requires url). |
| url | string | — | Upload endpoint. |
| method | 'POST' \| 'PUT' \| 'PATCH' | 'POST' | HTTP method. |
| headers | Record<string,string> | — | Extra request headers. |
| fieldName | string | 'file' | Multipart form field name for the file. |
| withCredentials | boolean | false | Send cookies / auth with the request. |
| data | Record<string,string> | — | Extra multipart form fields. |
| label | string | 'Drag & drop files here' | Big dropzone label. |
| hint | string | — | Secondary hint line. |
| buttonText | string | 'browse' | Browse link text. |
| preview | boolean | true | Show image thumbnail previews. |
| progressBar | boolean | true | Show the per-file progress bar. |
| layout | 'list' \| 'grid' | 'list' | File list layout. |
| showList | boolean | true | Render the file list. |
| removable | boolean | true | Show the per-file remove button. |
| size | 'sm' \| 'md' \| 'lg' | 'md' | Dropzone size preset. |
| disabled | boolean | false | Disable all interaction. |
| reducedMotion | 'auto' \| boolean | 'auto' | Honor prefers-reduced-motion. |
| theme | 'light' \| 'dark' \| 'auto' | 'auto' | Visual theme. |
| accentColor | string | #2563eb | Accent color (--gsu-accent). |
| rtl | boolean | false | Right-to-left layout. |
| className | string | — | Extra class on the root. |
| gsx | NestedCSS | — | React-only scoped per-instance styles. |
| onAdd | (added, all) => void | — | File(s) added (valid + invalid). |
| onRemove | (removed, all) => void | — | A file was removed. |
| onProgress | (file, percent) => void | — | Upload progress 0–100. |
| onSuccess | (file, response) => void | — | A file finished uploading. |
| onError | (file, error) => void | — | A file upload errored. |
| onComplete | (all) => void | — | The batch settled. |
| onInvalid | (file, reason, message) => void | — | A file failed validation. |
| onChange | (all) => void | — | Any change to the file set. |
The GSUploadFile object
Callbacks and getFiles() return tracked files shaped like:
interface GSUploadFile {
id: string; // stable per-instance id
file: File; // the underlying browser File
name: string;
size: number;
type: string;
status: 'pending' | 'uploading' | 'success' | 'error' | 'invalid' | 'aborted';
progress: number; // 0–100
previewUrl?: string; // object URL for image previews
error?: string; // validation / upload error message
invalidReason?: 'accept' | 'maxSize' | 'maxFiles' | 'duplicate';
response?: unknown; // server response (JSON or text) on success
}Catalogs
Layouts
| layout | Behaviour |
|---|---|
| list | Vertical rows: thumbnail + name/meta/progress + controls. |
| grid | Responsive card grid with large thumbnails — great for galleries. |
Themes
| theme | Behaviour |
|---|---|
| light | Light surfaces. |
| dark | Dark surfaces (slate palette). |
| auto | Follows prefers-color-scheme. |
Sizes
| size | Dropzone padding |
|---|---|
| sm | Compact. |
| md | Default. |
| lg | Spacious. |
Validation reasons (onInvalid)
| reason | Cause |
|---|---|
| accept | File type/extension not in accept. |
| maxSize | File larger than maxSize. |
| maxFiles | Adding would exceed maxFiles. |
| duplicate | Same name+size already added (dedupe). |
Accessibility
- The dropzone is a focusable
role="button"(tabindex=0); Enter / Space open the file picker. - Focus ring uses the accent color via
:focus-visible. - Remove / retry buttons carry descriptive
aria-labels; status icons arearia-hidden. - The loader / list regions announce politely.
- All motion respects
prefers-reduced-motion(and thereducedMotionprop).
Styling
Import the CSS once (native <link> or the React component injects it automatically):
<link rel="stylesheet" href="@get-set/gs-upload/styles/GSUpload.css" />Override the CSS custom properties to theme it:
.gs-upload {
--gsu-accent: #8b5cf6;
--gsu-radius: 20px;
}Or, in React, use the scoped gsx prop (auto-removed on unmount).
License
ISC © Get-Set
