Attachment
A file inside a message, with its preview, its metadata, its upload state and its own actions.
Installation
uvx django_shadcn@latest add attachment
Usage
<c-attachment>
<c-attachment.media variant="image">
<img src="{{ file.url }}" alt="">
</c-attachment.media>
<c-attachment.content>
<c-attachment.title>{{ file.name }}</c-attachment.title>
<c-attachment.description>{{ file.type }} · {{ file.size }}</c-attachment.description>
</c-attachment.content>
<c-attachment.actions>
<c-attachment.action aria-label="Remove">
<c-icon name="x" />
</c-attachment.action>
</c-attachment.actions>
</c-attachment>
The card is presentational. Nothing here uploads a file or tracks progress — you
set state from whatever already knows, and the card follows.
States
Five, and each one changes more than a label: idle dashes the border,
uploading and processing shimmer the title, error turns the border and
the media red.
<c-attachment state="uploading">...</c-attachment>
<c-attachment state="error">...</c-attachment>
The shimmer on the title is the shimmer utility from input.css. It stops
by itself when the reader has asked for reduced motion.
Sizes and orientation
size takes default, sm and xs. orientation="vertical" turns the card
into a tile, with the media on top and the actions floating over its corner.
<c-attachment size="sm">...</c-attachment>
<c-attachment size="xs">...</c-attachment>
<c-attachment orientation="vertical">...</c-attachment>
Examples
A card that opens the file
trigger covers the whole card, so the card itself is the target. It sits
behind the actions on purpose: the remove button keeps its own click and its
own tab stop, and neither swallows the other.
<c-attachment>
...
<c-attachment.actions>
<c-attachment.action aria-label="Remove">
<c-icon name="x" />
</c-attachment.action>
</c-attachment.actions>
<c-attachment.trigger tag="a" href="{{ file.url }}" aria-label="Open {{ file.name }}"></c-attachment.trigger>
</c-attachment>
The trigger has no text of its own, so it needs an aria-label. Without one it
is an unlabelled control covering the entire card.
Leave tag off and it renders a button with type="button", for a card that
opens a preview instead of navigating.
A row of files
group scrolls sideways, snaps each card into place and fades the edge you have
scrolled away from.
<c-attachment.group>
<c-attachment orientation="vertical">...</c-attachment>
<c-attachment orientation="vertical">...</c-attachment>
</c-attachment.group>
The scroll, the snapping and the fade are all CSS. There is no JavaScript in this component.