@botandrose/input-attachment
v0.6.4
Published
Next-gen file field
Downloads
35
Readme
input-attachment
A web components library providing advanced file upload functionality for Rails Active Storage with drag-and-drop support and real-time progress tracking.
Features
- 📁 Multiple File Support - Upload single or multiple files
- 🎨 Drag & Drop - Intuitive drag-and-drop file selection
- 📸 File Previews - Automatic preview generation for images and videos
- 📊 Progress Tracking - Real-time upload progress with visual feedback
- ✅ Validation - Built-in file type and size validation
- 🔐 Rails Active Storage Integration - Seamless direct uploads to AWS S3 or other storage backends
Installation
npm install @botandrose/input-attachmentor with Bun:
bun add @botandrose/input-attachmentUsage
Basic Example
<form>
<input-attachment
name="files"
directupload="/rails/active_storage/direct_uploads"
multiple
></input-attachment>
<button type="submit">Upload</button>
</form>With Validation
<input-attachment
name="photos"
directupload="/rails/active_storage/direct_uploads"
accepts="image"
max="5242880"
required
></input-attachment>API Reference
<input-attachment>
The main component providing file upload functionality.
Props
| Prop | Type | Default | Description |
| ------ | ------ | --------- | ------------- |
| name | string | - | Form field name for form submission |
| directupload | string | - | Rails Active Storage direct upload endpoint URL |
| multiple | boolean | false | Allow multiple file selection |
| required | boolean | false | Require at least one file |
| accepts | string | - | Comma-separated file types (e.g., "image", "video", "pdf") |
| max | number | - | Maximum file size in bytes |
| preview | boolean | true | Show file previews |
| disabled | boolean | false | Disable file selection (used during form submission) |
Methods
// Get/set array of file objects
get files(): AttachmentFile[]
set files(val: AttachmentFile[])
// Get/set the attachments as a JSON string of { value: signed_id, filename, src, ... } objects
get value(): string
set value(val: string)
// Clear all files
reset(): void
// Validate the component
checkValidity(): boolean
setCustomValidity(msg: string): void
reportValidity(): boolean
// Get validation error message
get validationMessage(): stringEvents
| Event | Detail | Description |
| ------- | -------- | ------------- |
| change | - | Fired when file list changes (bubbles) |
| direct-upload:initialize | { id, file, controller } | Upload queue initialized |
| direct-upload:start | { id } | Upload started |
| direct-upload:progress | { id, progress } | Upload progress (0-100) |
| direct-upload:error | { id, error } | Upload failed |
| direct-upload:end | { id } | Upload completed |
<attachment-file>
Individual file representation within the upload component.
Props
| Prop | Type | Default | Description |
| ------ | ------ | --------- | ------------- |
| name | string | - | Form field name |
| value | string | "" | Signed ID from Rails Active Storage |
| filename | string | - | Display filename |
| src | string | - | Preview image/video URL |
| filetype | string | - | File category (image/video/pdf/unknown) |
| size | number | - | File size in bytes |
| state | string | "complete" | Upload state (pending/complete/error) |
| percent | number | 100 | Upload progress percentage |
| preview | boolean | true | Show preview |
| accepts | string | - | Allowed file types |
| max | number | - | Maximum file size |
Methods
// Set file to upload
set file(file: File)
// Load existing file from Active Storage
set signedId(val: string)
// Validate the file
checkValidity(): booleanOutside Rails (cross-origin, token auth)
directupload can be an absolute URL on another origin, as long as that endpoint speaks the
@rails/activestorage protocol and the storage bucket's CORS allows your origin. Add
credentials from the direct-upload:before-blob-request event, which carries the XHR for the
blob-reserving request:
const el = document.querySelector("input-attachment")
el.addEventListener("direct-upload:before-blob-request", event => {
event.detail.xhr.setRequestHeader("Authorization", `Bearer ${token}`)
})The element registers itself only after defineCustomElements() runs, so in a framework call it
once on the client:
import { defineCustomElements } from "@botandrose/input-attachment"
defineCustomElements()Without a <form> submit, read the signed id from the change event: value is a JSON string
of the current attachments, and each one's value is its signed id.
el.addEventListener("change", () => {
const [attachment] = JSON.parse(el.value)
console.log(attachment?.value) // the signed id, or undefined once removed
})Form Submission Flow
- User selects/drops files into
<input-attachment> - Files become
<attachment-file>components with validation - On form submit,
FormControllerintercepts and manages upload queue - Each file uploads to Rails Active Storage via
DirectUploadController - Signed IDs are collected via
ElementInternals.setFormValue() - After all uploads complete, form is actually submitted
- Server receives signed IDs in form data
Architecture
Components
<input-attachment>- Main form field replacement with drag-and-drop<attachment-file>- Individual file representation<attachment-preview>- File preview display<file-drop>- Drag and drop interface (from@botandrose/file-drop)<progress-bar>- Upload progress indicator (from@botandrose/progress-bar)
Styling
The components use Shadow DOM, so external CSS cannot reach their internals. Instead, all visual properties are exposed as CSS custom properties that pierce the shadow boundary. Set them on the input-attachment element or any ancestor.
CSS Custom Properties
Text & Drop Zone
| Property | Default | Description |
| -------- | ------- | ----------- |
| --input-attachment-text-color | #000 | Main text color |
| --input-attachment-drop-bg | rgba(255,255,255, 0.25) | Drop zone background |
| --input-attachment-drop-border | rgba(0,0,0, 0.25) | Drop zone dashed border |
| --input-attachment-drop-color | #444 | Drop zone text color |
| --input-attachment-drop-bg-active | rgba(255,255,255, 0.5) | Drop zone background during drag-over |
Upload Dialog
| Property | Default | Description |
| -------- | ------- | ----------- |
| --input-attachment-overlay-bg | rgba(51, 51, 51, 0.9) | Modal overlay background |
| --input-attachment-dialog-bg | #fcfcfc | Dialog content background |
| --input-attachment-dialog-border | #1f1f1f | Dialog heading border |
Errors
| Property | Default | Description |
| -------- | ------- | ----------- |
| --input-attachment-error-color | #c00 | Validation error and retry link text |
| --input-attachment-error-color-hover | #900 | Retry link hover color |
| --input-attachment-error-bg | rgba(74, 70, 70, 0.25) | Error state progress bar background |
Parts
Style the file-drop title using the ::part() pseudo-element:
input-attachment::part(title) {
font-size: 16px;
}Rails Integration
Setup Active Storage Direct Uploads
In your Rails app, ensure Active Storage is configured:
# config/storage.yml
amazon:
service: S3
access_key_id: ...
secret_access_key: ...The directupload prop should point to:
/rails/active_storage/direct_uploadsAccessing Uploaded Files in Rails
class Post < ApplicationRecord
has_many_attached :attachments
end
# In controller
@post = Post.create(attachments: attachment_signed_ids)
# The signed_ids are automatically resolved to blobsValidation
File Type Validation
<input-attachment
accepts="image,video"
></input-attachment>Supported types: image, video, pdf, or specific MIME types
File Size Validation
<!-- Max 5MB -->
<input-attachment
max="5242880"
></input-attachment>Custom Validation
const attachment = document.querySelector('input-attachment');
// Check validity
if (!attachment.checkValidity()) {
console.log(attachment.validationMessage);
}
// Set custom error
attachment.setCustomValidity('Custom error message');Development
Prerequisites
- Node.js 18+
- Bun
Commands
# Install dependencies
bun install
# Start development server
bun start
# Build for production
bun run build
# Run tests
bun run test
# Watch tests
bun run test.watch
# Run e2e tests only
bun run test:e2e
# Generate new component
bun run generateBrowser Support
- Chrome/Edge 88+
- Firefox 85+
- Safari 15.1+
ElementInternals requires modern browsers with form-associated custom elements support.
License
MIT
