File input
Files the crew choose, drop, paste or photograph, checked before they are taken and uploaded with progress.
On this page
Code
import { FileInput, type FileUploadContext, type FileUploadResult } from "@bosunkit/components/file-input";
import type { ReactElement } from "react";
export const description: string =
"A PDF up to 100 MB, sent as it is added by an action that reports its progress; this one stores nothing.";
/** An upload that reports its progress over a second and a half and stores nothing. */
function pretendUpload(_file: File, context: FileUploadContext): Promise<FileUploadResult> {
return new Promise<FileUploadResult>((resolve: (result: FileUploadResult) => void): void => {
let sent: number = 0;
const timer: ReturnType<typeof setInterval> = setInterval((): void => {
sent += 0.2;
context.onProgress(sent);
if (sent >= 1) {
clearInterval(timer);
resolve({ status: "uploaded" });
}
}, 300);
context.signal.addEventListener("abort", (): void => {
clearInterval(timer);
});
});
}
export default function Showcase(): ReactElement {
return (
<div style={{ maxInlineSize: "32rem" }}>
<FileInput
appearance="outlined"
label="Insurance certificate"
accept={["application/pdf"]}
acceptLabel="a PDF"
maxBytes={100 * 1024 * 1024}
uploadAction={pretendUpload}
hint="The certificate the insurer sent, as a PDF."
/>
</div>
);
}
A file input takes files: a photo of a receipt, a crew member's certificate, an equipment manual. The crew choose them,
drop them on the field, paste them, or take a photo with a phone's camera (capture). Each is checked before it is
taken: its type against accept, its size against maxBytes, the field's count against maxFiles, and that its first
bytes are what its name says, since bosund stores the type the browser reports and a renamed file would otherwise pass.
A file it refuses stays in the list with what is wrong and what to do, until the crew remove it.
With an uploadAction each file is sent as it is added: the field shows its progress, a button to stop it, and a retry
when it fails, and it says when a file is uploaded. The field never fetches: the caller's action sends the file and
reports progress through the context it is given, and resolves rejected with bosund's reason when bosund refuses one.
Without an action the ready files go with the form under name. Images show as thumbnails read into data: URLs,
which the web UI's Content-Security-Policy allows where it refuses blob: ones. Outlined, the field's name sits on its
outline and names the choose button; plain, it takes its name from a FormField.
The area to drop on stacks a file mark, what to do, the field's limits and the Select File button, centered. Outlined, the field's own outline is the area's frame, and the files are listed under it, each with a framed button to remove it; a file the field refused, or whose upload failed, is framed in red.
Examples
Photos, several at once
Code
import { FileInput } from "@bosunkit/components/file-input";
import type { ReactElement } from "react";
export const description: string =
"Up to five photos of a repair, with a button that opens a phone's rear camera; images show as thumbnails.";
export default function Photos(): ReactElement {
return (
<div style={{ maxInlineSize: "32rem" }}>
<FileInput
appearance="outlined"
label="Photos of the repair"
accept={["image/*"]}
acceptLabel="a photo"
multiple
maxFiles={5}
maxBytes={100 * 1024 * 1024}
capture="environment"
/>
</div>
);
}
A button and the list
Code
import { FileInput } from "@bosunkit/components/file-input";
import type { ReactElement } from "react";
export const description: string = "The button variant, for a dialog with little room: no area to drop on.";
export default function ButtonOnly(): ReactElement {
return (
<div style={{ maxInlineSize: "32rem" }}>
<FileInput appearance="outlined" label="Receipt" variant="button" accept={["image/*", "application/pdf"]} />
</div>
);
}
A file it refuses
- windlass.pdf
Code
import { FileInput } from "@bosunkit/components/file-input";
import type { ReactElement } from "react";
export const description: string =
"A file bosund refused, kept in the list with its reason, beside one that uploaded and opens from its name.";
export default function Refused(): ReactElement {
return (
<div style={{ maxInlineSize: "32rem" }}>
<FileInput
appearance="outlined"
label="Manuals"
multiple
defaultValue={[
{
id: "1",
name: "volvo-penta-d2-40.pdf",
size: 4_718_592,
type: "application/pdf",
status: "uploaded",
url: "#",
},
{
id: "2",
name: "windlass.pdf",
size: 146_800_640,
type: "application/pdf",
status: "rejected",
error: "windlass.pdf is 140 MB, larger than the 100 MB this field takes. Choose a smaller file.",
},
]}
/>
</div>
);
}
In a form field
A passport-style photo for the crew list.
Code
import { FileInput } from "@bosunkit/components/file-input";
import { FormField } from "@bosunkit/components/form-field";
import type { ReactElement } from "react";
export const description: string = "Plain, inside a form field that names it; the file goes with the form.";
export default function Plain(): ReactElement {
return (
<div style={{ maxInlineSize: "32rem" }}>
<FormField label="Crew photo" hint="A passport-style photo for the crew list.">
<FileInput accept={["image/jpeg", "image/png"]} acceptLabel="a JPEG or a PNG" name="photo" />
</FormField>
</div>
);
}
Read-only
Code
import { FileInput } from "@bosunkit/components/file-input";
import type { ReactElement } from "react";
export const description: string = "Read-only: the stored files, each opening from its name, with nothing to change.";
export default function ReadOnly(): ReactElement {
return (
<div style={{ maxInlineSize: "32rem" }}>
<FileInput
appearance="outlined"
label="Certificates"
readOnly
defaultValue={[
{ id: "1", name: "stcw-basic-safety.pdf", size: 812_000, type: "application/pdf", status: "uploaded", url: "#" },
{ id: "2", name: "vhf-src.pdf", size: 455_000, type: "application/pdf", status: "uploaded", url: "#" },
]}
/>
</div>
);
}
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| value | readonly FileInputItem[] | undefined | – | The files, when the caller holds them; each carries its status. |
| defaultValue | readonly FileInputItem[] | undefined | – | The files it starts with when it holds them itself, such as ones bosund already stores. |
| onValueChange | ((items: readonly FileInputItem[]) => void) | undefined | – | Called with every file in the field whenever one is added, removed, or moves on in its upload. |
| accept | readonly string[] | undefined | [] | The types it takes, as the accept attribute lists them: image/*, application/pdf, .gpx. Any unless set. |
| acceptLabel | string | undefined | – | The types it takes in words, for its messages, translated by the caller ("a photo or a PDF"). |
| maxBytes | number | undefined | – | The largest file it takes, in bytes, written as bosund writes its limits: 100 MB is 100 * 1024 * 1024. |
| maxFiles | number | undefined | – | How many files it holds at most; one unless multiple. |
| multiple | boolean | undefined | false | Whether it takes more than one file. |
| uploadAction | ((file: File, context: FileUploadContext) => Promise<FileUploadResult>) | undefined | – | Sends a file to bosund as soon as it is added: resolve uploaded (with where it opens) or rejected with why,
report progress through the context, and stop when its signal aborts. A throw keeps the file, to be tried again.
Without it, files stay ready and go with the form under name. |
| checkContent | boolean | undefined | true | Whether a file's first bytes must match the type its name claims (a renamed text file is not a PNG); on unless set. |
| capture | "environment" | "user" | undefined | – | Adds a button that opens a phone's camera: environment the one facing away, user the one facing the crew. |
| variant | "dropzone" | "button" | undefined | "dropzone" | dropzone draws an area to drop files on; button only the button and the list. dropzone unless set. |
| appearance | "plain" | "outlined" | undefined | "plain" | plain for a FormField; outlined draws the outlined field with its name on the edge. |
| label | string | undefined | – | The field's name on the outline, with appearance="outlined"; omit it inside a FormField. |
| hint | ReactNode | – | Text under the outlined field; inside a FormField, use its hint. |
| error | string | undefined | – | What is wrong with the files as a whole, from the caller ("Add the insurance certificate"). |
| required | boolean | undefined | – | |
| disabled | boolean | undefined | – | |
| readOnly | boolean | undefined | – | Lists the files without a way to add, remove or retry them. |
| name | string | undefined | – | Submits the ready files with a form under this name, when there is no uploadAction. |
| previews | boolean | undefined | true | Whether images show as thumbnails; on unless set. Only images up to 5 MiB are read for one. |
| className | string | undefined | – | Classes added to the field's root. |
| testId | string | undefined | – | data-testid on the field. |
| ref | Ref<HTMLInputElement> | undefined | – | The file picker element. |
| motion | MotionProp | undefined | – | How the outlined field's error line moves; see OutlinedField. |
Related
- Signature input: a signature drawn on a pad, kept as SVG.
- Outlined field: the outline the field draws in.