Skip to content
View as Markdown

Attachment ​

Attachment is a reusable file/image attachment surface for ERP detail pages. It provides upload affordance (click or drag-and-drop onto the upload tile), optional helper text via uploadHint, image/file preview tiles, and per-item menu actions for download and delete.

Import ​

tsx
import { Attachment, useAttachment } from "@tailor-platform/app-shell";
import type { AttachmentItem, AttachmentOperation } from "@tailor-platform/app-shell";

Basic usage ​

Use useAttachment to manage upload/delete state locally, then flush operations to your backend on submit via applyChanges. Spread the returned props directly onto <Attachment />.

tsx
import { Attachment, Card, useAttachment } from "@tailor-platform/app-shell";
import type { AttachmentOperation } from "@tailor-platform/app-shell";

function ProductForm() {
  const { props, applyChanges } = useAttachment({
    initialItems: existingAttachments,
    accept: "image/*,.pdf",
  });

  async function handleSubmit() {
    await applyChanges(async (operations) => {
      for (const op of operations) {
        if (op.type === "upload") await uploadToServer(op.file);
        if (op.type === "delete") await deleteFromServer(op.item.id);
      }
    });
  }

  return (
    <Card.Root>
      <Card.Header title="Product images" description="PNG, JPG, or PDF — max 10 MB per file." />
      <Card.Content>
        <Attachment
          {...props}
          uploadLabel="Upload image"
          uploadHint="PNG, JPG, or PDF — max 10 MB per file."
          onDownload={(item) => console.log("download", item)}
        />
      </Card.Content>
    </Card.Root>
  );
}

Composition with Card ​

  • Use Card.Header for the section title and description.
  • Use Card.Root (and usually Card.Content) for border, radius, shadow, and padding around the attachment block.

useAttachment ​

useAttachment tracks pending uploads and deletes locally until applyChanges is called. It handles object URL creation/revocation for image previews automatically.

Options ​

OptionTypeDefaultDescription
initialItemsAttachmentItem[][]Existing items to populate on mount
acceptstring-Accepted file types passed to the file input
disabledbooleanfalseDisable upload and item actions

Return value ​

FieldTypeDescription
props{ items, onUpload, onDelete, accept, disabled }Spread directly onto <Attachment />
applyChanges(fn: (ops: AttachmentOperation[]) => Promise<void>) => Promise<void>Flush buffered operations to your backend; clears the buffer after fn resolves. If fn throws, the buffer is preserved so the call can be retried.
isApplyingbooleantrue while applyChanges is in progress. Use to show a loading indicator during form submission.

Upload ordering: newly uploaded files are prepended to items, so the most recent uploads appear first in the tile list.

AttachmentOperation ​

Represents a single pending change buffered by useAttachment. Passed as an array to the applyChanges callback.

ts
type AttachmentOperation =
  | { type: "upload"; file: File; item: AttachmentItem }
  | { type: "delete"; item: AttachmentItem };

AttachmentItem ​

ts
interface AttachmentItem {
  id: string;
  fileName: string;
  mimeType: string;
  previewUrl?: string;
}

Attachment props ​

PropTypeDefaultDescription
itemsAttachmentItem[][]Attachment list rendered as preview tiles
onUpload(files: File[]) => void(required)Called when files are selected or dropped on the upload tile
onDelete(item: AttachmentItem) => void(required)Called when Delete is chosen in a preview menu
onDownload(item: AttachmentItem) => void-Called when Download is chosen in a preview menu
uploadLabelstring"Click to upload"Primary label on the upload tile
uploadHintstring-Supporting text below the upload label
acceptstring-Accepted file types for hidden file input
disabledbooleanfalseDisables upload, drop, and hides per-item menu actions
classNamestring-Classes on the root (e.g. padding); component has no outer border or inset padding by default

Behavior ​

  • Layout: hidden file input, then the tile row—no built-in page inset.
  • Image items (mimeType starts with image/) render as 120×120 image thumbnails when previewUrl loads; otherwise a fallback tile.
  • Non-image items render as 120×120 file tiles with icon and wrapped filename.
  • Drag and drop applies only to the upload tile (dashed “click to upload” control), not the full attachment block. Use the file picker from the same tile for click-to-upload.
  • Disabled state (disabled={true}): the upload tile and drop target are hidden; per-item menu actions are also hidden.
  • Item actions are available through the preview menu (Download, Delete) when not disabled.

DOM ​

  • Root: data-slot="attachment"
  • Content wrapper: data-slot="attachment-content"