Amplify UI

Storage Browser for Amazon S3

The StorageBrowser component gives your end users a simple, visual interface for working with data stored in S3.

Home

import * as React from 'react';
import { StorageBrowser } from './MockStorageBrowser';

export default function Example() {
  return <StorageBrowser />;
}

Overview

Storage Browser for Amazon S3 is an open source component that you can add to your web applications to provide your end users with a simple, graphical interface to work with data stored in Amazon S3. With Storage Browser for S3, you can provide authorized end users access to easily browse, download, upload, copy, and delete data in S3 directly from your own applications.

End users work with S3 locations within the Storage Browser interface. Locations are S3 buckets or prefixes that you authorize end users to access using Amazon S3 Access Grants or Identity and Access Management (IAM) policies, depending on your use case. When the Storage Browser component is first rendered, it will show the LocationsView which displays only the locations you have granted them access to. Once an end user has selected a location, they can browse the S3 bucket or prefix, and all the data contained further down the S3 resource path, but they cannot browse buckets or prefixes higher up the S3 resource path.

Setup and Authentication

The StorageBrowser component is the first Amplify UI component to support different authentication methods other than Amplify Auth, which is based on Amazon Cognito and IAM policies.

In order to show S3 locations and their contents to end users, you first need to set up your preferred authentication and authorization methods. There are 3 ways you can set up authentication/authorization with the storage browser component:

  1. Amplify auth: If you are already using Amplify then this option lets you get started the fastest. It uses Amazon Cognito for authentication and IAM policies for authorization and access. And by using Amplify Gen 2, the access rules for users and groups can be customized. This option is ideal for use cases when you want to connect your customers and third-party partners to your data in S3.
  2. AWS IAM Identity Center and S3 Access Grants: We recommend this option if you want to grant access on a per-S3-prefix basis to both IAM principals and directly to users or groups from your corporate directory. With S3 Access Grants capabilities, applications can request data from Amazon S3 on behalf of the current authenticated user. This means your applications no longer need to first map the user to an IAM principal. And when you use S3 Access Grants with IAM Identity Center trusted identity propagation, each AWS CloudTrail data event for S3 references the end user identity that accessed your data. This option is ideal for use cases when you want to connect your entire workforce with your data in S3.
  3. Customer managed auth: We recommend this option if you have your own identity and authorization service for authenticating and authorizing users in your application. To use this option, you will need to provide the list of S3 locations to display to the user and a mechanism for fetching scoped AWS STS tokens for each location.

The StorageBrowser component signs all requests made to Amazon S3 based on the temporary credentials generated by one of the authorization modes above. With Ampify Auth, this is handled automatically for you based on your backed resource definitions. With IAM Identity Center and S3 Access Grants, you are responsible for setting up IAM Identity Center and S3 Access Grants and configuring them to work with your application. With custom auth, you are responsible for configuring your application to vend STS tokens to the StorageBrowser component to authorize end users' requests during their session.

Bucket CORS

The StorageBrowser component is a client-side component that makes authorized requests to S3 APIs from the browser. If you are not using Amplify to provision your S3 buckets, you will need to enable Cross-Origin Resource Sharing (CORS) policies on the buckets you want available in the StorageBrowser component.

[
  {
    "ID": "S3CORSRuleId1",
    "AllowedHeaders": [
      "*"
    ],
    "AllowedMethods": [
      "GET",
      "HEAD",
      "PUT",
      "POST",
      "DELETE"
    ],
    "AllowedOrigins": [
      "*"
    ],
    "ExposeHeaders": [
      "last-modified",
      "content-type",
      "content-length",
      "etag",
      "x-amz-version-id",
      "x-amz-request-id",
      "x-amz-id-2",
      "x-amz-cf-id",
      "x-amz-storage-class",
      "date",
      "access-control-expose-headers"
    ],
    "MaxAgeSeconds": 3000
  }
]
Security best practice

To reinforce security on your S3 bucket, we recommend that you define the AllowedOrigins element with request restrictions. You can restrict bucket requests so that only the application URL that you want to accept requests from are allowed.

Amplify Auth

Make sure you have an Amplify Gen2 project started, by following the getting started guides. Then create an S3 bucket with access rules by defining a storage resource and adding authorization rules:

export const storage = defineStorage({
  name: 'myProjectFiles',
  access: (allow) => ({
    'public/*': [
      allow.guest.to(['read']),
      allow.authenticated.to(['read', 'write', 'delete']),
    ],
    'protected/{entity_id}/*': [
      allow.authenticated.to(['read']),
      allow.entity('identity').to(['read', 'write', 'delete'])
    ],
    'private/{entity_id}/*': [
      allow.entity('identity').to(['read', 'write', 'delete'])
    ]
  })
});

The access rules defined in defineStorage are treated as locations, which is described in the Overview. Therefore, users will have access to all of the S3 resource paths you have authorized them to access and can start browsing the S3 buckets or prefixes specified by these paths.

Then in your React code, call Amplify.configure() with your amplify_outputs.json. If you have some access rules that require a logged in user, like allow.authenticated, you can wrap your page in the <Authenticator> component to easily add authentication flows to your app.

import {
  createAmplifyAuthAdapter,
  createStorageBrowser,
} from '@aws-amplify/ui-react-storage/browser';
import "@aws-amplify/ui-react-storage/styles.css";

import config from './amplify_outputs.json';

Amplify.configure(config);

export const { StorageBrowser } = createStorageBrowser({
  config: createAmplifyAuthAdapter(),
});

IAM Identity Center and S3 Access Grants

To use this authorization method, you first have to configure an IAM Identity Center and set up permission grants for your users and groups in S3 Access Grants. Then, you connect your application to Identity Center and configure your application to exchange an identity token from your external Identity Provider with one from Identity Center. Afterwards, you configure your application to provide the Identity Center token to Storage Browser when a user opens the page in your application to access your data in S3. To learn more, visit the S3 documentation.

import {
    createManagedAuthAdapter,
    createStorageBrowser,
} from '@aws-amplify/ui-react-storage/browser';
import '@aws-amplify/ui-react-storage/styles.css';

export const { StorageBrowser } = createStorageBrowser({
   config: createManagedAuthAdapter({
    credentialsProvider: async (options?: { forceRefresh?: boolean }) => {
      // return your credentials object
      return {
        credentials: {
          accessKeyId: 'my-access-key-id',
          secretAccessKey: 'my-secret-access-key',
          sessionToken: 'my-session-token',
          expiration: new Date(),
        },
      }
    },
    // AWS `region` and `accountId` of the S3 Access Grants Instance.
    region: '',
    accountId: '',
    // call `onAuthStateChange` when end user auth state changes 
    // to clear sensitive data from the `StorageBrowser` state
    registerAuthListener: (onAuthStateChange) => {},
  })
});

Customer managed auth

To use your own identity and authorization service with Storage Browser, you will need to provide the temporary credentials used to sign the requests your end users are making to S3 through the StorageBrowser component.

import { createStorageBrowser } from '@aws-amplify/ui-react-storage/browser';
import '@aws-amplify/ui-react-storage/styles.css';

export const { StorageBrowser } = createStorageBrowser({
   config: {
    // Default AWS `region` and `accountId` of the S3 buckets.
    region: 'XXX',
    accountId: 'XXXXXX',
    listLocations: async (input = {}) => {
      const { nextToken, pageSize } = input;
      return {
        locations: [
          {
            bucketName: '[bucket name]',
            prefix: '', // empty path means bucket root
            id: 'XXXXXXX', // unique identifier 
            type: 'BUCKET',
            permission: ['delete', 'get', 'list', 'write'],
          },
          {
            bucketName: '[bucket name]',
            prefix: 'some/path',
            id: 'XXXXXXX', // unique identifier 
            type: 'PREFIX',
            permission: ['delete', 'get', 'list', 'write'],
          }
        ]
      }
    },
    getLocationCredentials: async ({ scope, permission }) => {
      // get credentials for specified scope and permission
      return {
        credentials: {
          accessKeyId: '',
          secretAccessKey: '',
          sessionToken: '',
          expiration: new Date(),
        }
      }
    },
    registerAuthListener: (onStateChange) => {

    }
  },
})

You will also need to provide these config values to createStorageBrowser:

interface Config {
  accountId?: string;
  customEndpoint?: string;
  getLocationCredentials: GetLocationCredentials;
  listLocations: ListLocations;
  registerAuthListener: RegisterAuthListener;
  region: string;
}

Customization

Theming

The StorageBrowser component is built on Amplify UI components so if you already have an Amplify UI theme it will just work with the Storage Browser out of the box. The components used in the Storage Browser are: <Button>, <Breadcrumbs>, <Menu>, <Pagination>, <SearchField>, <Checkbox>, <TextField>, and <Message>.

Theme object

You can use the createTheme() function to theme the StorageBrowser component

Home

import * as React from 'react';
import {
  ThemeStyle,
  createTheme,
  defineComponentTheme,
} from '@aws-amplify/ui-react/server';
import { View } from '@aws-amplify/ui-react';

const storageBrowserTheme = defineComponentTheme({
  name: 'storage-browser',
  theme: (tokens) => {
    return {
      _element: {
        controls: {
          flexDirection: 'row-reverse',
          backgroundColor: tokens.colors.background.primary,
          padding: tokens.space.small,
          borderRadius: tokens.radii.small,
        },
        title: {
          fontWeight: tokens.fontWeights.thin,
        },
      },
    };
  },
});

const theme = createTheme({
  name: 'my-theme',
  primaryColor: 'green',
  components: [storageBrowserTheme],
});

export default function Example() {
  return (
    <View backgroundColor="background.tertiary" {...theme.containerProps()}>
      <StorageBrowser />
      <ThemeStyle theme={theme} />
    </View>
  );
}

CSS

One way to theme the Storage Browser component is to use plain CSS. Amplify UI components use CSS classnames and CSS variables, so you can just write CSS to style the Storage Browser.

Icons

You can use the <IconsProvider> to customize the icons used in the StorageBrowser component.

Home

import * as React from 'react';
import {
  FcAlphabeticalSortingAz,
  FcAlphabeticalSortingZa,
  FcMinus,
  FcNext,
  FcPrevious,
  FcRefresh,
  FcSearch,
} from 'react-icons/fc';
import { IconsProvider } from '@aws-amplify/ui-react';

export default function Example() {
  return (
    <IconsProvider
      icons={{
        storageBrowser: {
          refresh: <FcRefresh />,
          'sort-indeterminate': <FcMinus />,
          'sort-ascending': <FcAlphabeticalSortingAz />,
          'sort-descending': <FcAlphabeticalSortingZa />,
        },
        searchField: {
          search: <FcSearch />,
        },
        pagination: {
          next: <FcNext />,
          previous: <FcPrevious />,
        },
      }}
    >
      <StorageBrowser />
    </IconsProvider>
  );
}

Display Text

You can customize all of the text (except S3 data like keys and bucket names) used in Storage Browser by using the displayText prop which is a nested object organized by view. You don't need to provide an entire object; the StorageBrowser component will merge your input with the default strings. Some texts are plain strings and others are are functions that take some input, like a date, and return a string.

Select a location

import * as React from 'react';
import { StorageBrowser } from './MockStorageBrowser';

export default function Example() {
  return (
    <StorageBrowser
      displayText={{
        LocationsView: {
          // Some display texts are a string
          title: 'Select a location',
          // Some are a function that return a string
          getPermissionName: (permissions) => permissions.join('/'),
        },
      }}
    />
  );
}

All of the display texts are listed below with their default values and types.

CopyView
NameDescriptionType
actionCancelLabelCancel
string
actionExitLabelExit
string
actionDestinationLabelCopy destination
string
statusDisplayCanceledLabelCanceled
string
statusDisplayCompletedLabelCompleted
string
statusDisplayFailedLabelFailed
string
statusDisplayInProgressLabelIn progress
string
statusDisplayTotalLabelTotal
string
statusDisplayQueuedLabelNot started
string
tableColumnCancelHeader
string
tableColumnStatusHeaderStatus
string
tableColumnFolderHeaderFolder
string
tableColumnNameHeaderName
string
tableColumnTypeHeaderType
string
tableColumnSizeHeaderSize
string
tableColumnProgressHeaderProgress
string
titleCopy
string
actionStartLabelCopy
string
getListFoldersResultsMessage
function
loadingIndicatorLabelLoading
string
overwriteWarningMessageCopied files will overwrite existing files at selected destination.
string
searchPlaceholderSearch for folders
string
getActionCompleteMessage
function
searchSubmitLabelSubmit
string
searchClearLabelClear search
string
CreateFolderView
NameDescriptionType
actionCancelLabelCancel
string
actionExitLabelExit
string
actionDestinationLabelDestination
string
statusDisplayCanceledLabelCanceled
string
statusDisplayCompletedLabelCompleted
string
statusDisplayFailedLabelFailed
string
statusDisplayInProgressLabelIn progress
string
statusDisplayTotalLabelTotal
string
statusDisplayQueuedLabelNot started
string
tableColumnCancelHeader
string
tableColumnStatusHeaderStatus
string
tableColumnFolderHeaderFolder
string
tableColumnNameHeaderName
string
tableColumnTypeHeaderType
string
tableColumnSizeHeaderSize
string
tableColumnProgressHeaderProgress
string
titleCreate folder
string
actionStartLabelCreate folder
string
folderNameLabelFolder name
string
folderNamePlaceholderFolder name cannot contain "/", nor end or start with "."
string
getValidationMessage
function
getActionCompleteMessage
function
DeleteView
NameDescriptionType
actionCancelLabelCancel
string
actionExitLabelExit
string
actionDestinationLabelDestination
string
statusDisplayCanceledLabelCanceled
string
statusDisplayCompletedLabelCompleted
string
statusDisplayFailedLabelFailed
string
statusDisplayInProgressLabelIn progress
string
statusDisplayTotalLabelTotal
string
statusDisplayQueuedLabelNot started
string
tableColumnCancelHeader
string
tableColumnStatusHeaderStatus
string
tableColumnFolderHeaderFolder
string
tableColumnNameHeaderName
string
tableColumnTypeHeaderType
string
tableColumnSizeHeaderSize
string
tableColumnProgressHeaderProgress
string
titleDelete
string
actionStartLabelDelete
string
getActionCompleteMessage
function
LocationDetailView
NameDescriptionType
loadingIndicatorLabelLoading
string
searchSubmitLabelSubmit
string
searchClearLabelClear search
string
getDateDisplayValue
function
getListItemsResultMessage
function
searchSubfoldersToggleLabelInclude subfolders
string
searchPlaceholderSearch current folder
string
tableColumnLastModifiedHeaderLast modified
string
tableColumnNameHeaderName
string
tableColumnSizeHeaderSize
string
tableColumnTypeHeaderType
string
selectFileLabelSelect file
string
selectAllFilesLabelSelect all files
string
getActionListItemLabel
function
getTitle
function
LocationsView
NameDescriptionType
loadingIndicatorLabelLoading
string
searchSubmitLabelSubmit
string
searchClearLabelClear search
string
getDateDisplayValue
function
titleHome
string
searchPlaceholderFilter folders and files
string
getListLocationsResultMessage
function
getPermissionName
function
getDownloadLabel
function
tableColumnBucketHeaderBucket
string
tableColumnFolderHeaderFolder
string
tableColumnPermissionsHeaderPermissions
string
tableColumnActionsHeaderActions
string
UploadView
NameDescriptionType
actionCancelLabelCancel
string
actionExitLabelExit
string
actionDestinationLabelDestination
string
statusDisplayCanceledLabelCanceled
string
statusDisplayCompletedLabelCompleted
string
statusDisplayFailedLabelFailed
string
statusDisplayInProgressLabelIn progress
string
statusDisplayTotalLabelTotal
string
statusDisplayQueuedLabelNot started
string
tableColumnCancelHeader
string
tableColumnStatusHeaderStatus
string
tableColumnFolderHeaderFolder
string
tableColumnNameHeaderName
string
tableColumnTypeHeaderType
string
tableColumnSizeHeaderSize
string
tableColumnProgressHeaderProgress
string
titleUpload
string
actionStartLabelUpload
string
addFilesLabelAdd files
string
addFolderLabelAdd folder
string
getActionCompleteMessage
function
getFilesValidationMessage
function
statusDisplayOverwritePreventedLabelOverwrite prevented
string
overwriteToggleLabelOverwrite existing files
string

Internationalization

You can also use the displayText prop to also support different languages. You can use an open source library like i18next, react-intl, or make your own:

Home

import * as React from 'react';
import { ToggleButton, ToggleButtonGroup } from '@aws-amplify/ui-react';

const dictionary = {
  en: null,
  es: {
    LocationsView: {
      title: 'Inicio',
    },
  },
};

export default function Example() {
  const [language, setLanguage] = React.useState('en');
  return (
    <>
      <ToggleButtonGroup
        value={language}
        isExclusive
        onChange={(value) => setLanguage(value)}
      >
        <ToggleButton value="en">En</ToggleButton>
        <ToggleButton value="es">Es</ToggleButton>
      </ToggleButtonGroup>
      <StorageBrowser displayText={dictionary[language]} />
    </>
  );
}

Composition

If you wanted to rearrange some of the components in any of the views without wanting to rebuild the entire view yourself, you can use the composable components of the view(s) you wish to customize. You will first need to use the useView('[view name]') hook and pass the return to the <StorageBrowser.[view name].Provider> and then the children of the provider can be the composed sub-components.

Below you wll find examples of how to compose each view. See View reference for a list of components that can be composed in each view.

Locations

import * as React from 'react';
import { Button, Flex, Text } from '@aws-amplify/ui-react';
import { IconChevronRight } from '@aws-amplify/ui-react/internal';
import { StorageBrowser, useView } from './MockStorageBrowser';
import { ComposedCopyView } from './ComposedCopyView';
import { ComposedCreateFolderView } from './ComposedCreateFolderView';
import { ComposedDeleteView } from './ComposedDeleteView';
import { ComposedUploadView } from './ComposedUploadView';

function LocationsView() {
  const state = useView('Locations');

  return (
    <Flex direction="column" padding="medium">
      <Text fontWeight="bold">Locations</Text>
      {state.pageItems.map((location) => {
        return (
          <Button
            key={location.id}
            justifyContent="flex-start"
            onClick={() => {
              state.onNavigate(location);
            }}
          >
            <Text flex="1">
              s3://{location.bucket}/{location.prefix}
            </Text>
            <Text as="span" color="font.tertiary" fontWeight="normal">
              {location.permissions.includes('list') ? 'Read' : null}{' '}
              {location.permissions.includes('write') ? 'Write' : null}
            </Text>
            <IconChevronRight color="font.tertiary" />
          </Button>
        );
      })}
    </Flex>
  );
}

const { LocationActionView } = StorageBrowser;

function MyLocationActionView({
  type,
  onExit,
}: {
  type?: string;
  onExit: () => void;
}) {
  let DialogContent = null;
  if (!type) return DialogContent;

  switch (type) {
    case 'copy':
      return <ComposedCopyView onExit={onExit} />;
    case 'createFolder':
      return <ComposedCreateFolderView onExit={onExit} />;
    case 'delete':
      return <ComposedDeleteView onExit={onExit} />;
    case 'upload':
      return <ComposedUploadView onExit={onExit} />;
    default:
      return <LocationActionView onExit={onExit} />;
  }
}

function MyStorageBrowser() {
  const state = useView('LocationDetail');
  const [currentAction, setCurrentAction] = React.useState<string>();
  const ref = React.useRef<HTMLDialogElement>(null);

  if (!state.location.current) {
    return <LocationsView />;
  }

  return (
    <>
      <StorageBrowser.LocationDetailView
        key={currentAction}
        onActionSelect={(action) => {
          setCurrentAction(action);
          ref.current?.showModal();
        }}
      />
      <dialog ref={ref}>
        <MyLocationActionView
          type={currentAction}
          onExit={() => {
            setCurrentAction(undefined);
            ref.current?.close();
          }}
        />
      </dialog>
    </>
  );
}

export default function Example() {
  return (
    <StorageBrowser.Provider>
      <MyStorageBrowser />
    </StorageBrowser.Provider>
  );
}
import * as React from 'react';
import { Flex } from '@aws-amplify/ui-react';
import { StorageBrowser, useView } from './MockStorageBrowser';

function LocationsView() {
  const state = useView('Locations');

  return (
    <StorageBrowser.LocationsView.Provider {...state}>
      <Flex direction="row">
        <StorageBrowser.LocationsView.Refresh />
        <StorageBrowser.LocationsView.Search />
      </Flex>
      <StorageBrowser.LocationsView.LocationsTable />
      <StorageBrowser.LocationsView.Pagination />
    </StorageBrowser.LocationsView.Provider>
  );
}

export default function Example() {
  return (
    <StorageBrowser.Provider>
      <LocationsView />
    </StorageBrowser.Provider>
  );
}
import * as React from 'react';
import { Flex } from '@aws-amplify/ui-react';
import { StorageBrowser, useView } from './MockStorageBrowser';

function LocationDetailView() {
  const state = useView('LocationDetail');

  return (
    <StorageBrowser.LocationDetailView.Provider {...state}>
      <Flex direction="row">
        <StorageBrowser.LocationDetailView.Refresh />
        <StorageBrowser.LocationDetailView.Search />
      </Flex>
      <StorageBrowser.LocationDetailView.LocationItemsTable />
      <StorageBrowser.LocationDetailView.Pagination />
    </StorageBrowser.LocationDetailView.Provider>
  );
}

export default function Example() {
  return (
    <StorageBrowser.Provider>
      <LocationDetailView />
    </StorageBrowser.Provider>
  );
}
import * as React from 'react';
import { StorageBrowser, useView } from './MockStorageBrowser';

export function ComposedCreateFolderView({ onExit }: { onExit: () => void }) {
  const state = useView('CreateFolder');

  return (
    <StorageBrowser.CreateFolderView.Provider {...state} onActionExit={onExit}>
      <StorageBrowser.CreateFolderView.Exit />
      <StorageBrowser.CreateFolderView.NameField />
      <StorageBrowser.CreateFolderView.Start />
      <StorageBrowser.CreateFolderView.Message />
    </StorageBrowser.CreateFolderView.Provider>
  );
}
import * as React from 'react';
import { Flex } from '@aws-amplify/ui-react';
import { StorageBrowser, useView } from './MockStorageBrowser';

export function ComposedUploadView({ onExit }: { onExit: () => void }) {
  const state = useView('Upload');

  return (
    <StorageBrowser.UploadView.Provider {...state} onActionExit={onExit}>
      <StorageBrowser.UploadView.Exit />
      <StorageBrowser.UploadView.TasksTable />
      <Flex direction="row" width="100%">
        <StorageBrowser.UploadView.AddFiles />
        <StorageBrowser.UploadView.AddFolder />
        <StorageBrowser.UploadView.Start />
      </Flex>
      <StorageBrowser.UploadView.Message />
    </StorageBrowser.UploadView.Provider>
  );
}
import * as React from 'react';
import { StorageBrowser, useView } from './MockStorageBrowser';

export function ComposedCopyView({ onExit }: { onExit: () => void }) {
  const state = useView('Copy');

  return (
    <StorageBrowser.CopyView.Provider {...state} onActionExit={onExit}>
      <StorageBrowser.CopyView.Exit />
      <StorageBrowser.CopyView.TasksTable />
      <StorageBrowser.CopyView.Destination />
      <StorageBrowser.CopyView.FoldersSearch />
      <StorageBrowser.CopyView.FoldersTable />
      <StorageBrowser.CopyView.Start />
      <StorageBrowser.CopyView.Message />
    </StorageBrowser.CopyView.Provider>
  );
}
import * as React from 'react';
import { StorageBrowser, useView } from './MockStorageBrowser';

export function ComposedDeleteView({ onExit }: { onExit: () => void }) {
  const state = useView('Delete');

  return (
    <StorageBrowser.DeleteView.Provider {...state} onActionExit={onExit}>
      <StorageBrowser.DeleteView.Exit />
      <StorageBrowser.DeleteView.TasksTable />
      <StorageBrowser.DeleteView.Start />
      <StorageBrowser.DeleteView.Message />
    </StorageBrowser.DeleteView.Provider>
  );
}

Custom UI

The createStorageBrowser function returns a useView() hook that lets you use the Storage Browser's internal state and event handlers to build your own UI on top the Storage Browser functionality.

The useView() hook takes a single argument which is the name of the view you want to get the UI state and event handlers for. The available views are Locations, LocationDetails, Copy, Upload, Delete, and CreateFolder. The return value of the hook will have all the necessary internal state and event handlers required to build that view. In fact, the Storage Browser component itself uses the useView() to manage its state, so you can build the UI exactly how we do!

Locations

import * as React from 'react';
import { StorageBrowser, useView } from './MockStorageBrowser';
import { CustomDeleteView } from './CustomDeleteView';
import { CustomCopyView } from './CustomCopyView';
import { CustomCreateFolderView } from './CustomCreateFolderView';
import { CustomUploadView } from './CustomUploadView';
import { CustomLocationsView } from './CustomLocationsView';

function MyLocationActionView({
  type,
  onExit,
}: {
  type?: string;
  onExit: () => void;
}) {
  let DialogContent = null;
  if (!type) return DialogContent;

  switch (type) {
    case 'copy':
      return <CustomCopyView onExit={onExit} />;
    case 'createFolder':
      return <CustomCreateFolderView onExit={onExit} />;
    case 'delete':
      return <CustomDeleteView onExit={onExit} />;
    case 'upload':
      return <CustomUploadView onExit={onExit} />;
    default:
      return null;
  }
}

function MyStorageBrowser() {
  const state = useView('LocationDetail');
  const [currentAction, setCurrentAction] = React.useState<string>();

  if (!state.location.current) {
    return <CustomLocationsView />;
  }

  if (currentAction) {
    return (
      <MyLocationActionView
        type={currentAction}
        onExit={() => {
          setCurrentAction(undefined);
        }}
      />
    );
  }

  return (
    <StorageBrowser.LocationDetailView
      key={currentAction}
      onActionSelect={(action) => {
        setCurrentAction(action);
      }}
    />
  );
}

export default function Example() {
  return (
    <StorageBrowser.Provider>
      <MyStorageBrowser />
    </StorageBrowser.Provider>
  );
}
import * as React from 'react';
import { Button, Flex, Text } from '@aws-amplify/ui-react';
import { FiChevronRight } from 'react-icons/fi';
import { StorageBrowser, useView } from './MockStorageBrowser';

export function CustomLocationsView() {
  const state = useView('Locations');

  return (
    <Flex direction="column" padding="medium">
      <Text fontWeight="bold">Locations</Text>
      {state.pageItems.map((location) => {
        return (
          <Button
            key={location.id}
            justifyContent="flex-start"
            onClick={() => {
              state.onNavigate(location);
            }}
          >
            <Text flex="1">
              s3://{location.bucket}/{location.prefix}
            </Text>
            <Text as="span" color="font.tertiary" fontWeight="normal">
              {location.permissions.includes('list') ? 'Read' : null}{' '}
              {location.permissions.includes('write') ? 'Write' : null}
            </Text>
            <FiChevronRight color="font.tertiary" />
          </Button>
        );
      })}
    </Flex>
  );
}
import * as React from 'react';
import { Button, Flex, Text } from '@aws-amplify/ui-react';
import { useView } from './MockStorageBrowser';

export function CustomCopyView({ onExit }: { onExit: () => void }) {
  const state = useView('Copy');

  return (
    <Flex direction="column">
      <Button variation="link" alignSelf="flex-start" onClick={onExit}>
        Exit
      </Button>
      {state.tasks.map((task) => (
        <Flex key={task.data.key} direction="row">
          <Text>{task.data.key}</Text>
        </Flex>
      ))}
      <Button onClick={state.onActionStart}>
        Copy {state.tasks.length} files
      </Button>
    </Flex>
  );
}
import * as React from 'react';
import { Button, Flex, TextField } from '@aws-amplify/ui-react';
import { useView } from './MockStorageBrowser';

export function CustomCreateFolderView({ onExit }: { onExit: () => void }) {
  const state = useView('CreateFolder');

  return (
    <Flex
      as="form"
      onSubmit={(e) => {
        e.preventDefault();
        try {
          state.onActionStart();
        } catch (error) {
          console.log(error);
        }
      }}
      direction="column"
    >
      <Button variation="link" alignSelf="flex-start" onClick={onExit}>
        Exit
      </Button>
      <TextField
        label=""
        value={state.folderName}
        onChange={(e) => {
          state.onFolderNameChange(e.target.value);
        }}
        outerEndComponent={<Button type="submit">Start</Button>}
      />
    </Flex>
  );
}
import * as React from 'react';
import { Button, Flex, Text, View } from '@aws-amplify/ui-react';
import { useView } from './MockStorageBrowser';

export function CustomUploadView({ onExit }: { onExit: () => void }) {
  const state = useView('Upload');

  return (
    <Flex direction="column">
      <Button variation="link" alignSelf="flex-start" onClick={onExit}>
        Exit
      </Button>
      <Button
        onClick={() => {
          state.onSelectFiles('FILE');
        }}
      >
        Add files
      </Button>
      {state.tasks.map((task) => {
        return (
          <View key={task.data.key}>
            <Text>{task.data.key}</Text>
            <Text>{task.progress}</Text>
          </View>
        );
      })}
    </Flex>
  );
}
import * as React from 'react';
import { Button, Flex, Text } from '@aws-amplify/ui-react';
import { StorageBrowser, useView } from './MockStorageBrowser';

export function CustomDeleteView({ onExit }: { onExit: () => void }) {
  const state = useView('Delete');

  return (
    <Flex direction="column">
      <Button variation="link" alignSelf="flex-start" onClick={onExit}>
        Exit
      </Button>
      {state.tasks.map((task) => (
        <Flex key={task.data.key} direction="row">
          <Text>{task.data.key}</Text>
        </Flex>
      ))}
      <Button onClick={state.onActionStart}>
        Delete {state.tasks.length} files
      </Button>
    </Flex>
  );
}

View reference

Below you will find the interfaces that the useView() hook returns for a given view as well as the composable sub-components of the view.

Locations view

Locations view state

interface LocationsViewState {
  hasNextPage: boolean;
  hasError: boolean;
  highestPageVisited: number;
  isLoading: boolean;
  message: string | undefined;
  shouldShowEmptyMessage: boolean;
  pageItems: LocationData[];
  page: number;
  searchQuery: string;
  hasExhaustedSearch: boolean;
  onDownload: (item: LocationData) => void;
  onNavigate: (location: LocationData) => void;
  onRefresh: () => void;
  onPaginate: (page: number) => void;
  onSearch: () => void;
  onSearchQueryChange: (value: string) => void;
  onSearchClear: () => void;
}

Locations view components

  • <StorageBrowser.LocationsView.Provider />
  • <StorageBrowser.LocationsView.LoadingIndicator />
  • <StorageBrowser.LocationsView.LocationsTable />
  • <StorageBrowser.LocationsView.Message />
  • <StorageBrowser.LocationsView.Pagination />
  • <StorageBrowser.LocationsView.Refresh />
  • <StorageBrowser.LocationsView.Search />
  • <StorageBrowser.LocationsView.Title />

LocationDetails view

LocationDetails view state

interface LocationDetailViewState {
  actions: ActionsListItem[];
  hasError: boolean;
  hasNextPage: boolean;
  hasDownloadError: boolean;
  highestPageVisited: number;
  isLoading: boolean;
  isSearchingSubfolders: boolean;
  location: LocationState;
  areAllFilesSelected: boolean;
  fileDataItems: FileDataItem[] | undefined;
  hasFiles: boolean;
  message: string | undefined;
  downloadErrorMessage: string | undefined;
  shouldShowEmptyMessage: boolean;
  searchQuery: string;
  hasExhaustedSearch: boolean;
  pageItems: LocationItemData[];
  page: number;
  onActionSelect: (actionType: string) => void;
  onDropFiles: (files: File[]) => void;
  onRefresh: () => void;
  onNavigate: (location: LocationData, path?: string) => void;
  onNavigateHome: () => void;
  onPaginate: (page: number) => void;
  onDownload: (fileItem: FileDataItem) => void;
  onSelect: (isSelected: boolean, fileItem: FileData) => void;
  onSelectAll: () => void;
  onSearch: () => void;
  onSearchClear: () => void;
  onSearchQueryChange: (value: string) => void;
  onToggleSearchSubfolders: () => void;
}

LocationDetails view components

  • <StorageBrowser.LocationDetails.Provider />
  • <StorageBrowser.LocationDetails.ActionsList />
  • <StorageBrowser.LocationDetails.DropZone />
  • <StorageBrowser.LocationDetails.LoadingIndicator />
  • <StorageBrowser.LocationDetails.LocationItemsTable />
  • <StorageBrowser.LocationDetails.Message />
  • <StorageBrowser.LocationDetails.Navigation />
  • <StorageBrowser.LocationDetails.Pagination />
  • <StorageBrowser.LocationDetails.Refresh />
  • <StorageBrowser.LocationDetails.Search />
  • <StorageBrowser.LocationDetails.SearchSubfoldersToggle />
  • <StorageBrowser.LocationDetails.Title />

Upload view

Upload view state

interface UploadViewState {
  isOverwritingEnabled: boolean;
  onDropFiles: (files: File[]) => void;
  onSelectFiles: (type: 'FILE' | 'FOLDER') => void;
  onToggleOverwrite: () => void;
  invalidFiles: FileItems | undefined;
  isProcessing: boolean;
  isProcessingComplete: boolean;
  location: LocationState;
  onActionCancel: () => void;
  onActionExit: () => void;
  onActionStart: () => void;
  onTaskRemove?: (task: Task<T>) => void;
  statusCounts: StatusCounts;
  tasks: Tasks<T>;
}

Upload view components

  • <StorageBrowser.UploadView.Provider />
  • <StorageBrowser.UploadView.AddFiles />
  • <StorageBrowser.UploadView.AddFolder />
  • <StorageBrowser.UploadView.Cancel />
  • <StorageBrowser.UploadView.DropZone />
  • <StorageBrowser.UploadView.Destination />
  • <StorageBrowser.UploadView.Exit />
  • <StorageBrowser.UploadView.Message />
  • <StorageBrowser.UploadView.OverwriteToggle />
  • <StorageBrowser.UploadView.Start />
  • <StorageBrowser.UploadView.Statuses />
  • <StorageBrowser.UploadView.TasksTable />
  • <StorageBrowser.UploadView.Title />

Copy view

Copy view state

interface CopyViewState {
  invalidFiles: FileItems | undefined;
  isProcessing: boolean;
  isProcessingComplete: boolean;
  location: LocationState;
  onActionCancel: () => void;
  onActionExit: () => void;
  onActionStart: () => void;
  onTaskRemove?: (task: Task<T>) => void;
  statusCounts: StatusCounts;
  tasks: Tasks<T>;
}

Copy view components

  • <StorageBrowser.CopyView.Provider />
  • <StorageBrowser.CopyView.Cancel />
  • <StorageBrowser.CopyView.Destination />
  • <StorageBrowser.CopyView.Exit />
  • <StorageBrowser.CopyView.FoldersLoadingIndicator />
  • <StorageBrowser.CopyView.FoldersMessage />
  • <StorageBrowser.CopyView.FoldersPagination />
  • <StorageBrowser.CopyView.FoldersSearch />
  • <StorageBrowser.CopyView.FoldersTable />
  • <StorageBrowser.CopyView.Message />
  • <StorageBrowser.CopyView.Start />
  • <StorageBrowser.CopyView.Statuses />
  • <StorageBrowser.CopyView.TasksTable />
  • <StorageBrowser.CopyView.Title />

Delete view

Delete view state

interface DeleteViewState {
  isProcessing: boolean;
  isProcessingComplete: boolean;
  location: LocationState;
  onActionCancel: () => void;
  onActionExit: () => void;
  onActionStart: () => void;
  onTaskRemove?: (task: Task<T>) => void;
  statusCounts: StatusCounts;
  tasks: Tasks<T>;
}

Delete view components

  • <StorageBrowser.DeleteView.Provider />
  • <StorageBrowser.DeleteView.Cancel />
  • <StorageBrowser.DeleteView.Exit />
  • <StorageBrowser.DeleteView.Message />
  • <StorageBrowser.DeleteView.Start />
  • <StorageBrowser.DeleteView.Statuses />
  • <StorageBrowser.DeleteView.TasksTable />
  • <StorageBrowser.DeleteView.Title />

CreateFolder view

CreateFolder view state

interface CreateFolderViewState {
  isProcessing: boolean;
  isProcessingComplete: boolean;
  location: LocationState;
  onActionCancel: () => void;
  onActionExit: () => void;
  onActionStart: () => void;
  onTaskRemove?: (task: Task<T>) => void;
  statusCounts: StatusCounts;
  tasks: Tasks<T>;
}

CreateFolder view components

  • <StorageBrowser.CreateFolderView.Provider />
  • <StorageBrowser.CreateFolderView.Exit />
  • <StorageBrowser.CreateFolderView.NameField />
  • <StorageBrowser.CreateFolderView.Message />
  • <StorageBrowser.CreateFolderView.Start />
  • <StorageBrowser.CreateFolderView.Title />

Roadmap

We made our roadmap for Storage Browser public so that you could see what's in store and provide feedback to help drive the future direction of this product. Visit our roadmap and share your thoughts today.

These are the features we are currently evaluating.

  1. Thumbnail previews
  2. Full object previews
  3. User-controlled object tags
  4. Support for S3 Glacier Flexible Retrieval and S3 Glacier Deep Archive
  5. Support for S3 Access Points
  6. User controlled byte-range GETs
  7. Support for a CloudFront cache

Amplify open source software, documentation and community are supported by Amazon Web Services.

© 2024 Amazon Web Services, Inc. and its affiliates. All rights reserved. View the site terms and privacy policy.

Flutter and the related logo are trademarks of Google LLC. We are not endorsed by or affiliated with Google LLC.