Monet
@etched/monet 0.0.1

Attachment

Ported

A file as a chip: its type icon, its name (a link with href), its size, and a remove button with onRemove. Native <div> attributes and ref pass through.

run-1284.log48 KB
import { Attachment } from "@etched/monet/attachment";

export function Preview() {
  return <Attachment name="run-1284.log" size={48_213} />;
}

Installation

import { Attachment, AttachmentGroup } from "@etched/monet/attachment";

Examples

File types

The icon follows the MIME type (type), or the extension without one. A long name ends in an ellipsis before its extension. In an AttachmentGroup, the chips wrap, and screen readers hear a list and its count.

  • board-photo.jpg2.3 MB
  • thermal-soak-lot-7801.csv95 KB
  • run-1284-logs.zip12.2 MB
  • operator-notes.txt1 KB
  • bring-up-checklist.key625 KB
  • SH-24A-0193 bring-up report, revision C with the lab's notes.pdf178 KB
import { Attachment, AttachmentGroup } from "@etched/monet/attachment";

export function FileTypes() {
  return (
    <AttachmentGroup aria-label="Run files">
      <Attachment name="board-photo.jpg" type="image/jpeg" size={2_450_000} />
      <Attachment name="thermal-soak-lot-7801.csv" size={96_400} />
      <Attachment name="run-1284-logs.zip" size={12_800_000} />
      <Attachment name="operator-notes.txt" size={800} />
      <Attachment name="bring-up-checklist.key" size={640_000} />
      <Attachment name="SH-24A-0193 bring-up report, revision C with the lab's notes.pdf" size={182_000} />
    </AttachmentGroup>
  );
}

Removable

onRemove shows a remove button. Focus then moves to the next file's remove button, or the one before, so it is never lost in the list.

  • thermal-soak-lot-7801.csv95 KB
  • board-photo.jpg2.3 MB
  • run-1284.log48 KB
import { useState } from "react";
import { Attachment, AttachmentGroup } from "@etched/monet/attachment";

const picked = [
  { name: "thermal-soak-lot-7801.csv", size: 96_400 },
  { name: "board-photo.jpg", size: 2_450_000 },
  { name: "run-1284.log", size: 48_213 },
];

export function Removable() {
  const [files, setFiles] = useState(picked);

  return (
    <AttachmentGroup aria-label="Files to send">
      {files.map((file) => (
        <Attachment key={file.name} {...file} onRemove={() => setFiles((all) => all.filter((other) => other !== file))} />
      ))}
    </AttachmentGroup>
  );
}

Accessibility

  • An AttachmentGroup is a list and each file is a list item, so screen readers hear the count. The type icon is hidden.
  • With href, the name is a link, named by the full file name. The size is plain text after the name.
  • The remove button is named by removeLabel ("Remove" and the file's name). In a group, focus then moves to the next file's remove button, or the one before.
  • Name the group with aria-label, such as "Files to send". After the last file is removed, move focus, for example to the composer's field.

API reference

AttachmentGroup

PropTypeDefaultDescription
children *ReactNode–Attachments. Each goes in its own list item, so put them directly inside, not in a fragment.

Attachment

PropTypeDefaultDescription
name *string–The file's name, with its extension. A long name ends in an ellipsis before its extension, so the extension stays in view.
sizenumber–The file's size in bytes, shown as "48 KB" or "2.4 MB".
typestring–The MIME type, such as application/pdf. It picks the icon; without it, the extension does.
hrefstring–Where the file opens or downloads: the name becomes a link.
onRemove() => void–Shows a remove button. In an AttachmentGroup, focus then moves to the next file's remove button, or the one before. After the last one, the app moves focus, for example to the composer's field.
removeLabelstring"Remove" and the file's nameNames the remove button. Pass translated text.