Universal Media Picker
Headless media picker (local/URL/library sources, canvas crop + compress, S3-compatible and Cloudinary providers) with React and Svelte adapters. Credentials always live in your own signing backend, never in this component.
Variables de entorno
| Clave | Requerida | Descripción |
|---|---|---|
S3_ENDPOINT | Sí | S3-compatible endpoint (AWS/MinIO) used by YOUR backend's presigned-URL signing endpoint. Never read by the client bundle — the core only ever calls the `getUploadUrl` hook you implement. |
S3_ACCESS_KEY_ID | Sí | S3-compatible access key used by your backend to sign presigned PUT/POST URLs. Never sent to the browser. |
S3_SECRET_ACCESS_KEY | Sí | S3-compatible secret key used by your backend to sign presigned PUT/POST URLs. Never sent to the browser. |
S3_BUCKET | Sí | Bucket name your backend signs presigned URLs against. |
CLOUDINARY_CLOUD_NAME | Sí | Cloudinary cloud name. Used both by your signing backend and to build public delivery URLs. |
CLOUDINARY_API_KEY | Sí | Cloudinary API key used by your backend to compute signed-upload params. Never sent to the browser. |
CLOUDINARY_API_SECRET | Sí | Cloudinary API secret used by your backend to compute signed-upload params. Never sent to the browser. |
Instalación manual
Descarga el tarball y copia cada archivo listado abajo a su ruta destino:
curl -L -o media-picker.tar.gz https://TU_HOST/registry/media-picker.tar.gz
tar -xzf media-picker.tar.gz | Origen | Destino en tu proyecto |
|---|---|
core/provider.ts | src/modularcore/media-picker/core/provider.ts |
core/media-picker.ts | src/modularcore/media-picker/core/media-picker.ts |
core/sources.ts | src/modularcore/media-picker/core/sources.ts |
core/net/ssrf-guard.ts | src/modularcore/media-picker/core/net/ssrf-guard.ts |
core/canvas/canvas-environment.ts | src/modularcore/media-picker/core/canvas/canvas-environment.ts |
core/canvas/crop.ts | src/modularcore/media-picker/core/canvas/crop.ts |
core/canvas/compress.ts | src/modularcore/media-picker/core/canvas/compress.ts |
core/canvas/transform.ts | src/modularcore/media-picker/core/canvas/transform.ts |
core/library-state.ts | src/modularcore/media-picker/core/library-state.ts |
core/providers/s3-compatible.ts | src/modularcore/media-picker/core/providers/s3-compatible.ts |
core/providers/cloudinary.ts | src/modularcore/media-picker/core/providers/cloudinary.ts |
adapters/react/use-media-picker.ts | src/modularcore/media-picker/adapters/react/use-media-picker.ts |
adapters/svelte/create-media-picker.svelte.ts | src/modularcore/media-picker/adapters/svelte/create-media-picker.svelte.ts |
ui/react/MediaLibraryGrid.tsx | src/modularcore/media-picker/ui/react/MediaLibraryGrid.tsx |
ui/react/FolderSelect.tsx | src/modularcore/media-picker/ui/react/FolderSelect.tsx |
ui/react/MimeTypeFilter.tsx | src/modularcore/media-picker/ui/react/MimeTypeFilter.tsx |
ui/react/ImageEditor.tsx | src/modularcore/media-picker/ui/react/ImageEditor.tsx |
ui/react/BulkActionsBar.tsx | src/modularcore/media-picker/ui/react/BulkActionsBar.tsx |
ui/react/RemoteUrlLoader.tsx | src/modularcore/media-picker/ui/react/RemoteUrlLoader.tsx |
ui/svelte/MediaLibraryGrid.svelte | src/modularcore/media-picker/ui/svelte/MediaLibraryGrid.svelte |
ui/svelte/FolderSelect.svelte | src/modularcore/media-picker/ui/svelte/FolderSelect.svelte |
ui/svelte/MimeTypeFilter.svelte | src/modularcore/media-picker/ui/svelte/MimeTypeFilter.svelte |
ui/svelte/ImageEditor.svelte | src/modularcore/media-picker/ui/svelte/ImageEditor.svelte |
ui/svelte/BulkActionsBar.svelte | src/modularcore/media-picker/ui/svelte/BulkActionsBar.svelte |
ui/svelte/RemoteUrlLoader.svelte | src/modularcore/media-picker/ui/svelte/RemoteUrlLoader.svelte |
ui/react/tailwind/MediaLibraryGrid.tsx | src/modularcore/media-picker/ui/react/tailwind/MediaLibraryGrid.tsx |
ui/react/tailwind/FolderSelect.tsx | src/modularcore/media-picker/ui/react/tailwind/FolderSelect.tsx |
ui/react/tailwind/MimeTypeFilter.tsx | src/modularcore/media-picker/ui/react/tailwind/MimeTypeFilter.tsx |
ui/react/tailwind/ImageEditor.tsx | src/modularcore/media-picker/ui/react/tailwind/ImageEditor.tsx |
ui/react/tailwind/BulkActionsBar.tsx | src/modularcore/media-picker/ui/react/tailwind/BulkActionsBar.tsx |
ui/react/tailwind/RemoteUrlLoader.tsx | src/modularcore/media-picker/ui/react/tailwind/RemoteUrlLoader.tsx |
ui/svelte/tailwind/MediaLibraryGrid.svelte | src/modularcore/media-picker/ui/svelte/tailwind/MediaLibraryGrid.svelte |
ui/svelte/tailwind/FolderSelect.svelte | src/modularcore/media-picker/ui/svelte/tailwind/FolderSelect.svelte |
ui/svelte/tailwind/MimeTypeFilter.svelte | src/modularcore/media-picker/ui/svelte/tailwind/MimeTypeFilter.svelte |
ui/svelte/tailwind/ImageEditor.svelte | src/modularcore/media-picker/ui/svelte/tailwind/ImageEditor.svelte |
ui/svelte/tailwind/BulkActionsBar.svelte | src/modularcore/media-picker/ui/svelte/tailwind/BulkActionsBar.svelte |
ui/svelte/tailwind/RemoteUrlLoader.svelte | src/modularcore/media-picker/ui/svelte/tailwind/RemoteUrlLoader.svelte |
ui/react/shadcn/MediaLibraryGrid.tsx | src/modularcore/media-picker/ui/react/shadcn/MediaLibraryGrid.tsx |
ui/react/shadcn/FolderSelect.tsx | src/modularcore/media-picker/ui/react/shadcn/FolderSelect.tsx |
ui/react/shadcn/MimeTypeFilter.tsx | src/modularcore/media-picker/ui/react/shadcn/MimeTypeFilter.tsx |
ui/react/shadcn/ImageEditor.tsx | src/modularcore/media-picker/ui/react/shadcn/ImageEditor.tsx |
ui/react/shadcn/BulkActionsBar.tsx | src/modularcore/media-picker/ui/react/shadcn/BulkActionsBar.tsx |
ui/react/shadcn/RemoteUrlLoader.tsx | src/modularcore/media-picker/ui/react/shadcn/RemoteUrlLoader.tsx |
ui/svelte/shadcn/MediaLibraryGrid.svelte | src/modularcore/media-picker/ui/svelte/shadcn/MediaLibraryGrid.svelte |
ui/svelte/shadcn/FolderSelect.svelte | src/modularcore/media-picker/ui/svelte/shadcn/FolderSelect.svelte |
ui/svelte/shadcn/MimeTypeFilter.svelte | src/modularcore/media-picker/ui/svelte/shadcn/MimeTypeFilter.svelte |
ui/svelte/shadcn/ImageEditor.svelte | src/modularcore/media-picker/ui/svelte/shadcn/ImageEditor.svelte |
ui/svelte/shadcn/BulkActionsBar.svelte | src/modularcore/media-picker/ui/svelte/shadcn/BulkActionsBar.svelte |
ui/svelte/shadcn/RemoteUrlLoader.svelte | src/modularcore/media-picker/ui/svelte/shadcn/RemoteUrlLoader.svelte |
ui/react/vanilla/MediaLibraryGrid.tsx | src/modularcore/media-picker/ui/react/vanilla/MediaLibraryGrid.tsx |
ui/react/vanilla/FolderSelect.tsx | src/modularcore/media-picker/ui/react/vanilla/FolderSelect.tsx |
ui/react/vanilla/MimeTypeFilter.tsx | src/modularcore/media-picker/ui/react/vanilla/MimeTypeFilter.tsx |
ui/react/vanilla/ImageEditor.tsx | src/modularcore/media-picker/ui/react/vanilla/ImageEditor.tsx |
ui/react/vanilla/BulkActionsBar.tsx | src/modularcore/media-picker/ui/react/vanilla/BulkActionsBar.tsx |
ui/react/vanilla/RemoteUrlLoader.tsx | src/modularcore/media-picker/ui/react/vanilla/RemoteUrlLoader.tsx |
ui/svelte/vanilla/MediaLibraryGrid.svelte | src/modularcore/media-picker/ui/svelte/vanilla/MediaLibraryGrid.svelte |
ui/svelte/vanilla/FolderSelect.svelte | src/modularcore/media-picker/ui/svelte/vanilla/FolderSelect.svelte |
ui/svelte/vanilla/MimeTypeFilter.svelte | src/modularcore/media-picker/ui/svelte/vanilla/MimeTypeFilter.svelte |
ui/svelte/vanilla/ImageEditor.svelte | src/modularcore/media-picker/ui/svelte/vanilla/ImageEditor.svelte |
ui/svelte/vanilla/BulkActionsBar.svelte | src/modularcore/media-picker/ui/svelte/vanilla/BulkActionsBar.svelte |
ui/svelte/vanilla/RemoteUrlLoader.svelte | src/modularcore/media-picker/ui/svelte/vanilla/RemoteUrlLoader.svelte |
ui/shadcn-theme.css | src/modularcore/media-picker/ui/shadcn-theme.css |
ui/vanilla-styles.css | src/modularcore/media-picker/ui/vanilla-styles.css |
Instalación con CLI
modularcore add media-pickerDocumentación
# @modularcore/media-picker
Headless media picker — local file / remote URL / library sources, canvas crop + compress, and
S3-compatible + Cloudinary storage providers — with React and Svelte adapters.
Credentials never live in this component.
only seam to a storage backend, and its interface has no place to pass a long-lived secret
(access key, API secret). Any real provider (
signed upload payload) from a backend endpoint that you control and implement — see
[
[
## What's in this package
-
upload), with generation-guarded state so stale async results never clobber newer ones.
-
-
provider-backed library key.
-
-
-
implementations that call into your signing backend.
-
Svelte 5 runes).
## Basic usage (Svelte 5)
For a demo/playground with no real backend, implement a minimal in-memory
serves them via
## UI style variants
Each of the 6 UI components (
only markup/CSS differs:
-
-
-
Tailwind CSS v4 — copy
comment for a bundler gotcha some setups hit when
-
framework dependency, works with or without a bundler.
Headless media picker — local file / remote URL / library sources, canvas crop + compress, and
S3-compatible + Cloudinary storage providers — with React and Svelte adapters.
Credentials never live in this component.
StorageProvider (see core/provider.ts) is theonly seam to a storage backend, and its interface has no place to pass a long-lived secret
(access key, API secret). Any real provider (
core/providers/s3-compatible.ts,core/providers/cloudinary.ts) obtains short-lived, scoped credentials (a presigned URL, asigned upload payload) from a backend endpoint that you control and implement — see
[
docs/s3-presign-endpoint-example.md](./docs/s3-presign-endpoint-example.md) and[
docs/cloudinary-signing-endpoint-example.md](./docs/cloudinary-signing-endpoint-example.md).## What's in this package
-
core/media-picker.ts — MediaPicker, the headless orchestrator (load → crop → compress →upload), with generation-guarded state so stale async results never clobber newer ones.
-
core/provider.ts — the StorageProvider interface every backend implements against.-
core/sources.ts — load a Blob from a local File, a remote URL (SSRF-guarded), or aprovider-backed library key.
-
core/canvas/crop.ts, core/canvas/compress.ts — canvas-based image transforms.-
core/net/ssrf-guard.ts — blocks private/loopback/link-local targets before any remote fetch.-
core/providers/s3-compatible.ts, core/providers/cloudinary.ts — real providerimplementations that call into your signing backend.
-
adapters/react, adapters/svelte — thin bindings over MediaPicker (Svelte adapter usesSvelte 5 runes).
## Basic usage (Svelte 5)
import { createMediaPicker } from '@modularcore/media-picker/svelte';
import { createS3CompatibleProvider } from '@modularcore/media-picker/providers/s3-compatible';
const picker = createMediaPicker();
const provider = createS3CompatibleProvider({ getUploadUrl: /* your signing endpoint call */ });
picker.loadLocalFile(file);
await picker.crop({ x: 0, y: 0, width: 512, height: 512 });
await picker.upload(provider);
For a demo/playground with no real backend, implement a minimal in-memory
StorageProvider (seeapps/web/src/lib/demo-storage-provider.ts in this monorepo) that keeps blobs in a Map andserves them via
URL.createObjectURL — never wire a real provider to unauthenticated demo pages.## UI style variants
Each of the 6 UI components (
MediaLibraryGrid, FolderSelect, MimeTypeFilter, ImageEditor,BulkActionsBar, RemoteUrlLoader) ships in 4 presentations, all with identical props/behavior —only markup/CSS differs:
-
ui/react/.tsx, ui/svelte/.svelte — headless, unstyled reference UI (the original default).-
ui/{react,svelte}/tailwind/ — Tailwind CSS utility classes only.-
ui/{react,svelte}/shadcn/ — Shadcn/ui theme, using real @radix-ui/react-toggle +@radix-ui/react-slider (React) or bits-ui (Svelte) as optional peer dependencies. RequiresTailwind CSS v4 — copy
ui/shadcn-theme.css into your own Tailwind entry CSS (see its headercomment for a bundler gotcha some setups hit when
@importing it instead).-
ui/{react,svelte}/vanilla/ — plain CSS (ui/vanilla-styles.css, mc-* class prefix), noframework dependency, works with or without a bundler.