Skip to content

Commit a004c2f

Browse files
tiffanynwyeungashika112calebpollman
authored
feat(storage-browser): add custom file validation option (#6575)
* chore: run CI against feature branch PRs * chore(storage-browser): refactor files context, migrate file validation, rename files dir (#6552) * chore(storage-browser): refactor & migrate file validation to files context * chore: address feedback * chore: add & adjust tests, clean up reducer & files resolver * chore: adjust files context tests * fix: only track `invalidFiles` from latest set of added files * chore: address feedback, clean up tests * chore: address feedback * chore: fix naming * chore: remove extraneous check in test case * refactor: add file action type, address feedback * chore: rename 'files' to 'fileitems' * chore: clean up testing, fix missed names * chore: fix split imports, simplify upload handler data creation * chore: fix missed names * chore: rename files folder to fileItems, address feedback * feat(storage-browser): add options and validateFile props (#6555) * feat(storage-browser): add options and validateFile props to createStorageBrowser * nit: fix typos in unrelated descriptions * chore: fix inconsistent naming * chore: fix context tests, address minor syntax errors, standardize naming * chore: fix sizes, import order * chore: fix import order * chore(storage-browser): add custom file validation docs examples & e2e tests (#6569) * chore(storage-browser): add docs examples and e2e tests * chore: clean up wording, code examples * chore: adjust cypress commands * Update docs/src/pages/[platform]/connected-components/storage/storage-browser/react.mdx Co-authored-by: ashika112 <155593080+ashika112@users.noreply.github.com> * Update docs/src/pages/[platform]/connected-components/storage/storage-browser/react.mdx Co-authored-by: Caleb Pollman <cpollman@amazon.com> --------- Co-authored-by: ashika112 <155593080+ashika112@users.noreply.github.com> Co-authored-by: Caleb Pollman <cpollman@amazon.com> * chore(storage-browser): add feat changeset (#6573) * Revert "chore: run CI against feature branch PRs" This reverts commit 328a4e6. --------- Co-authored-by: ashika112 <155593080+ashika112@users.noreply.github.com> Co-authored-by: Caleb Pollman <cpollman@amazon.com>
1 parent 02cb81b commit a004c2f

File tree

45 files changed

+1149
-520
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+1149
-520
lines changed

.changeset/selfish-humans-rest.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
---
2+
'@aws-amplify/ui-react-storage': minor
3+
---
4+
5+
feat(storage-browser): add custom file validation and options config
6+
7+
**Add custom file validation**
8+
9+
```tsx
10+
import React from 'react';
11+
import { createStorageBrowser } from '@aws-amplify/ui-react-storage/browser';
12+
import '@aws-amplify/ui-react-storage/styles.css';
13+
14+
const MAX_FILE_SIZE = 1000 * 1000; // 1 MB
15+
16+
const customValidateFile = (file: File) => {
17+
const isValidSize = file.size <= MAX_FILE_SIZE;
18+
const isValidType = file.type.includes('image');
19+
return isValidSize && isValidType;
20+
};
21+
22+
const { StorageBrowser } = createStorageBrowser({
23+
// ...config goes here...
24+
options: {
25+
validateFile: customValidateFile,
26+
},
27+
});
28+
29+
export default function Example() {
30+
return (
31+
<StorageBrowser
32+
displayText={{
33+
UploadView: {
34+
getFilesValidationMessage: (data) => {
35+
const invalidFileNames = data?.invalidFiles?.map(
36+
({ file }) => file.name
37+
);
38+
return {
39+
content: `Only image files that are 1 MB or smaller are accepted. Invalid files: ${invalidFileNames}`,
40+
type: 'error',
41+
};
42+
},
43+
},
44+
}}
45+
/>
46+
);
47+
}
48+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import React from 'react';
2+
import { createStorageBrowser } from '@aws-amplify/ui-react-storage/browser';
3+
import '@aws-amplify/ui-react-storage/styles.css';
4+
import { mockConfig } from './mockConfig'; // IGNORE
5+
6+
const MAX_FILE_SIZE = 1000 * 1000; // 1 MB
7+
8+
const customValidateFile = (file: File) => {
9+
const isValidSize = file.size <= MAX_FILE_SIZE;
10+
const isValidType = file.type.includes('image');
11+
return isValidSize && isValidType;
12+
};
13+
14+
const { StorageBrowser } = createStorageBrowser({
15+
options: {
16+
validateFile: customValidateFile,
17+
},
18+
config: mockConfig, // IGNORE
19+
});
20+
21+
export default function Example() {
22+
return (
23+
<StorageBrowser
24+
displayText={{
25+
UploadView: {
26+
getFilesValidationMessage: (data) => {
27+
const invalidFileNames = data?.invalidFiles?.map(
28+
({ file }) => file.name
29+
);
30+
return {
31+
content: `Only image files that are 1 MB or smaller are accepted. Invalid files: ${invalidFileNames}`,
32+
type: 'error',
33+
};
34+
},
35+
},
36+
}}
37+
/>
38+
);
39+
}

docs/src/pages/[platform]/connected-components/storage/storage-browser/react.mdx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -586,6 +586,23 @@ export const generateUrlHandler: GenerateLink = ({ data, config, options }) => {
586586
This example is for demonstration purpose only. For more details, please refer to the [presigned URLs documentation](https://docs.aws.amazon.com/AmazonS3/latest/userguide/using-presigned-url.html#PresignedUrl-Expiration)
587587
</Message>
588588

589+
### Customize file validation
590+
591+
`createStorageBrowser` allows customization of the Storage Browser settings by passing `options` . To customize file validation, provide a `validateFile` prop in `options` to replace the default file validation performed when selecting files. On file selection events, `validateFile` runs against each selected file and returns `true` if the file is considered valid.
592+
593+
[Custom `displayText`](#display-text) should also be used to show a different error message to reflect your custom validation.
594+
595+
Below is an example to override file validation for Storage Browser to only accept image files that are less than or equal to 1 MB in size, and displays a custom error message if an invalid file is selected.
596+
<Example>
597+
<ExampleCode>
598+
```jsx file=./examples/CustomFileValidation.tsx
599+
```
600+
</ExampleCode>
601+
</Example>
602+
<Message colorTheme="info" variation='filled' heading="Validation best practice">
603+
When creating custom validation, it is important to ensure that valid files also adhere to the [S3 quota limits](https://docs.aws.amazon.com/general/latest/gr/s3.html#limits_s3). A file **will** fail during upload if it exceeds the S3 limits, even if the custom validation succeeded when selecting the file.
604+
</Message>
605+
589606
## Initializing `StorageBrowser` with a default location
590607

591608
The `StorageBrowser` can be initialized with a default location using the `defaultValue` prop.

examples/next-app-router/src/app/page.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export default function Home() {
2828
<Link href={`/storage-browser/default-value${LINK_PAYLOAD}`}>
2929
Default Value
3030
</Link>
31+
<Link href="/storage-browser/file-validation">File Validation</Link>
3132
</div>
3233
</div>
3334
</main>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { Suspense } from 'react';
2+
export default function RootLayout({
3+
children,
4+
}: {
5+
children: React.ReactNode;
6+
}) {
7+
return (
8+
<Suspense>
9+
<div className="flex w-full flex-row">
10+
<div className="flex-1 p-2">{children}</div>
11+
</div>
12+
</Suspense>
13+
);
14+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
'use client';
2+
3+
import { StorageBrowser } from './storage-browser-with-validation'; // IGNORE
4+
5+
export default function App() {
6+
return (
7+
<StorageBrowser
8+
displayText={{
9+
UploadView: {
10+
getFilesValidationMessage: (data) => {
11+
const invalidFileNames = data?.invalidFiles?.map(
12+
({ file }) => file.name
13+
);
14+
return {
15+
content: `Only image files (PNG/JPEG/GIF) that are 1 MB or smaller are accepted. Invalid files added to the upload queue: ${invalidFileNames}`,
16+
type: 'error',
17+
};
18+
},
19+
},
20+
}}
21+
/>
22+
);
23+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import {
2+
createStorageBrowser,
3+
defaultActionConfigs,
4+
} from '@aws-amplify/ui-react-storage/browser';
5+
import { MockHandlers } from '@aws-amplify/ui-test-utils/storage-browser';
6+
import '@aws-amplify/ui-react-storage/styles.css';
7+
8+
import { INITIAL_VALUES } from '../storage-browser';
9+
10+
const customValidateFile = (file: File) => {
11+
const validFileSize = file.size <= 1000 * 1000; // 1MB
12+
const onlyImages = ['image/gif', 'image/jpeg', 'image/png'].includes(
13+
file.type
14+
);
15+
return validFileSize && onlyImages;
16+
};
17+
18+
const handlers = new MockHandlers({ initialValues: INITIAL_VALUES });
19+
20+
export const { StorageBrowser } = createStorageBrowser({
21+
actions: {
22+
default: {
23+
...defaultActionConfigs,
24+
copy: {
25+
...defaultActionConfigs.copy,
26+
handler: handlers.copy,
27+
},
28+
createFolder: {
29+
...defaultActionConfigs.createFolder,
30+
handler: handlers.createFolder,
31+
},
32+
delete: {
33+
...defaultActionConfigs.delete,
34+
handler: handlers.delete,
35+
},
36+
download: handlers.download,
37+
listLocationItems: handlers.listLocationItems,
38+
upload: {
39+
...defaultActionConfigs.upload,
40+
handler: handlers.upload,
41+
},
42+
},
43+
},
44+
config: {
45+
listLocations: handlers.listLocations,
46+
region: '',
47+
registerAuthListener: () => null,
48+
getLocationCredentials: () =>
49+
Promise.resolve({
50+
credentials: {
51+
accessKeyId: 'accessKeyId',
52+
expiration: new Date(),
53+
secretAccessKey: 'secretAccessKey',
54+
sessionToken: 'sessionToken',
55+
},
56+
}),
57+
},
58+
options: {
59+
validateFile: customValidateFile,
60+
},
61+
});

examples/next-app-router/src/app/storage-browser/storage-browser.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ import {
22
createStorageBrowser,
33
defaultActionConfigs,
44
} from '@aws-amplify/ui-react-storage/browser';
5-
import '@aws-amplify/ui-react-storage/styles.css';
65
import {
76
InitialValues,
87
MockHandlers,
98
} from '@aws-amplify/ui-test-utils/storage-browser';
9+
import '@aws-amplify/ui-react-storage/styles.css';
1010

1111
export const PREFIXES = {
1212
base: 'my-prefix/',
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { StorageBrowser } from './storage-browser-with-validation'; // import first, not included in docs example
2+
3+
export default function App() {
4+
return (
5+
<StorageBrowser
6+
displayText={{
7+
UploadView: {
8+
getFilesValidationMessage: (data) => {
9+
const invalidFileNames = data?.invalidFiles?.map(
10+
({ file }) => file.name
11+
);
12+
return {
13+
content: `Only image files (PNG/JPEG/GIF) that are 1 MB or smaller are accepted. Invalid files added to the upload queue: ${invalidFileNames}`,
14+
type: 'error',
15+
};
16+
},
17+
},
18+
}}
19+
/>
20+
);
21+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import {
2+
createStorageBrowser,
3+
defaultActionConfigs,
4+
} from '@aws-amplify/ui-react-storage/browser';
5+
import { MockHandlers } from '@aws-amplify/ui-test-utils/storage-browser';
6+
import '@aws-amplify/ui-react-storage/styles.css';
7+
8+
import { INITIAL_VALUES } from '../storage-browser';
9+
10+
const customValidateFile = (file: File) => {
11+
const validFileSize = file.size <= 1000 * 1000; // 1MB
12+
const onlyImages = ['image/gif', 'image/jpeg', 'image/png'].includes(
13+
file.type
14+
);
15+
return validFileSize && onlyImages;
16+
};
17+
18+
const handlers = new MockHandlers({ initialValues: INITIAL_VALUES });
19+
20+
export const { StorageBrowser } = createStorageBrowser({
21+
actions: {
22+
default: {
23+
...defaultActionConfigs,
24+
copy: {
25+
...defaultActionConfigs.copy,
26+
handler: handlers.copy,
27+
},
28+
createFolder: {
29+
...defaultActionConfigs.createFolder,
30+
handler: handlers.createFolder,
31+
},
32+
delete: {
33+
...defaultActionConfigs.delete,
34+
handler: handlers.delete,
35+
},
36+
download: handlers.download,
37+
listLocationItems: handlers.listLocationItems,
38+
upload: {
39+
...defaultActionConfigs.upload,
40+
handler: handlers.upload,
41+
},
42+
},
43+
},
44+
config: {
45+
listLocations: handlers.listLocations,
46+
region: '',
47+
registerAuthListener: () => null,
48+
getLocationCredentials: () =>
49+
Promise.resolve({
50+
credentials: {
51+
accessKeyId: 'accessKeyId',
52+
expiration: new Date(),
53+
secretAccessKey: 'secretAccessKey',
54+
sessionToken: 'sessionToken',
55+
},
56+
}),
57+
},
58+
options: {
59+
validateFile: customValidateFile,
60+
},
61+
});

0 commit comments

Comments
 (0)