GrFilePreview

  • core
  • data
  • API · 12 props

Shows a stored file as a tile: the image itself, or an icon for its type.

import { GrFilePreview } from '@feugene/granularity/components/GrFilePreview'

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

NeedTake
Pick a file and send it to a serverGrFileUpload
A file as the value of a form fieldGrFormFile
Open a picture full screenGrImageViewer
The avatar of a person or an entityGrAvatar
Show the contents of a file as textGrCodeBlock (the @feugene/granularity-code package)

Playground controls come from the API, not from hand

Stage

Loading…

0 changed· state is in the URL

Examples 3 each with a live demo and its code

  • Attachments of an expense report

    mime decides 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 that isPreviewableKind(fileKindOf(mime)) calls images, and makes only those clickable.

    Client visit, Lisbon · 14–16 Oct6 attachments · waiting for approval
    412 KB
    hotel-invoice.pdf
    186 KB
    398 KB
    per-diem.xlsx
    24 KB
    boarding-passes.zip
    1.1 MB
    bank-export.dat
    8 KB
  • Listing 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 in tile-size is the escape hatch past the scale. name becomes the alt of 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 says application/octet-stream — that is a placeholder with the name, not an empty frame.

    #T-1093 · Export to CSV fails
    MO
    Mina Ortiz · Customer · Mon 09:14The export fails with a red banner every time. Screenshot and the log attached.
    export.log
    DK
    Daniel Kim · Support · Mon 10:02Thanks — the first screenshot did not survive retention. Could you send it again?
    MO
    Mina Ortiz · Customer · Mon 10:20Here it is.

API generated from the package · cannot drift

Props

PropTypedefaultDescription
ariaLabelstring | undefinedundefinedThe 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" | undefinedundefinedThe 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`.
namestring | null | undefinedundefinedThe name of the file: the accessible name of the picture and the caption of the placeholder.
srcstring | null | undefinedundefinedThe address of the preview. If it is empty, the placeholder by type appears at once.
asstring | Component | undefinedundefinedA 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.
hrefstring | undefinedundefinedA link to the original — for non-pictures and for going past the viewer.
targetstring | undefinedundefinedWhere 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`.
relstring | undefinedundefinedA `rel` value of your own. Set — it cancels the automatic substitution.
clickableboolean | undefinedfalseThe tile is clickable and emits `click` — usually to open the viewer.
mimestring | null | undefinedundefinedThe MIME type. It decides whether this is a picture or a file.
tileSizeGrSizeWithPx | undefinedundefinedA 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.
ratioGrFilePreviewRatio | undefinedundefinedThe 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

EventSignatureDescription
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
import type { GrFileKind, GrFilePreviewProps, GrFilePreviewEmits } from '@feugene/granularity/components/GrFilePreview'

Filedist/types/components/GrFilePreview/config.d.ts

grFilePreviewConfigconst
const grFilePreviewConfig: import("@feugene/granum/contract").GranumComponentDescriptor<"GrFilePreview">;

Filedist/types/components/GrFilePreview/fileKindOf.d.ts

GrFileKindtype
type GrFileKind = 'image' | 'pdf' | 'document' | 'spreadsheet' | 'archive'
fileKindOffunction
function fileKindOf(mime: string | null | undefined): GrFileKind;
isPreviewableKindfunction
function isPreviewableKind(kind: GrFileKind): boolean;

Filedist/types/components/GrFilePreview/GrFilePreview.vue.d.ts

GrFilePreviewPropsinterface
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;
}
GrFilePreviewEmitsinterface
interface GrFilePreviewEmits {
    (e: 'click', event: MouseEvent): void;
}

Filedist/types/components/GrFilePreview/grFilePreviewStyles.d.ts

GrFilePreviewRatiotype
type GrFilePreviewRatio = '1:1' | '4:3' | '16:9';

Filedist/types/components/GrFilePreview/defaults.d.ts

GrFilePreviewConfigurablePropsinterface
interface GrFilePreviewConfigurableProps {
    tileSize: GrSizeWithPx;
    ratio: GrFilePreviewRatio;
    loading: 'lazy' | 'eager';
}

Filedist/types/components/GrFilePreview/safelist.d.ts

grFilePreviewSafelistconst
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;
  • src is 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.