GrFilePreview
Shows a stored file as a tile: the image itself, or an icon for its type.
Overview
When to take it
- a feed of attachments — receipts, contracts, exports: the set holds pictures and documents mixed together, and one rule will not show them both;
- the type of the file is not known in advance — the controller returns the entries
without a filter, and an
<img>on a PDF draws a broken icon; - the preview opens a viewer — the tile emits
click, and the consumer shows the window; - there are a dozen tiles on the page — lazy loading and an aspect ratio that holds the space are already inside.
When to take something else
| Need | Take |
|---|---|
| Pick a file and send it to a server | GrFileUpload |
| A file as the value of a form field | GrFormFile |
| Open a picture full screen | GrImageViewer |
| The avatar of a person or an entity | GrAvatar |
| Show the contents of a file as text | GrCodeBlock (the @feugene/granularity-code package) |
Playground controls come from the API, not from hand
Loading…
0 changed· state is in the URL
<GrFilePreview>Label</GrFilePreview>Examples 3 each with a live demo and its code
Attachments of an expense report
mimedecides the tile: receipts are photos, the invoice is a PDF, the per diem a table, the boarding passes an archive, and a type the server does not know gets a placeholder rather than a hole. The tile opens no viewer — the page sends to GrImageViewer exactly the files thatisPreviewableKind(fileKindOf(mime))calls images, and makes only thoseclickable.Client visit, Lisbon · 14–16 Oct6 attachments · waiting for approval 412 KB186 KB398 KB24 KB1.1 MB8 KBListing photos in one shape
Agents upload landscape and portrait shots mixed;
ratio="4:3"gives them one shape, so the row lines up, and a number intile-sizeis the escape hatch past the scale.namebecomes thealtof each photo — the tile does not invent a description — and the images load lazily, holding their place.2-room flat in Alfama · €1,450 a monthRua dos Remédios 42, 3rd floor · 64 m² A preview that no longer opens
In a support thread
tile-size="sm"fits the message. The first screenshot was purged by retention: its address fails, and the tile draws «the image did not open» instead of a broken picture. The log came through a gateway that only saysapplication/octet-stream— that is a placeholder with the name, not an empty frame.#T-1093 · Export to CSV fails MOMina Ortiz · Customer · Mon 09:14The export fails with a red banner every time. Screenshot and the log attached.DKDaniel Kim · Support · Mon 10:02Thanks — the first screenshot did not survive retention. Could you send it again?MOMina Ortiz · Customer · Mon 10:20Here it is.
API generated from the package · cannot drift
Props
| Prop | Type | default | Description |
|---|---|---|---|
ariaLabel | string | undefined | undefined | The accessible name of an interactive tile. Unset — the name comes from the content: the `alt` of the picture or the caption of the placeholder. |
loading | "lazy" | "eager" | undefined | undefined | The native `loading` of the picture: `lazy` loads a preview near the edge of the viewport rather than all the tiles of a feed at once. Unset — from `GrConfigProvider`, otherwise `lazy`. |
name | string | null | undefined | undefined | The name of the file: the accessible name of the picture and the caption of the placeholder. |
src | string | null | undefined | undefined | The address of the preview. If it is empty, the placeholder by type appears at once. |
as | string | Component | undefined | undefined | A root tag of your own: a link component (`RouterLink`, Inertia’s `Link`) or a tag for the sake of the page markup (`article`, `figure`). Stronger than `href`. A non-interactive tag is only a replacement for `div`: no cursor, no focus ring. |
href | string | undefined | undefined | A link to the original — for non-pictures and for going past the viewer. |
target | string | undefined | undefined | Where to open the link. Declared as a prop rather than left to fallthrough: otherwise `_blank` would go onto the `<a>` past the component, and there would be nowhere to put in the protective `rel`. |
rel | string | undefined | undefined | A `rel` value of your own. Set — it cancels the automatic substitution. |
clickable | boolean | undefined | false | The tile is clickable and emits `click` — usually to open the viewer. |
mime | string | null | undefined | undefined | The MIME type. It decides whether this is a picture or a file. |
tileSize | GrSizeWithPx | undefined | undefined | A step of the canonical scale or an arbitrary width in pixels. A number is an escape hatch, like the diameter in GrAvatar: a 96px tile in a feed of attachments does not fit into four steps. |
ratio | GrFilePreviewRatio | undefined | undefined | The proportions of the tile: they hold the space until loading, and the picture is cropped to them (`object-cover`). Unset — from `GrConfigProvider`, otherwise `1:1`. |
Events
| Event | Signature | Description |
|---|---|---|
click | (event: "click", event: MouseEvent): void | — |
Types
Every exported type of the subpath, grouped by the file it lives in. Names, generics and bodies come from the package’s declaration files and cannot drift.
import type { GrFileKind, GrFilePreviewProps, GrFilePreviewEmits } from '@feugene/granularity/components/GrFilePreview'Filedist/types/components/GrFilePreview/config.d.ts
const grFilePreviewConfig: import("@feugene/granum/contract").GranumComponentDescriptor<"GrFilePreview">;Filedist/types/components/GrFilePreview/fileKindOf.d.ts
type GrFileKind = 'image' | 'pdf' | 'document' | 'spreadsheet' | 'archive'function fileKindOf(mime: string | null | undefined): GrFileKind;function isPreviewableKind(kind: GrFileKind): boolean;Filedist/types/components/GrFilePreview/GrFilePreview.vue.d.ts
interface GrFilePreviewProps {
src?: string | null;
mime?: string | null;
name?: string | null;
tileSize?: GrSizeWithPx;
ratio?: GrFilePreviewRatio;
href?: string;
target?: string;
rel?: string;
as?: string | Component;
clickable?: boolean;
loading?: 'lazy' | 'eager';
ariaLabel?: string;
}interface GrFilePreviewEmits {
(e: 'click', event: MouseEvent): void;
}Filedist/types/components/GrFilePreview/grFilePreviewStyles.d.ts
type GrFilePreviewRatio = '1:1' | '4:3' | '16:9';Filedist/types/components/GrFilePreview/defaults.d.ts
interface GrFilePreviewConfigurableProps {
tileSize: GrSizeWithPx;
ratio: GrFilePreviewRatio;
loading: 'lazy' | 'eager';
}Filedist/types/components/GrFilePreview/safelist.d.ts
const grFilePreviewSafelist: string[];Usage from the library prose
The type is decided by mime, not by the extension
The extension lies: a .dat on an export, a .pdf on a renamed archive. The backend
returns the real type — that is what is taken.
There are six kinds: a picture, a PDF, a document, a spreadsheet, an archive and the
unrecognised. A picture is the only one drawn with an <img>; the rest get an icon and a
caption. An empty mime, application/octet-stream and an unknown type give a
placeholder rather than emptiness: “there is no type” is an ordinary state of a row in a
database, not a reason to show a hole.
text/csv is classified as a spreadsheet rather than as text: it is opened as a
spreadsheet.
The classification is public: fileKindOf(mime) and isPreviewableKind(kind) are
given away by the package. They are needed outside for exactly the reason that the tile
does not open the viewer (see “Limits”): deciding which files to hand to
GrImageViewer falls to the consumer — and without those functions
they write a mime.startsWith('image/') of their own, which diverges from the tile at the
very first new type.
A placeholder instead of a broken picture
Three paths lead to one and the same placeholder, and that is deliberate:
- the type is not a picture;
srcis empty;- the loading failed (
onerror) — the preview may have vanished from the disk.
In the last case the sign is different — “the image did not open” rather than “this is a
file”: the difference between “a file of this kind” and “there was a picture but it did
not arrive” is visible at once. A new src does not inherit the error.
alt is not invented
If name is set, it becomes the alt. If it is not, the picture is decorative
(alt=""), because a description invented by the component will be read by a screen
reader as a fact, and that is worse than an empty one.
On a placeholder the name is printed as a caption under the icon, and the icon itself is
hidden from a screen reader: the text has already said everything.
An interactive tile takes its name from the content — the alt of the picture or the
caption. If the content is nameless, the name is set with ariaLabel: a button without a
name is empty for a screen reader.
A tile is clickable only when it has been asked to be
Without clickable and href it is a picture, not a control: the tile takes no Tab stop
and gets no cursor — an empty stop is worse than none.
The order in which the tag is chosen is as → <a href> → <button clickable> →
<div>, as in GrCard and GrLink. A link component (Inertia’s Link, RouterLink)
receives href; a string tag other than a does not.
as names a tag, not a behaviour. as="article" or as="figure" changes the markup of
the page and nothing more: the classes are the same as with a <div>, there is no cursor and
no focus ring, and no generic name is imposed on the tile — link-name does not apply to an
<article>. What makes a tile a control is clickable, href and a link component. An
action hung on a tag outside the tab order is clicked with the mouse and warns in a dev
build.
The size and the proportions
tileSize is a step of the canonical scale or a number: a 96px tile in a feed of
attachments does not fit into four steps. The numeric escape hatch is here for the same
reason as the diameter in GrAvatar.
ratio holds the space until the loading finishes. Without it a row of tiles jumps when
the pictures arrive out of order.
The space is held by a skeleton, not by emptiness
While the picture is on its way, the tile shows a skeleton. It has three states — “loading”, “ready”, “did not open” — and without the middle one a feed of attachments lies: an empty cell reads as “the file has no preview”, although the request is still in flight. Across two dozen tiles arriving out of order the difference is visible at once.
The picture stays in the tree in the process and simply waits invisibly: take it out of there and the browser will not start the loading, and the “loading” state will never end.
Limits
The component does not load files, does not open the viewer itself and does not
generate previews for PDFs: rendering the first page of a document in the browser is the
job of a separate library, and its weight is out of proportion to a tile. If a thumbnail
of a PDF is needed, prepare it on the server and pass it in src as a picture.
Props in this example