Default

Loading interactive example. Full source is available below.

View example source
Download source
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import * as React from 'react';
import { MessageBar } from "@coreai-ds/react-components";
import { MessageBarBody } from "@coreai-ds/react-components";
import { MessageBarTitle } from "@coreai-ds/react-components";
import { FileUpload } from "@coreai-ds/react-components";
import { Text } from "@coreai-ds/react-components";

export const Default = () => {
    const maxFileSize = 5 * 1024 * 1024;
    const acceptedFileTypes = {
        'application/pdf': ['.pdf'],
        'text/plain': ['.txt'],
        'image/png': ['.png'],
        'image/jpeg': ['.jpg', '.jpeg'],
    };
    const instructionsId = React.useId();
    const errorId = React.useId();
    const [names, setNames] = React.useState<string[]>([]);
    const [error, setError] = React.useState<string>();
    const styles = /* css */ `
      .file-upload-default-example {
        display: flex;
        flex-direction: column;
        max-width: 720px;
        padding: var(--spacingHorizontalL);
        row-gap: var(--spacingVerticalL);
      }
    `;
    return (<>
        <style>{styles}</style>
        <div className="file-upload-default-example">
          {error ? (<MessageBar id={errorId} intent="error">
              <MessageBarBody>
                <MessageBarTitle>Unable to add file</MessageBarTitle>
                {error}
              </MessageBarBody>
            </MessageBar>) : null}
          <FileUpload accept={acceptedFileTypes} ariaDescribedBy={error ? `${instructionsId} ${errorId}` : instructionsId} ariaLabel="Choose files" maxSize={maxFileSize} minSize={1} onDropAccepted={(added) => {
            setError(undefined);
            setNames(added.map((file) => file.name));
        }} onDropRejected={(rejections) => setError(rejections
            .map(({ file, errors }) => `${file.name}: ${errors.map(({ message }) => message).join(', ')}`)
            .join('; '))}>
            {({ isDragActive, isDragAccept, isDragReject }) => (<>
                <Text preset="body">
                  {isDragReject
                ? 'This file is not supported'
                : isDragAccept
                    ? 'Drop to add the file'
                    : isDragActive
                        ? 'Checking file…'
                        : 'Drag files here or click to browse'}
                </Text>
                <Text id={instructionsId} foreground={3} preset="bodyS">
                  PDF, TXT, PNG, or JPEG · Max 5 MB
                </Text>
              </>)}
          </FileUpload>
          {names.length > 0 ? <Text preset="bodyS">Selected: {names.join(', ')}</Text> : null}
        </div>
      </>);
};

export default Default;
API referenceGenerated CoreAI prop and type summaries

API

Generated CoreAI prop summaries, not replacement type declarations. Most native DOM/event and ARIA props are omitted. ? means optional; JSDoc shows defaults, deprecations, and conditional requirements. Defaults assume the standard CoreAI provider.

FileUpload API

interface FileUploadProps {
  accept?: Accept;

  /**
   * IDs of elements that describe the drop zone and its file input.
   */
  ariaDescribedBy?: string;

  /**
   * Localized accessible name for the drop zone and its file input.
   */
  ariaLabel: string;

  /**
   * Presentational content shown inside the drop zone, optionally based on its
   * current drag state. Do not include interactive controls such as buttons or
   * links because the root is already interactive.
   */
  children: ReactNode | ((state: FileUploadState) => ReactNode);

  className?: string;

  disabled?: boolean;

  maxFiles?: number;

  maxSize?: number;

  minSize?: number;

  onDrop?: (<T extends File>(acceptedFiles: T[], fileRejections: FileRejection[], event: DropEvent) => void);

  onDropAccepted?: (<T extends File>(files: T[], event: DropEvent) => void);

  onDropRejected?: ((fileRejections: FileRejection[], event: DropEvent) => void);

  /**
   * Allows getting a ref to the component instance.
   * Once the component unmounts, React will set `ref.current` to `null`
   * (or call the ref with `null` if you passed a callback ref).
   */
  ref?: LegacyRef<HTMLDivElement>;

  validator?: (<T extends File>(file: T) => FileError | readonly FileError[] | null);
}

Implementation

CoreAI 0.12.0; Fluent 9.74.9.

Setup and dependencies.

Import components from @coreai-ds/react-components. CoreAI guidance above takes precedence over API demonstration examples.

Custom Content

Loading interactive example. Full source is available below.

Download source
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import * as React from 'react';
import { MessageBar } from "@coreai-ds/react-components";
import { MessageBarBody } from "@coreai-ds/react-components";
import { MessageBarTitle } from "@coreai-ds/react-components";
import { FileUpload } from "@coreai-ds/react-components";
import { DocumentArrowUp24Regular } from "@fluentui/react-icons";
import { Text } from "@coreai-ds/react-components";

export const CustomDropZoneContent = () => {
    const maxFileSize = 5 * 1024 * 1024;
    const instructionsId = React.useId();
    const errorId = React.useId();
    const [selectedFile, setSelectedFile] = React.useState<File>();
    const [error, setError] = React.useState<string>();
    const styles = /* css */ `
      .file-upload-custom-content-example {
        display: flex;
        flex-direction: column;
        max-width: 720px;
        padding: var(--spacingHorizontalL);
        row-gap: var(--spacingVerticalL);
      }

      .file-upload-custom-content-example__drop-zone {
        align-items: center;
        background-color: var(--colorBrandBackground2);
        border: var(--strokeWidthThin) solid var(--colorBrandStroke1);
        min-height: 180px;
        padding: var(--spacingVerticalXXL);
      }

      .file-upload-custom-content-example__drop-zone:hover {
        background-color: var(--colorBrandBackground2Hover);
      }

      .file-upload-custom-content-example__drop-zone:active {
        background-color: var(--colorBrandBackground2Pressed);
      }

      .file-upload-custom-content-example__upload-icon {
        margin-bottom: var(--spacingVerticalXXS);
      }
    `;
    return (<>
        <style>{styles}</style>
        <div className="file-upload-custom-content-example">
          {error ? (<MessageBar id={errorId} intent="error">
              <MessageBarBody>
                <MessageBarTitle>Unable to add file</MessageBarTitle>
                {error}
              </MessageBarBody>
            </MessageBar>) : null}

          <FileUpload ariaLabel={selectedFile ? 'Replace knowledge file' : 'Select a knowledge file'} ariaDescribedBy={error ? `${instructionsId} ${errorId}` : instructionsId} accept={{
            'application/pdf': ['.pdf'],
            'text/plain': ['.txt'],
            'text/markdown': ['.md'],
        }} className="file-upload-custom-content-example__drop-zone" maxSize={maxFileSize} multiple={false} onDropAccepted={(added) => {
            setError(undefined);
            setSelectedFile(added[0]);
        }} onDropRejected={(rejections) => setError(rejections
            .map(({ file, errors }) => `${file.name}: ${errors.map(({ message }) => message).join(', ')}`)
            .join('; '))}>
            <DocumentArrowUp24Regular aria-hidden className="file-upload-custom-content-example__upload-icon"/>
            <Text preset="heading5">
              {selectedFile ? selectedFile.name : 'Select a knowledge file'}
            </Text>
            <Text foreground={3} preset="bodyS">
              {selectedFile
            ? 'Drop another file or click to replace this selection'
            : 'Drop a product guide or click to browse'}
            </Text>
            <Text foreground={3} preset="bodyS">
              PDF, TXT, or Markdown · Max 5 MB
            </Text>
          </FileUpload>
        </div>
      </>);
};

export default CustomDropZoneContent;

Disabled

Loading interactive example. Full source is available below.

Download source
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import * as React from 'react';
import { FileUpload } from "@coreai-ds/react-components";
import { Text } from "@coreai-ds/react-components";

export const Disabled = () => {
    const instructionsId = React.useId();
    const styles = /* css */ `
      .file-upload-disabled-example {
        display: flex;
        flex-direction: column;
        max-width: 720px;
        padding: var(--spacingHorizontalL);
        row-gap: var(--spacingVerticalL);
      }
    `;
    return (<>
        <style>{styles}</style>
        <FileUpload className="file-upload-disabled-example" ariaDescribedBy={instructionsId} ariaLabel="File upload unavailable" disabled>
          <Text foreground="disabled" preset="body">
            File upload is unavailable
          </Text>
          <Text id={instructionsId} foreground="disabled" preset="bodyS">
            Contact your administrator for access.
          </Text>
        </FileUpload>
      </>);
};

export default Disabled;

With File Grid

Loading interactive example. Full source is available below.

Download source
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import * as React from 'react';
import { Spinner } from "@coreai-ds/react-components";
import { Badge } from "@coreai-ds/react-components";
import { TableCellLayout } from "@coreai-ds/react-components";
import { Button } from "@coreai-ds/react-components";
import { ArrowClockwise20Regular } from "@fluentui/react-icons";
import { Delete20Regular } from "@fluentui/react-icons";
import { MessageBar } from "@coreai-ds/react-components";
import { MessageBarBody } from "@coreai-ds/react-components";
import { MessageBarTitle } from "@coreai-ds/react-components";
import { FileUpload } from "@coreai-ds/react-components";
import { Text } from "@coreai-ds/react-components";
import { DataGrid } from "@coreai-ds/react-components";
import { DataGridHeader } from "@coreai-ds/react-components";
import { DataGridRow } from "@coreai-ds/react-components";
import { DataGridHeaderCell } from "@coreai-ds/react-components";
import { DataGridBody } from "@coreai-ds/react-components";
import { DataGridCell } from "@coreai-ds/react-components";

export const WithFileGrid = () => {
    type UploadStatus = 'pending' | 'uploading' | 'uploaded' | 'failed';
    interface UploadItem {
        id: string;
        name: string;
        size: number;
        status: UploadStatus;
    }
    const maxFileSize = 5 * 1024 * 1024;
    const acceptedFileTypes = {
        'application/pdf': ['.pdf'],
        'text/plain': ['.txt'],
        'image/png': ['.png'],
        'image/jpeg': ['.jpg', '.jpeg'],
    };
    const initialFiles: UploadItem[] = [
        {
            id: 'product-guide.pdf',
            name: 'product-guide.pdf',
            size: 1842176,
            status: 'uploaded',
        },
    ];
    const createUploadItem = (file: File): UploadItem => ({
        id: `${file.name}-${file.lastModified}-${file.size}`,
        name: file.name,
        size: file.size,
        status: 'pending',
    });
    const formatFileSize = (bytes: number): string => {
        const megabytes = bytes / (1024 * 1024);
        return `${megabytes.toLocaleString(undefined, { maximumFractionDigits: 2 })} MB`;
    };
    const instructionsId = React.useId();
    const errorId = React.useId();
    const [files, setFiles] = React.useState<UploadItem[]>(initialFiles);
    const [error, setError] = React.useState<string>();
    const handleRetry = (file: UploadItem) => {
        setFiles((prev) => prev.map((item) => (item.id === file.id ? { ...item, status: 'uploading' } : item)));
        window.setTimeout(() => {
            setFiles((prev) => prev.map((item) => (item.id === file.id ? { ...item, status: 'uploaded' } : item)));
        }, 800);
    };
    const handleDelete = (file: UploadItem) => {
        setFiles((prev) => prev.filter((item) => item.id !== file.id));
    };
    const renderUploadStatus = (status: UploadStatus): React.ReactNode => {
        if (status === 'uploading') {
            return <Spinner as="span" label="Uploading" labelPosition="after" size="tiny"/>;
        }
        if (status === 'uploaded') {
            return (<Badge appearance="tint" color="success">
            Uploaded
          </Badge>);
        }
        if (status === 'failed') {
            return (<Badge appearance="tint" color="danger">
            Failed
          </Badge>);
        }
        return 'Pending';
    };
    const columns = [
        {
            columnId: 'name',
            compare: (a: UploadItem, b: UploadItem) => a.name.localeCompare(b.name),
            renderHeaderCell: () => 'Name',
            renderCell: (item: UploadItem) => <TableCellLayout truncate>{item.name}</TableCellLayout>,
        },
        {
            columnId: 'size',
            compare: (a: UploadItem, b: UploadItem) => a.size - b.size,
            renderHeaderCell: () => 'Size',
            renderCell: (item: UploadItem) => (<TableCellLayout>{formatFileSize(item.size)}</TableCellLayout>),
        },
        {
            columnId: 'status',
            compare: (a: UploadItem, b: UploadItem) => a.status.localeCompare(b.status),
            renderHeaderCell: () => 'Status',
            renderCell: (item: UploadItem) => (<TableCellLayout>{renderUploadStatus(item.status)}</TableCellLayout>),
        },
        {
            columnId: 'actions',
            compare: () => 0,
            renderHeaderCell: () => 'Actions',
            renderCell: (item: UploadItem) => (<TableCellLayout className="file-upload-grid-example__actions">
            {item.status === 'failed' ? (<Button appearance="subtle" aria-label={`Retry ${item.name}`} icon={<ArrowClockwise20Regular aria-hidden/>} onClick={() => handleRetry(item)}/>) : null}
            <Button appearance="subtle" aria-label={`Delete ${item.name}`} disabled={item.status === 'uploading'} icon={<Delete20Regular aria-hidden/>} onClick={() => handleDelete(item)}/>
          </TableCellLayout>),
        },
    ];
    const handleFilesAdded = (added: readonly File[]) => {
        setError(undefined);
        setFiles((prev) => {
            const next = [...prev];
            for (const file of added) {
                const item = createUploadItem(file);
                if (!next.some((existing) => existing.id === item.id)) {
                    next.push(item);
                }
            }
            return next;
        });
        // Simulate upload progress for newly added files.
        for (const file of added) {
            const id = createUploadItem(file).id;
            window.setTimeout(() => {
                setFiles((prev) => prev.map((item) => (item.id === id ? { ...item, status: 'uploading' } : item)));
            }, 200);
            window.setTimeout(() => {
                setFiles((prev) => prev.map((item) => item.id === id
                    ? {
                        ...item,
                        status: file.name.toLowerCase().includes('fail') ? 'failed' : 'uploaded',
                    }
                    : item));
            }, 1200);
        }
    };
    const styles = /* css */ `
    .file-upload-grid-example {
      display: flex;
      flex-direction: column;
      max-width: 720px;
      padding: var(--spacingHorizontalL);
      row-gap: var(--spacingVerticalL);
    }

    .file-upload-grid-example__actions {
      align-items: center;
      column-gap: var(--spacingHorizontalXXS);
      display: flex;
    }
  `;
    return (<>
        <style>{styles}</style>
        <div className="file-upload-grid-example">
          {error ? (<MessageBar id={errorId} intent="error">
              <MessageBarBody>
                <MessageBarTitle>Unable to add file</MessageBarTitle>
                {error}
              </MessageBarBody>
            </MessageBar>) : null}

          <FileUpload accept={acceptedFileTypes} ariaDescribedBy={error ? `${instructionsId} ${errorId}` : instructionsId} ariaLabel="Choose files" maxSize={maxFileSize} minSize={1} multiple onDropAccepted={handleFilesAdded} onDropRejected={(rejections) => setError(rejections
            .map(({ file, errors }) => `${file.name}: ${errors.map(({ message }) => message).join(', ')}`)
            .join('; '))}>
            <Text preset="body">Drag files here or click to browse</Text>
            <Text id={instructionsId} foreground={3} preset="bodyS">
              PDF, TXT, PNG, or JPEG · Max 5 MB
            </Text>
          </FileUpload>

          {files.length === 0 ? (<Text foreground={3} preset="bodyS">
              No files yet. Drop or browse to add some.
            </Text>) : (<DataGrid aria-label="Selected files" columns={columns} getRowId={(item) => item.id} items={files} sortable>
              <DataGridHeader>
                <DataGridRow>
                  {({ renderHeaderCell }) => (<DataGridHeaderCell>{renderHeaderCell()}</DataGridHeaderCell>)}
                </DataGridRow>
              </DataGridHeader>
              <DataGridBody<UploadItem>>
                {({ item, rowId }) => (<DataGridRow<UploadItem> key={rowId}>
                    {({ renderCell, columnId }) => (<DataGridCell focusMode={columnId === 'actions' ? 'group' : undefined}>
                        {renderCell(item)}
                      </DataGridCell>)}
                  </DataGridRow>)}
              </DataGridBody>
            </DataGrid>)}
        </div>
      </>);
};

export default WithFileGrid;