Attachment
PortedA 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.
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.
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>
);
}Links
With href, the name opens or downloads the file.
import { Attachment, AttachmentGroup } from "@etched/monet/attachment";
export function Links() {
return (
<AttachmentGroup aria-label="Ana Ruiz's files">
<Attachment name="resume-ana-ruiz.pdf" size={210_000} href="#resume-ana-ruiz.pdf" />
<Attachment name="take-home-solution.zip" size={1_300_000} href="#take-home-solution.zip" />
</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.
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
AttachmentGroupis alistand 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
| Prop | Type | Default | Description |
|---|---|---|---|
| children * | ReactNode | – | Attachments. Each goes in its own list item, so put them directly inside, not in a fragment. |
Attachment
| Prop | Type | Default | Description |
|---|---|---|---|
| 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. |
| size | number | – | The file's size in bytes, shown as "48 KB" or "2.4 MB". |
| type | string | – | The MIME type, such as application/pdf. It picks the icon; without it, the extension does. |
| href | string | – | 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. |
| removeLabel | string | "Remove" and the file's name | Names the remove button. Pass translated text. |