Attachment

File row with upload states, built on Item.

A file, with its upload life.

A file in a chat composer or an upload list is the same row shape Item already draws — media, a name, a size, a remove button — so this is that row, not a second one. What it adds is the part Item has no opinion on: the file's state. A file is picked, then uploading, then processing, then done or failed, and each has to look different or the reader cannot tell a stuck upload from a finished one.

Installation

Attachment ships with the library — no separate install.

import { Attachment, FileIcon, ImageIcon, XIcon, Button } from 'panelui-native';

Or copy the source into your project, to own and edit it:

npx panelui-cli@latest add attachment

Usage

<Attachment state="uploading">
  <Attachment.Media>
    <FileIcon />
  </Attachment.Media>
  <Attachment.Content>
    <Attachment.Title>report.pdf</Attachment.Title>
    <Attachment.Description>PDF · 2.4 MB</Attachment.Description>
  </Attachment.Content>
  <Attachment.Actions>
    <Attachment.Action accessibilityLabel="Remove report.pdf">
      <XIcon size={16} />
    </Attachment.Action>
  </Attachment.Actions>
</Attachment>

Composition

<Attachment>
  <Attachment.Group>…</Attachment.Group>
  <Attachment.Media>…</Attachment.Media>
  <Attachment.Content>…</Attachment.Content>
  <Attachment.Title>…</Attachment.Title>
  <Attachment.Description>…</Attachment.Description>
  <Attachment.Actions>…</Attachment.Actions>
  <Attachment.Action>…</Attachment.Action>
</Attachment>
  • Attachment.Group — A row or column of attachments — the stack Item.Group provides.
  • Attachment.Media — Leading slot: a file-type icon tile, or an image thumbnail.
  • Attachment.Content — The name-and-description column.
  • Attachment.Title — The file name. Shimmers while uploading or processing.
  • Attachment.Description — File type, size or status. Tints destructive on error.
  • Attachment.Actions — Trailing slot for the action buttons.
  • Attachment.Action — One icon button — remove, retry, download. Its label is required.

Examples

A finished file

The default state is done. It is an Item underneath, so the media, sizing and pressable behaviour are the ones you already know.

<Attachment>
  <Attachment.Media>
    <FileIcon size={18} />
  </Attachment.Media>
  <Attachment.Content>
    <Attachment.Title>sales-dashboard.pdf</Attachment.Title>
    <Attachment.Description>PDF · 2.4 MB</Attachment.Description>
  </Attachment.Content>
  <Attachment.Actions>
    <Attachment.Action accessibilityLabel="Remove sales-dashboard.pdf">
      <XIcon size={16} />
    </Attachment.Action>
  </Attachment.Actions>
</Attachment>

Upload states

While uploading or processing the name shimmers — the same sweep the rest of the library uses for in-progress work. error tints the row destructive, which carries the failure without relying on an icon nobody reads.

<Attachment state="uploading">…</Attachment>
<Attachment state="processing">…</Attachment>
<Attachment state="error">
  <Attachment.Content>
    <Attachment.Title>report.pdf</Attachment.Title>
    <Attachment.Description>Upload failed — tap to retry</Attachment.Description>
  </Attachment.Content>
</Attachment>

A live upload

Pass progress (0–1) while uploading and a thin bar rides the row's bottom edge, without shifting the layout.

const [progress, setProgress] = useState(0);

<Attachment state={running ? "uploading" : "done"} progress={progress}>
  <Attachment.Media>
    <ImageIcon size={18} />
  </Attachment.Media>
  <Attachment.Content>
    <Attachment.Title>screenshot.png</Attachment.Title>
    <Attachment.Description>Uploading — {Math.round(progress * 100)}%</Attachment.Description>
  </Attachment.Content>
</Attachment>

A group of thumbnails

Attachment.Group lays out a horizontal row; set each attachment to orientation="vertical" so it stacks into a thumbnail card.

<Attachment.Group orientation="horizontal">
  {files.map((file) => (
    <Attachment key={file.name} orientation="vertical" className="w-32">
      <Attachment.Media variant="icon">
        <ImageIcon size={18} />
      </Attachment.Media>
      <Attachment.Content>
        <Attachment.Title className="text-sm">{file.name}</Attachment.Title>
        <Attachment.Description>Image</Attachment.Description>
      </Attachment.Content>
    </Attachment>
  ))}
</Attachment.Group>

API Reference

Attachment

PropTypeDefaultDescription
classNamestring
stateAttachmentStateWhere the file is in its life. uploading/processing shimmer the name; error tints the row destructive. Default done.
sizeAttachmentSizeRow density, passed through to the underlying Item.
orientation'horizontal' | 'vertical'Stack the row into a card — for a horizontal group of thumbnails.
progressnumberUpload progress 0–1. Draws a thin bar along the bottom while busy.
disabledboolean

Attachment.Group

PropTypeDefaultDescription
classNamestring
orientation'horizontal' | 'vertical'

Attachment.Media

PropTypeDefaultDescription
classNamestring
variant'default' | 'icon' | 'image'icon frames it in a tile; image clips it for a thumbnail.

Attachment.Content

PropTypeDefaultDescription
classNamestring

Attachment.Title

PropTypeDefaultDescription
classNamestring

Attachment.Description

PropTypeDefaultDescription
classNamestring

Attachment.Actions

PropTypeDefaultDescription
classNamestring

Attachment.Action

PropTypeDefaultDescription
classNamestring
accessibilityLabelstring

Every part also accepts the underlying React Native props (ViewProps or TextProps) and a className for Tailwind utilities.

Notes

Attachment is a thin layer over Item — every part maps to an Item part, so size, orientation and the pressable behaviour are Item's, documented there. What Attachment owns is state: the shimmer on the title while busy, the destructive tint on error, and the progress bar.

An icon action needs a label. Attachment.Action requires accessibilityLabel — an X on its own tells a screen reader nothing about what it removes.

On this page