FilePreview
The composed preview layer is the one most apps will mount directly.
Each component pairs a loader with the matching view, owns its
useLoader call, and dispatches <StatusView kind="loading|error">
into the body slot while the data is in flight.
All composed previews follow the same prop contract:
{ src, metadata?, onSave? }. Hand them a URL (signed HTTP, blob:,
or data:) and they handle the rest.
Usage
Pick the right container for the file shape, or let FilePreview
dispatch by extension:
If you want a custom header / chrome, skip FilePreview and compose
the matching per-format container yourself:
The demos below pass the loaders small data: URIs so the page
exercises the full loader → view path end-to-end without a network
fixture. In a real app the URLs would be signed HTTP URLs from your
storage backend.
Media
ImagePreview
Wraps ImageView and surfaces a tiny metadata grid (format,
resolution, size, modified). No loader is involved — the <img>
element does its own decode. Pass a blob: URL via src (e.g.
URL.createObjectURL(file)) for just-uploaded files so they render
without a network round-trip.
VideoPreview
Wraps VideoView and surfaces duration + resolution once the
demuxer has the header. The browser streams chunks via Range
requests as the user scrubs, so previewing a multi-GB recording
never has to download up-front.
The demo above pulls a small clip from a public CDN. In a real host you pass the same signed URL your data layer hands out.
When the browser can't decode it
A <video> that cannot decode its input fails quietly — it paints
black and reports nothing, which reads to the user as "the preview is
broken" rather than "your browser can't play H.265." VideoPreview
turns all three shapes of that failure into a stated reason:
- The element raises a
MediaError— network, decode, or unsupported source. The numeric code picks the headline; the raw browser string (often an internal demuxer code) is appended verbatim under it. - Metadata parses but the picture is 0×0 — no
MediaErroris raised at all. This is the classic black-screen case: the container demuxed, the video codec did not. Audio may even play. - Nothing happens for
stallMs(15s default) — non-fatal, so the player stays mounted and only gets an advisory banner. A large file on a slow link looks identical to a stuck decode at second five.
Every message carries a remediation line — re-encode to H.264/AAC MP4
or VP9/Opus WebM — because in practice the fix is nearly always a
transcode on the producing side, not something the viewer can change.
Hosts composing VideoView directly get the same classification
through its onError callback, typed as VideoErrorInfo.
Data formats
CsvPreview
Calls loadCsv and feeds the result into TableView. The sub-bar
reports columns, total rows, the detected delimiter, and how many
bytes the range read actually pulled.
JsonPreview
Calls loadJson and renders the parsed value through JsonTreeView,
with a tree / raw toggle in the sub-bar. If parsing fails, the
toggle disables and the raw body shows underneath an inline error.
JsonlPreview
Calls loadJsonl and feeds the records into JsonlView. Long files
report an estimated line count derived from the bytes-per-line
ratio of the sliced chunk.
TextPreview
Calls loadText and feeds the body into TextView. Pass onSave
to grow an Edit button; the promise drives the saving state and any
thrown error surfaces inline. The demo's fake save fails on payloads
over 4 KB so the error state is reachable too.
Binary formats
These three previews each touch a small fixed slice of the file — Parquet's footer, the NPy header, MCAP's header + footer + summary — so the work is roughly constant regardless of file size. They drop into the same prop contract as the others; no demos here only because the docs site doesn't ship binary fixtures.
ParquetPreview
Reads the metadata + the first 1000 rows via hyparquet,
renders through TableView. Files larger than MAX_PARQUET_BYTES
(5 MB) trigger a too-large status because Parquet's metadata
layout makes "first N MB" meaningless — the schema lives at the end
of the file. Override via maxBytes on the loader if your host can
handle bigger.
NpyPreview
Range-fetches the first 256 bytes (and one more range if the header
dict claims to be longer), parses dtype / shape / element count, and
renders the result as a KeyValueView. Never touches the element
data — previewing a 50 GB array is the same fetch as a 1 KB array.
McapPreview
Reads the leading Header record, the trailing Footer, and the
summary section the footer points to. Renders top-level metadata
through KeyValueView and the per-channel stats through a nested
TableView (with the sub-bar hidden — the parent already owns one).
FilePreview dispatcher
The convenience entry-point — pass FileMetadata plus a src and
FilePreview reads metadata.ext, picks the matching container, and
mounts a stable PreviewHeader on top.
The switcher above remounts FilePreview against the same prop
contract every time the file kind changes — csv, json, jsonl,
md all route to their loader + view pair, svg goes through
ImagePreview (no loader), and an unknown extension like bin
falls back to the unsupported status.
Unsupported extensions are anything outside the bundled
IMAGE_EXTS / VIDEO_EXTS / TEXT_EXTS sets and the binary formats
(csv, parquet, json, jsonl, npy, mcap). The host can
either add another case upstream, or render its own
<StatusView kind="unsupported"> from the file metadata.
Props reference
Every per-format container accepts the same PreviewProps. The
FilePreview dispatcher requires metadata (so it can read
metadata.ext to pick the body); the per-format containers leave it
optional.
PreviewProps
| Prop | Type | Default | Description |
|---|---|---|---|
src | string | — | URL the renderer / loader reads from. Accepts HTTP URLs, blob: URLs (e.g. URL.createObjectURL of a just-uploaded file), or data: URLs. Caller refreshes signed URLs before expiry. Required. |
metadata | FileMetadata | — | File metadata used by PreviewHeader and the image / video info grids. Loaders read metadata.size to skip a HEAD round-trip. Optional on per-format containers; required on FilePreview. |
onSave | (text: string) => Promise<void> | — | Only used by TextPreview. When supplied, the sub-bar grows an Edit button. |
FileMetadata
| Field | Type | Description |
|---|---|---|
name | string | Required. The filename rendered as the bold second line in the header. |
ext | string | Extension (png, mp4, parquet, …). Drives icon selection and FilePreview's dispatch. |
path | string | Full file path. The parent directory is shown above the filename; falls back to / for bucket-root files. |
size | number | Raw bytes. Formatted via fmtSize on render. |
modified | Date | string | Last-modified timestamp. Dates are formatted as YYYY-MM-DD HH:mm; strings pass through. |
LoaderOpts
Pass-through options the loaders accept. Composed previews thread
{ size, signal } through automatically — set the others when you
call the loader directly.
| Field | Type | Description |
|---|---|---|
size | number | Skip the HEAD probe by passing the byte size you already have. |
signal | AbortSignal | Cancellation. useLoader plumbs one in for every fetch. |
maxBytes | number | Override the default slice cap (per loader). |
maxRows | number | Override the default row / line cap (CSV, Parquet, JSONL). |