diff --git a/.changeset/dull-cheetahs-deliver.md b/.changeset/dull-cheetahs-deliver.md new file mode 100644 index 00000000000..e173bce57e7 --- /dev/null +++ b/.changeset/dull-cheetahs-deliver.md @@ -0,0 +1,8 @@ +--- +"@aws-amplify/ui-react-storage": patch +"@aws-amplify/ui-react": patch +--- + +fix(storage): fixing drop handler for file extensions + +Previously, adding a file extension for an `acceptedFileTypes` when a customer would drop a file it would show as rejected even if it was a valid file type. This fixes that issue. diff --git a/examples/next/pages/ui/components/storage/storage-manager/accept-file-extension/aws-exports.js b/examples/next/pages/ui/components/storage/storage-manager/accept-file-extension/aws-exports.js new file mode 100644 index 00000000000..4245c81a219 --- /dev/null +++ b/examples/next/pages/ui/components/storage/storage-manager/accept-file-extension/aws-exports.js @@ -0,0 +1,2 @@ +import awsExports from '@environments/storage/file-uploader/src/aws-exports'; +export default awsExports; diff --git a/examples/next/pages/ui/components/storage/storage-manager/accept-file-extension/index.page.tsx b/examples/next/pages/ui/components/storage/storage-manager/accept-file-extension/index.page.tsx new file mode 100644 index 00000000000..ef00f4f1e16 --- /dev/null +++ b/examples/next/pages/ui/components/storage/storage-manager/accept-file-extension/index.page.tsx @@ -0,0 +1,19 @@ +import { Amplify } from 'aws-amplify'; +import { StorageManager } from '@aws-amplify/ui-react-storage'; +import '@aws-amplify/ui-react/styles.css'; +import awsExports from './aws-exports'; +Amplify.configure(awsExports); + +export function StorageManagerExample() { + return ( + <> + + + ); +} +export default StorageManagerExample; diff --git a/packages/react/src/primitives/DropZone/__tests__/filterAllowedFiles.test.ts b/packages/react/src/primitives/DropZone/__tests__/filterAllowedFiles.test.ts new file mode 100644 index 00000000000..f877c724c12 --- /dev/null +++ b/packages/react/src/primitives/DropZone/__tests__/filterAllowedFiles.test.ts @@ -0,0 +1,32 @@ +import { filterAllowedFiles } from '../filterAllowedFiles'; + +describe('filterAllowFiles', () => { + const droppedFiles = [ + new File([], 'test.jpg', { type: 'image/jpg' }), + new File([], 'test.png', { type: 'image/png' }), + ]; + + it('should work with * MIME types', () => { + const { acceptedFiles, rejectedFiles } = filterAllowedFiles(droppedFiles, [ + 'image/*', + ]); + expect(rejectedFiles).toHaveLength(0); + expect(acceptedFiles).toHaveLength(2); + }); + + it('should work with extension types', () => { + const { acceptedFiles, rejectedFiles } = filterAllowedFiles(droppedFiles, [ + '.png', + ]); + expect(rejectedFiles).toHaveLength(1); + expect(acceptedFiles).toHaveLength(1); + }); + + it('should work with *', () => { + const { acceptedFiles, rejectedFiles } = filterAllowedFiles(droppedFiles, [ + '*', + ]); + expect(rejectedFiles).toHaveLength(0); + expect(acceptedFiles).toHaveLength(2); + }); +}); diff --git a/packages/react/src/primitives/DropZone/filterAllowedFiles.ts b/packages/react/src/primitives/DropZone/filterAllowedFiles.ts new file mode 100644 index 00000000000..3b26fa3d756 --- /dev/null +++ b/packages/react/src/primitives/DropZone/filterAllowedFiles.ts @@ -0,0 +1,50 @@ +// Drag event file shape is different than the drop event fileshape +type DragFile = + | { + kind: string; + type: string; + name?: string; + } + | File; + +export function filterAllowedFiles( + files: FileType[], + acceptedFileTypes: string[] +): { acceptedFiles: FileType[]; rejectedFiles: FileType[] } { + // Allow any files if acceptedFileTypes is undefined, empty array, or contains '*' + if ( + !acceptedFileTypes || + acceptedFileTypes.length === 0 || + acceptedFileTypes.includes('*') + ) { + return { acceptedFiles: files, rejectedFiles: [] }; + } + const acceptedFiles: FileType[] = []; + const rejectedFiles: FileType[] = []; + + function filterFile(file: DragFile) { + const { type = '', name = '' } = file; + const mimeType = type.toLowerCase(); + const baseMimeType = mimeType.split('/')[0]; + + return acceptedFileTypes.some((type) => { + const validType = type.trim().toLowerCase(); + // if the accepted file type is a file extension + // it will start with '.', check against the file name + if (validType.charAt(0) === '.') { + return name.toLowerCase().endsWith(validType); + } + // This is something like a image/* mime type + if (validType.endsWith('/*')) { + return baseMimeType === validType.split('/')[0]; + } + return mimeType === validType; + }); + } + + files.forEach((file) => { + (filterFile(file) ? acceptedFiles : rejectedFiles).push(file); + }); + + return { acceptedFiles, rejectedFiles }; +} diff --git a/packages/react/src/primitives/DropZone/useDropZone.ts b/packages/react/src/primitives/DropZone/useDropZone.ts index 102a64a2281..377b67095db 100644 --- a/packages/react/src/primitives/DropZone/useDropZone.ts +++ b/packages/react/src/primitives/DropZone/useDropZone.ts @@ -1,49 +1,7 @@ import { useState } from 'react'; import { UseDropZoneProps, UseDropZoneReturn, DragState } from './types'; import { isFunction } from '@aws-amplify/ui'; - -type DragFile = - | { - kind: string; - type: string; - } - | File; - -function filterAllowedFiles( - files: FileType[], - acceptedFileTypes: string[] -): { acceptedFiles: FileType[]; rejectedFiles: FileType[] } { - // Allow any files if acceptedFileTypes is undefined, empty array, or contains '*' - if ( - !acceptedFileTypes || - acceptedFileTypes.length === 0 || - acceptedFileTypes.includes('*') - ) { - return { acceptedFiles: files, rejectedFiles: [] }; - } - const acceptedFiles: FileType[] = []; - const rejectedFiles: FileType[] = []; - - function filterFile({ type = '' }) { - const mimeType = type.toLowerCase(); - const baseMimeType = mimeType.split('/')[0]; - - return acceptedFileTypes.some((type) => { - const validType = type.trim().toLowerCase(); - if (validType.endsWith('/*')) { - // This is something like a image/* mime type - return baseMimeType === validType.split('/')[0]; - } - return mimeType === validType; - }); - } - - files.forEach((file) => { - (filterFile(file) ? acceptedFiles : rejectedFiles).push(file); - }); - - return { acceptedFiles, rejectedFiles }; -} +import { filterAllowedFiles } from './filterAllowedFiles'; export function useDropZone({ onDropComplete,