Skip to content

FileCard

WxFileCard is one file in a media library: a preview where there is a picture to show, a glyph where there is not, the name it goes by, and the few things that can be done to it.

A media library — drag across them, or pick one
excavator-front-view.jpg
excavator-front-view.jpg
hydraulics-diagram.png
hydraulics-diagram.png
pdf
service-manual-2026.pdf
xlsx
spare-parts.xlsx
docx
delivery-terms.docx
zip
site-survey.zip
mp4
walkaround.mp4
brand-mark.svg
brand-mark.svg
pptx
proposal-deck.pptx
sketch
field-notes.sketch
Nothing yet — double-click a name to rename it, too; the panel drops from the button that was pressed, and nothing in the grid moves. Selected: 1.
Sizes, and a card with nothing to do
thumbnail.png
thumbnail.png
pdf
report.pdf
gz
archive.tar.gz
docx
locked-by-policy.docx

Usage

vue
<script setup lang="ts">
import { WxFileCard } from '@webx-ui/core'
</script>

<template>
  <wx-file-card
    :name="file.name"
    :url="file.url"
    :thumbnail="file.thumbnail"
    renamable
    removable
    copyable
    @rename="save(file, $event)"
    @remove="destroy(file)"
  />
</template>

It does none of it

Renaming reports a name, deleting reports a wish, the edit action reports that somebody wants an editor — which is one line to answer:

ts
const edited = await openImageEditor({ src: file.url })

ImageEditor is the component behind it, and the demo above is wired to it. Nothing else happens to the file until the screen holding the cards says so — the card has no idea there is a server, which is what lets the same card sit in a library, in a picker, and in a form field.

The one exception is the clipboard. Copying a URL is finished the moment it happens, so the card does it and says what it did: copy with the URL, or copy-error when the browser refused — which it does outside a secure context, where there is no clipboard at all.

Picture or glyph

A picture is drawn from thumbnail, or from url when there is no thumbnail. What counts as a picture is the MIME type where there is one and the extension where there is not, and only the formats a browser will actually draw: a TIFF is an image and renders as a broken one, so it gets a glyph like any other file. A picture that fails to load falls back to its glyph too.

Everything else is an icon and the extension written underneath. The icons go by family — a .docx and an .odt are both a page of prose — and the line of text is what separates them, and what says anything at all about a .sketch.

An icon of your own

file-<extension> is the name for every one of them, so a .dwg gets its own drawing without a release of this library:

ts
import { registerIcons } from '@webx-ui/core'

registerIcons({ 'file-dwg': '<path d="…" />' })

The card asks the icon registry, so that is all it takes. :icon="…" overrides one card's glyph where the name is not the whole truth.

Renaming

renamable opens a small panel under the actions — from the rename action, or by double-clicking the name. Enter or Save commits; Escape, Cancel and a click on the page behind it all leave the name alone. The name is selected whole, extension and all: a rename is usually a new name.

It is a panel and not a field in place of the name for two reasons. Swapping a line of text for an input changes the height of the card, and a card in a grid changes the height of its row, so renaming one file made the whole library jump. And it drops from the button that was pressed, which is where the reader is already looking — not at the other end of the card.

A plain click on the name is not a rename — it belongs to whatever is choosing the file. That is also why the card is dismissed rather than saved when the panel is clicked away from: a panel dismissed is a panel dismissed.

Nothing is renamed by the card. @rename carries the new name, and the card goes on showing the old one until name is given the new one.

The name that does not fit

The name is one line, cut off with an ellipsis, and the full name is in a tooltip. The tooltip is only armed when the name is really cut off, which is measured rather than assumed — a tip that repeats a name already fully visible is noise, and how wide the card is decides that, not how long the name is.

The actions

PropWhat it offers
renamableThe name, in a panel under the button
editable@edit — "open this picture in an editor". Pictures only; there is nothing to crop in a .zip
copyableurl to the clipboard, with a tick for a moment afterwards
removable@remove, after a question unless :confirm-remove="false"

They sit over the preview, and where the pointer can hover they wait until it does. A touch screen never hovers, so there they simply stand — actions that wait for a hover that cannot happen are actions nobody can reach.

They also stay while any of their panels is up. The menu, the rename field and the question before a deletion are all teleported, so neither the pointer nor the focus is on the card while one of them is open, and the buttons would otherwise fade out from under it.

disabled takes all four away and stops the name being edited: a file a reader may look at and not touch.

Before deleting

confirmRemove is on: the delete action asks first, in a panel under the button, and @remove comes only when the question is answered. A file deleted from a grid of thumbnails is a file deleted by a misplaced click, and the thumbnails all look much alike — which is why the question names the file. removeConfirmText replaces it; :confirm-remove="false" skips it, for a screen that asks its own question or where the deletion can be undone.

Props

PropTypeDefaultDescription
namestringThe file name, extension and all
urlstringWhere the file is; what the copy action copies
thumbnailstringA smaller picture to draw instead of url
typestringMIME type; decides picture or glyph before the extension does
iconIconNameby extensionGlyph to draw instead
selectedbooleanfalseDraws the card as chosen
size'sm' | 'md' | 'lg''md'Glyph and text size
disabledbooleanfalseNo actions, no renaming
renamablebooleanfalseOffers renaming
editablebooleanfalseOffers the edit action, for pictures
removablebooleanfalseOffers deleting
confirmRemovebooleantrueAsks before deleting
removeConfirmTextstringDelete <name>?The question it asks
copyablebooleanfalseOffers copying the link
actionsMenubooleanfalseThe actions are a ··· menu at every width, never a row
renameLabelstring'Rename'Tooltip and accessible name
saveLabelstring'Save'The two buttons under the rename field
cancelLabelstring'Cancel'
editLabelstring'Edit picture'
removeLabelstring'Delete'
copyLabelstring'Copy link'
copiedLabelstring'Copied'Shown for a moment after a copy worked

Events: rename (string); edit; remove; copy (string); copy-error (unknown).

Slots: preview — with { picture }, for a video still or a player; actions — your own, after the card's; meta — under the name, for a size or a date.

Helpers

The three the card uses are exported, since a table of files wants the same answers:

ts
import { extensionOf, fileIconName, isPicture } from '@webx-ui/core'

extensionOf('photo.JPG?v=2') // 'jpg'
isPicture('report.pdf') // false
fileIconName('spare-parts.xlsx') // 'file-xlsx'

In a library

The card is an element like any other, so v-wx-select goes on it and SelectionArea does the rest — the box, the taps, the run with shift. The buttons on the card are buttons, so a drag that starts on one is the button's, not the box's.

Accessibility

Every action is a real button with an accessible name, whether or not a tooltip is showing. The preview is decorative when there is a glyph and carries the file name as its alt when there is a picture. Renaming is an input with the rename label on it, inside a panel that takes focus when it opens and gives it back when it closes, and the keys are the ones a file manager has taught everybody: Enter to save, Escape to leave it alone.

Released under the MIT License.