ModularCore Hub
← Volver al catálogo

Universal Media Picker

media v0.1.0 react, svelte

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

ClaveRequeridaDescripción
S3_ENDPOINTS3-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_IDS3-compatible access key used by your backend to sign presigned PUT/POST URLs. Never sent to the browser.
S3_SECRET_ACCESS_KEYS3-compatible secret key used by your backend to sign presigned PUT/POST URLs. Never sent to the browser.
S3_BUCKETBucket name your backend signs presigned URLs against.
CLOUDINARY_CLOUD_NAMECloudinary cloud name. Used both by your signing backend and to build public delivery URLs.
CLOUDINARY_API_KEYCloudinary API key used by your backend to compute signed-upload params. Never sent to the browser.
CLOUDINARY_API_SECRETCloudinary 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
OrigenDestino en tu proyecto
core/provider.tssrc/modularcore/media-picker/core/provider.ts
core/media-picker.tssrc/modularcore/media-picker/core/media-picker.ts
core/sources.tssrc/modularcore/media-picker/core/sources.ts
core/net/ssrf-guard.tssrc/modularcore/media-picker/core/net/ssrf-guard.ts
core/canvas/canvas-environment.tssrc/modularcore/media-picker/core/canvas/canvas-environment.ts
core/canvas/crop.tssrc/modularcore/media-picker/core/canvas/crop.ts
core/canvas/compress.tssrc/modularcore/media-picker/core/canvas/compress.ts
core/canvas/transform.tssrc/modularcore/media-picker/core/canvas/transform.ts
core/library-state.tssrc/modularcore/media-picker/core/library-state.ts
core/providers/s3-compatible.tssrc/modularcore/media-picker/core/providers/s3-compatible.ts
core/providers/cloudinary.tssrc/modularcore/media-picker/core/providers/cloudinary.ts
adapters/react/use-media-picker.tssrc/modularcore/media-picker/adapters/react/use-media-picker.ts
adapters/svelte/create-media-picker.svelte.tssrc/modularcore/media-picker/adapters/svelte/create-media-picker.svelte.ts
ui/react/MediaLibraryGrid.tsxsrc/modularcore/media-picker/ui/react/MediaLibraryGrid.tsx
ui/react/FolderSelect.tsxsrc/modularcore/media-picker/ui/react/FolderSelect.tsx
ui/react/MimeTypeFilter.tsxsrc/modularcore/media-picker/ui/react/MimeTypeFilter.tsx
ui/react/ImageEditor.tsxsrc/modularcore/media-picker/ui/react/ImageEditor.tsx
ui/react/BulkActionsBar.tsxsrc/modularcore/media-picker/ui/react/BulkActionsBar.tsx
ui/react/RemoteUrlLoader.tsxsrc/modularcore/media-picker/ui/react/RemoteUrlLoader.tsx
ui/svelte/MediaLibraryGrid.sveltesrc/modularcore/media-picker/ui/svelte/MediaLibraryGrid.svelte
ui/svelte/FolderSelect.sveltesrc/modularcore/media-picker/ui/svelte/FolderSelect.svelte
ui/svelte/MimeTypeFilter.sveltesrc/modularcore/media-picker/ui/svelte/MimeTypeFilter.svelte
ui/svelte/ImageEditor.sveltesrc/modularcore/media-picker/ui/svelte/ImageEditor.svelte
ui/svelte/BulkActionsBar.sveltesrc/modularcore/media-picker/ui/svelte/BulkActionsBar.svelte
ui/svelte/RemoteUrlLoader.sveltesrc/modularcore/media-picker/ui/svelte/RemoteUrlLoader.svelte
ui/react/tailwind/MediaLibraryGrid.tsxsrc/modularcore/media-picker/ui/react/tailwind/MediaLibraryGrid.tsx
ui/react/tailwind/FolderSelect.tsxsrc/modularcore/media-picker/ui/react/tailwind/FolderSelect.tsx
ui/react/tailwind/MimeTypeFilter.tsxsrc/modularcore/media-picker/ui/react/tailwind/MimeTypeFilter.tsx
ui/react/tailwind/ImageEditor.tsxsrc/modularcore/media-picker/ui/react/tailwind/ImageEditor.tsx
ui/react/tailwind/BulkActionsBar.tsxsrc/modularcore/media-picker/ui/react/tailwind/BulkActionsBar.tsx
ui/react/tailwind/RemoteUrlLoader.tsxsrc/modularcore/media-picker/ui/react/tailwind/RemoteUrlLoader.tsx
ui/svelte/tailwind/MediaLibraryGrid.sveltesrc/modularcore/media-picker/ui/svelte/tailwind/MediaLibraryGrid.svelte
ui/svelte/tailwind/FolderSelect.sveltesrc/modularcore/media-picker/ui/svelte/tailwind/FolderSelect.svelte
ui/svelte/tailwind/MimeTypeFilter.sveltesrc/modularcore/media-picker/ui/svelte/tailwind/MimeTypeFilter.svelte
ui/svelte/tailwind/ImageEditor.sveltesrc/modularcore/media-picker/ui/svelte/tailwind/ImageEditor.svelte
ui/svelte/tailwind/BulkActionsBar.sveltesrc/modularcore/media-picker/ui/svelte/tailwind/BulkActionsBar.svelte
ui/svelte/tailwind/RemoteUrlLoader.sveltesrc/modularcore/media-picker/ui/svelte/tailwind/RemoteUrlLoader.svelte
ui/react/shadcn/MediaLibraryGrid.tsxsrc/modularcore/media-picker/ui/react/shadcn/MediaLibraryGrid.tsx
ui/react/shadcn/FolderSelect.tsxsrc/modularcore/media-picker/ui/react/shadcn/FolderSelect.tsx
ui/react/shadcn/MimeTypeFilter.tsxsrc/modularcore/media-picker/ui/react/shadcn/MimeTypeFilter.tsx
ui/react/shadcn/ImageEditor.tsxsrc/modularcore/media-picker/ui/react/shadcn/ImageEditor.tsx
ui/react/shadcn/BulkActionsBar.tsxsrc/modularcore/media-picker/ui/react/shadcn/BulkActionsBar.tsx
ui/react/shadcn/RemoteUrlLoader.tsxsrc/modularcore/media-picker/ui/react/shadcn/RemoteUrlLoader.tsx
ui/svelte/shadcn/MediaLibraryGrid.sveltesrc/modularcore/media-picker/ui/svelte/shadcn/MediaLibraryGrid.svelte
ui/svelte/shadcn/FolderSelect.sveltesrc/modularcore/media-picker/ui/svelte/shadcn/FolderSelect.svelte
ui/svelte/shadcn/MimeTypeFilter.sveltesrc/modularcore/media-picker/ui/svelte/shadcn/MimeTypeFilter.svelte
ui/svelte/shadcn/ImageEditor.sveltesrc/modularcore/media-picker/ui/svelte/shadcn/ImageEditor.svelte
ui/svelte/shadcn/BulkActionsBar.sveltesrc/modularcore/media-picker/ui/svelte/shadcn/BulkActionsBar.svelte
ui/svelte/shadcn/RemoteUrlLoader.sveltesrc/modularcore/media-picker/ui/svelte/shadcn/RemoteUrlLoader.svelte
ui/react/vanilla/MediaLibraryGrid.tsxsrc/modularcore/media-picker/ui/react/vanilla/MediaLibraryGrid.tsx
ui/react/vanilla/FolderSelect.tsxsrc/modularcore/media-picker/ui/react/vanilla/FolderSelect.tsx
ui/react/vanilla/MimeTypeFilter.tsxsrc/modularcore/media-picker/ui/react/vanilla/MimeTypeFilter.tsx
ui/react/vanilla/ImageEditor.tsxsrc/modularcore/media-picker/ui/react/vanilla/ImageEditor.tsx
ui/react/vanilla/BulkActionsBar.tsxsrc/modularcore/media-picker/ui/react/vanilla/BulkActionsBar.tsx
ui/react/vanilla/RemoteUrlLoader.tsxsrc/modularcore/media-picker/ui/react/vanilla/RemoteUrlLoader.tsx
ui/svelte/vanilla/MediaLibraryGrid.sveltesrc/modularcore/media-picker/ui/svelte/vanilla/MediaLibraryGrid.svelte
ui/svelte/vanilla/FolderSelect.sveltesrc/modularcore/media-picker/ui/svelte/vanilla/FolderSelect.svelte
ui/svelte/vanilla/MimeTypeFilter.sveltesrc/modularcore/media-picker/ui/svelte/vanilla/MimeTypeFilter.svelte
ui/svelte/vanilla/ImageEditor.sveltesrc/modularcore/media-picker/ui/svelte/vanilla/ImageEditor.svelte
ui/svelte/vanilla/BulkActionsBar.sveltesrc/modularcore/media-picker/ui/svelte/vanilla/BulkActionsBar.svelte
ui/svelte/vanilla/RemoteUrlLoader.sveltesrc/modularcore/media-picker/ui/svelte/vanilla/RemoteUrlLoader.svelte
ui/shadcn-theme.csssrc/modularcore/media-picker/ui/shadcn-theme.css
ui/vanilla-styles.csssrc/modularcore/media-picker/ui/vanilla-styles.css

Instalación con CLI

modularcore add media-picker

Documentació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. StorageProvider (see core/provider.ts) is the
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 (core/providers/s3-compatible.ts,
core/providers/cloudinary.ts) obtains short-lived, scoped credentials (a presigned URL, a
signed 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.tsMediaPicker, 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 a
provider-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 provider
implementations that call into your signing backend.
- adapters/react, adapters/svelte — thin bindings over MediaPicker (Svelte adapter uses
Svelte 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 (see
apps/web/src/lib/demo-storage-provider.ts in this monorepo) that keeps blobs in a Map and
serves 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. Requires
Tailwind CSS v4 — copy ui/shadcn-theme.css into your own Tailwind entry CSS (see its header
comment for a bundler gotcha some setups hit when @importing it instead).
- ui/{react,svelte}/vanilla/ — plain CSS (ui/vanilla-styles.css, mc-* class prefix), no
framework dependency, works with or without a bundler.