Media Library
alphav0.2.0Drive-style media library — folders and files, lazy loading, drag-drop upload, drag-to-move, context menus, and multi-type preview.
Context
A composition-first CMS/asset surface. It owns the Drive shell — storage-quota bar, type-filter chips, folder-card row, thumbnail grid, upload pipeline, and a preview dispatcher — and delegates every actual file render to the shipped viewers (pdf-viewer, code-block, markdown-editor, video-player) plus a folder-navigation file-tree. Ships shadcn-style: one batteries-included <MediaLibrary> plus a headless <MediaLibraryRoot> + à-la-carte parts, so consumers drop what they don't need and tree-shake the unused viewers away.
Installation
pnpm dlx shadcn@latest init"registries": {
"@ilinxa": "https://ui.ilinxa.com/r/{name}.json"
}pnpm dlx shadcn@latest add @ilinxa/media-libraryAdd -fixtures for dummy data:
pnpm dlx shadcn@latest add @ilinxa/media-library-fixturesPreview
Media library
Folders
Brand & logos
2 items · Mar 24
Documents
2 items · Jun 6
Photos
3 items · Jun 9
Video
1 item · Jun 1
Files
favicon.png
hero-home.jpg
og-card.png
Drag-drop files to upload, drag a card onto a folder to move, right-click for actions, double-click a file to preview.
Demo source
Usage
When to use
Reach for MediaLibrary when you need a Google-Drive-style asset manager — folders + files, lazy loading, drag-drop upload, drag-to-move, right-click menus, and rich multi-type preview. It composes the shipped viewers (pdf-viewer, code-block, markdown-editor, video-player) and a folder file-tree — you supply the data + the backend callbacks.
Full (batteries-included)
import { MediaLibrary } from "@/components/media-library"
export function Example() {
return (
<MediaLibrary
nodes={tree}
storage={{ used: 12.8e9, total: 50e9 }}
onLoadChildren={(folderId) => fetchFolder(folderId)}
onUpload={(files, folderId, progress) => uploadToCdn(files, folderId, progress)}
onMove={(ids, target) => moveAssets(ids, target)}
onRename={(id, name) => renameAsset(id, name)}
onDelete={(ids) => deleteAssets(ids)}
onCreateFolder={(parentId, name) => createFolder(parentId, name)}
/>
)
}Lighter (drop the parts you don't need)
Like shadcn's sidebar, every part is an export. Compose <MediaLibraryRoot> with only the pieces you want — omitting <MediaLibraryLightbox /> / <MediaLibraryDetailsPane /> also tree-shakes the heavy viewers (pdf.js / CodeMirror / marked) out of your bundle.
import {
MediaLibraryRoot,
MediaLibraryBreadcrumbs,
MediaLibraryFolderRow,
MediaLibraryFileGrid,
} from "@/components/media-library"
<MediaLibraryRoot nodes={tree} onMove={moveAssets} preview={false}>
<MediaLibraryBreadcrumbs />
<MediaLibraryFolderRow />
<MediaLibraryFileGrid />
</MediaLibraryRoot>Just the preview
import { FilePreview } from "@/components/media-library"
// Anywhere — no library shell. Routes by MIME/extension to the right viewer.
<FilePreview node={{ id: "1", name: "readme.md", type: "file",
mimeType: "text/markdown", url: "/files/readme.md" }} />Notes
- Data: nodes are
MediaNode(a superset of the sharedFsNode) — give files aurl+mimeTypefor preview, andwidth/heightfor the dimension badge. - Text preview(code / JSON / txt / Markdown) is fetched from the node's
url; passresolveTextContentfor auth / signed URLs. - Capabilities are opt-in: omit
onUpload/onMove/onDelete/onRename/onCreateFolderand the matching buttons, menu items, and dnd disappear (read-only gallery). - Uploads call
onUploadonce per file; report that file's percent via theprogresscallback and resolve with the realMediaNode[]. - Cut → paste-into-folder moves items; copy/duplicate is deferred to v0.2.
Features
- Three tiers: full <MediaLibrary>, headless <MediaLibraryRoot> + parts, and standalone primitives (FilePreview, QuotaBar, FileCard)
- Multi-type preview dispatcher (image / video / pdf / code / json / text / markdown) in both a side pane and a full-screen lightbox with prev/next
- Lazy-loaded viewers (React.lazy) — dropping the preview parts drops pdf.js / CodeMirror / marked from the bundle
- Drag-drop upload (optimistic items + per-file progress + retry) via a consumer onUpload callback
- Drag-to-move files/folders with self/cycle drop validation; right-click context menus; cut → paste move
- Storage-quota bar, type-filter chips with live counts, breadcrumb navigation, lazy onLoadChildren
- Controlled/uncontrolled selection + current folder; imperative handle; full label i18n
- v0.1.2 — F-cross-13 path-b sweep: item context-menu trigger drops `asChild` (`className="contents"` wrapper; right-click bubbles from the card in both backends). Zero public-API change.