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:
- 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.
- 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.
- 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
}
]
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
Name | Description | Type |
---|---|---|
actionCancelLabel | Cancel |
|
actionExitLabel | Exit |
|
actionDestinationLabel | Copy destination |
|
statusDisplayCanceledLabel | Canceled |
|
statusDisplayCompletedLabel | Completed |
|
statusDisplayFailedLabel | Failed |
|
statusDisplayInProgressLabel | In progress |
|
statusDisplayTotalLabel | Total |
|
statusDisplayQueuedLabel | Not started |
|
tableColumnCancelHeader |
| |
tableColumnStatusHeader | Status |
|
tableColumnFolderHeader | Folder |
|
tableColumnNameHeader | Name |
|
tableColumnTypeHeader | Type |
|
tableColumnSizeHeader | Size |
|
tableColumnProgressHeader | Progress |
|
title | Copy |
|
actionStartLabel | Copy |
|
getListFoldersResultsMessage |
| |
loadingIndicatorLabel | Loading |
|
overwriteWarningMessage | Copied files will overwrite existing files at selected destination. |
|
searchPlaceholder | Search for folders |
|
getActionCompleteMessage |
| |
searchSubmitLabel | Submit |
|
searchClearLabel | Clear search |
|
CreateFolderView
Name | Description | Type |
---|---|---|
actionCancelLabel | Cancel |
|
actionExitLabel | Exit |
|
actionDestinationLabel | Destination |
|
statusDisplayCanceledLabel | Canceled |
|
statusDisplayCompletedLabel | Completed |
|
statusDisplayFailedLabel | Failed |
|
statusDisplayInProgressLabel | In progress |
|
statusDisplayTotalLabel | Total |
|
statusDisplayQueuedLabel | Not started |
|
tableColumnCancelHeader |
| |
tableColumnStatusHeader | Status |
|
tableColumnFolderHeader | Folder |
|
tableColumnNameHeader | Name |
|
tableColumnTypeHeader | Type |
|
tableColumnSizeHeader | Size |
|
tableColumnProgressHeader | Progress |
|
title | Create folder |
|
actionStartLabel | Create folder |
|
folderNameLabel | Folder name |
|
folderNamePlaceholder | Folder name cannot contain "/", nor end or start with "." |
|
getValidationMessage |
| |
getActionCompleteMessage |
|
DeleteView
Name | Description | Type |
---|---|---|
actionCancelLabel | Cancel |
|
actionExitLabel | Exit |
|
actionDestinationLabel | Destination |
|
statusDisplayCanceledLabel | Canceled |
|
statusDisplayCompletedLabel | Completed |
|
statusDisplayFailedLabel | Failed |
|
statusDisplayInProgressLabel | In progress |
|
statusDisplayTotalLabel | Total |
|
statusDisplayQueuedLabel | Not started |
|
tableColumnCancelHeader |
| |
tableColumnStatusHeader | Status |
|
tableColumnFolderHeader | Folder |
|
tableColumnNameHeader | Name |
|
tableColumnTypeHeader | Type |
|
tableColumnSizeHeader | Size |
|
tableColumnProgressHeader | Progress |
|
title | Delete |
|
actionStartLabel | Delete |
|
getActionCompleteMessage |
|
LocationDetailView
Name | Description | Type |
---|---|---|
loadingIndicatorLabel | Loading |
|
searchSubmitLabel | Submit |
|
searchClearLabel | Clear search |
|
getDateDisplayValue |
| |
getListItemsResultMessage |
| |
searchSubfoldersToggleLabel | Include subfolders |
|
searchPlaceholder | Search current folder |
|
tableColumnLastModifiedHeader | Last modified |
|
tableColumnNameHeader | Name |
|
tableColumnSizeHeader | Size |
|
tableColumnTypeHeader | Type |
|
selectFileLabel | Select file |
|
selectAllFilesLabel | Select all files |
|
getActionListItemLabel |
| |
getTitle |
|
LocationsView
Name | Description | Type |
---|---|---|
loadingIndicatorLabel | Loading |
|
searchSubmitLabel | Submit |
|
searchClearLabel | Clear search |
|
getDateDisplayValue |
| |
title | Home |
|
searchPlaceholder | Filter folders and files |
|
getListLocationsResultMessage |
| |
getPermissionName |
| |
getDownloadLabel |
| |
tableColumnBucketHeader | Bucket |
|
tableColumnFolderHeader | Folder |
|
tableColumnPermissionsHeader | Permissions |
|
tableColumnActionsHeader | Actions |
|
UploadView
Name | Description | Type |
---|---|---|
actionCancelLabel | Cancel |
|
actionExitLabel | Exit |
|
actionDestinationLabel | Destination |
|
statusDisplayCanceledLabel | Canceled |
|
statusDisplayCompletedLabel | Completed |
|
statusDisplayFailedLabel | Failed |
|
statusDisplayInProgressLabel | In progress |
|
statusDisplayTotalLabel | Total |
|
statusDisplayQueuedLabel | Not started |
|
tableColumnCancelHeader |
| |
tableColumnStatusHeader | Status |
|
tableColumnFolderHeader | Folder |
|
tableColumnNameHeader | Name |
|
tableColumnTypeHeader | Type |
|
tableColumnSizeHeader | Size |
|
tableColumnProgressHeader | Progress |
|
title | Upload |
|
actionStartLabel | Upload |
|
addFilesLabel | Add files |
|
addFolderLabel | Add folder |
|
getActionCompleteMessage |
| |
getFilesValidationMessage |
| |
statusDisplayOverwritePreventedLabel | Overwrite prevented |
|
overwriteToggleLabel | Overwrite existing files |
|
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.
- Thumbnail previews
- Full object previews
- User-controlled object tags
- Support for S3 Glacier Flexible Retrieval and S3 Glacier Deep Archive
- Support for S3 Access Points
- User controlled byte-range GETs
- Support for a CloudFront cache