├── .all-contributorsrc ├── .babelrc.js ├── .eslintrc.json ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── config.yml │ └── feature_request.md ├── pull_request_template.md └── workflows │ ├── build.yml │ └── docs.yml ├── .gitignore ├── .mochasetup.js ├── .size-snapshot.json ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── dist ├── index.d.ts ├── index.es.js ├── index.es.js.map ├── index.js └── index.js.map ├── docs ├── installation.md ├── intro.md ├── screenshots.md ├── support.md └── theming.md ├── package.json ├── pics ├── demo_pic.jpg ├── demo_pic2.JPG ├── demo_pic4.JPG └── demo_pic5.JPG ├── rollup.config.js ├── src ├── components │ ├── DropzoneArea.js │ ├── DropzoneArea.md │ ├── DropzoneAreaBase.js │ ├── DropzoneAreaBase.md │ ├── DropzoneDialog.js │ ├── DropzoneDialog.md │ ├── DropzoneDialogBase.js │ ├── DropzoneDialogBase.md │ ├── PreviewList.js │ └── SnackbarContentWrapper.js ├── helpers.js ├── index.d.ts └── index.js ├── styleguide.config.js └── yarn.lock /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "projectName": "material-ui-dropzone", 3 | "projectOwner": "Yuvaleros", 4 | "repoType": "github", 5 | "repoHost": "https://github.com", 6 | "files": [ 7 | "README.md" 8 | ], 9 | "imageSize": 80, 10 | "commit": false, 11 | "commitConvention": "gitmoji", 12 | "contributors": [ 13 | { 14 | "login": "Yuvaleros", 15 | "name": "Yuvaleros", 16 | "avatar_url": "https://avatars1.githubusercontent.com/u/3898166?v=4", 17 | "profile": "https://github.com/Yuvaleros", 18 | "contributions": [ 19 | "ideas", 20 | "code", 21 | "design", 22 | "doc", 23 | "question", 24 | "review", 25 | "maintenance" 26 | ] 27 | }, 28 | { 29 | "login": "panz3r", 30 | "name": "Mattia Panzeri", 31 | "avatar_url": "https://avatars3.githubusercontent.com/u/1754457?v=4", 32 | "profile": "https://github.com/panz3r", 33 | "contributions": [ 34 | "ideas", 35 | "code", 36 | "design", 37 | "doc", 38 | "example", 39 | "infra", 40 | "bug", 41 | "question", 42 | "review", 43 | "maintenance" 44 | ] 45 | }, 46 | { 47 | "login": "max-carroll", 48 | "name": "Max Carroll", 49 | "avatar_url": "https://avatars2.githubusercontent.com/u/13512675?v=4", 50 | "profile": "https://github.com/max-carroll", 51 | "contributions": [ 52 | "ideas", 53 | "code", 54 | "design", 55 | "example", 56 | "review" 57 | ] 58 | }, 59 | { 60 | "login": "mattcorner", 61 | "name": "Matthew Corner", 62 | "avatar_url": "https://avatars1.githubusercontent.com/u/27866636?v=4", 63 | "profile": "https://github.com/mattcorner", 64 | "contributions": [ 65 | "bug", 66 | "ideas", 67 | "code" 68 | ] 69 | }, 70 | { 71 | "login": "loongyh", 72 | "name": "Barry Loong", 73 | "avatar_url": "https://avatars3.githubusercontent.com/u/20846761?v=4", 74 | "profile": "https://github.com/loongyh", 75 | "contributions": [ 76 | "ideas", 77 | "code" 78 | ] 79 | }, 80 | { 81 | "login": "blouin", 82 | "name": "JF Blouin", 83 | "avatar_url": "https://avatars1.githubusercontent.com/u/20212446?v=4", 84 | "profile": "https://github.com/blouin", 85 | "contributions": [ 86 | "ideas", 87 | "code" 88 | ] 89 | }, 90 | { 91 | "login": "anthonyraymond", 92 | "name": "Anthony Raymond", 93 | "avatar_url": "https://avatars1.githubusercontent.com/u/7503585?v=4", 94 | "profile": "http://stackoverflow.com/users/2275818/anthony-raymond", 95 | "contributions": [ 96 | "code", 97 | "example" 98 | ] 99 | }, 100 | { 101 | "login": "isaacbuckman", 102 | "name": "isaacbuckman", 103 | "avatar_url": "https://avatars1.githubusercontent.com/u/34870239?v=4", 104 | "profile": "https://github.com/isaacbuckman", 105 | "contributions": [ 106 | "bug", 107 | "code", 108 | "example" 109 | ] 110 | }, 111 | { 112 | "login": "MatthijsMud", 113 | "name": "MatthijsMud", 114 | "avatar_url": "https://avatars3.githubusercontent.com/u/11519728?v=4", 115 | "profile": "https://github.com/MatthijsMud", 116 | "contributions": [ 117 | "bug", 118 | "code" 119 | ] 120 | } 121 | ], 122 | "contributorsPerLine": 3 123 | } 124 | -------------------------------------------------------------------------------- /.babelrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | [ 4 | '@babel/preset-env', 5 | { 6 | modules: false, 7 | }, 8 | ], 9 | '@babel/preset-react', 10 | ], 11 | plugins: [ 12 | 'babel-plugin-optimize-clsx', 13 | [ 14 | '@babel/plugin-proposal-class-properties', 15 | { 16 | loose: true, 17 | }, 18 | ], 19 | [ 20 | '@babel/plugin-proposal-object-rest-spread', 21 | { 22 | loose: true, 23 | }, 24 | ], 25 | // any package needs to declare 7.4.4 as a runtime dependency. default is ^7.0.0 26 | [ 27 | '@babel/plugin-transform-runtime', 28 | { 29 | version: '^7.4.4', 30 | }, 31 | ], 32 | // for IE 11 support 33 | '@babel/plugin-transform-object-assign', 34 | // material-ui 'productionPlugins' 35 | '@babel/plugin-transform-react-constant-elements', 36 | 'babel-plugin-transform-dev-warning', 37 | [ 38 | 'babel-plugin-transform-react-remove-prop-types', 39 | { 40 | mode: 'unsafe-wrap', 41 | }, 42 | ], 43 | // END material-ui 'productionPlugins' 44 | ], 45 | ignore: [/@babel[\\|/]runtime/], // Fix a Windows issue. 46 | }; 47 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "es6": true, 4 | "browser": true, 5 | "mocha": true, 6 | "node": true 7 | }, 8 | "parser": "babel-eslint", 9 | "parserOptions": { 10 | "ecmaVersion": 7, 11 | "sourceType": "module", 12 | "ecmaFeatures": { 13 | "jsx": true, 14 | "experimentalObjectRestSpread": true 15 | } 16 | }, 17 | "globals": { 18 | "React": true 19 | }, 20 | "settings": { 21 | "react": { 22 | "version": "15.0" 23 | } 24 | }, 25 | "plugins": [ 26 | "babel", 27 | "react" 28 | ], 29 | "extends": [ 30 | "eslint:recommended", 31 | "plugin:react/recommended" 32 | ], 33 | "rules": { 34 | "array-bracket-spacing": ["error", "never"], 35 | "arrow-parens": "error", 36 | "arrow-spacing": "error", 37 | "brace-style": "error", 38 | "comma-dangle": ["error", "always-multiline"], 39 | "comma-spacing": ["error", {"before": false, "after": true}], 40 | "comma-style": ["error", "last"], 41 | "computed-property-spacing": ["error", "never"], 42 | "consistent-return": "off", 43 | "consistent-this": ["error", "self"], 44 | "dot-location": ["error", "property"], 45 | "dot-notation": "error", 46 | "eol-last": "error", 47 | "eqeqeq": ["error", "smart"], 48 | "generator-star-spacing": "error", 49 | "id-blacklist": ["error", "e"], 50 | "indent": ["error", 4, {"SwitchCase": 1}], 51 | "jsx-quotes": ["error", "prefer-double"], 52 | "key-spacing": "error", 53 | "keyword-spacing": "error", 54 | "max-len": ["error", 120, 4, {"ignoreComments": true}], 55 | "new-cap": ["off", {"capIsNew": true, "newIsCap": true}], 56 | "no-await-in-loop": "error", 57 | "no-case-declarations": "off", 58 | "no-cond-assign": "error", 59 | "no-dupe-args": "error", 60 | "no-dupe-keys": "error", 61 | "no-duplicate-case": "error", 62 | "no-empty-pattern": "error", 63 | "no-extra-boolean-cast": "error", 64 | "no-extra-semi": "error", 65 | "no-multi-spaces": "error", 66 | "no-multiple-empty-lines": "error", 67 | "no-shadow": "off", 68 | "no-spaced-func": "error", 69 | "no-trailing-spaces": "error", 70 | "no-undef": "error", 71 | "no-underscore-dangle": "error", 72 | "no-unneeded-ternary": "error", 73 | "no-unreachable": "error", 74 | "no-unused-expressions": "error", 75 | "no-unused-vars": "error", 76 | "no-var": "error", 77 | "object-curly-spacing": ["error", "never"], 78 | "one-var": ["error", "never"], 79 | "operator-linebreak": ["error", "after"], 80 | "padded-blocks": ["error", "never"], 81 | "prefer-arrow-callback": "off", 82 | "prefer-const": "error", 83 | "quotes": ["error", "single", "avoid-escape"], 84 | "semi": ["error", "always"], 85 | "space-before-blocks": ["error", "always"], 86 | "space-before-function-paren": ["error", "never"], 87 | "space-infix-ops": "error", 88 | "space-unary-ops": ["error", {"words": true, "nonwords": false}], 89 | "spaced-comment": "error", 90 | "strict": "off", 91 | "yoda": "error", 92 | "babel/new-cap": "off", 93 | "babel/object-shorthand": "off" 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F41B Bug Report" 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | --- 8 | 9 | ## Bug Report 10 | 11 | ### Describe the bug 12 | 13 | 16 | 17 | ### Steps to reproduce 18 | 19 | 28 | 29 | ### Expected behavior 30 | 31 | 34 | 35 | 40 | 41 | ### Versions 42 | 43 | - OS: [e.g. iOS] 44 | - Browser: [e.g. chrome, safari] 45 | - `@mui/material` version: [e.g. 5.0.2] 46 | - `material-ui-dropzone` version: [e.g. 3.0.1] 47 | 48 | ### Additional context 49 | 50 | 53 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: true 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F680 Feature Request" 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | --- 8 | 9 | ## Feature Request 10 | 11 | ### Describe the problem related to this feature request 12 | 13 | 17 | 18 | ### Describe the solution you'd like 19 | 20 | 23 | 24 | ### Describe alternatives you've considered 25 | 26 | 29 | 30 | ### Teachability, Documentation, Adoption, Migration Strategy 31 | 32 | 36 | 37 | ### Additional context 38 | 39 | 42 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | 8 | 9 | 15 | 16 | ## Type of change 17 | 18 | 21 | 22 | - Bug fix (non-breaking change which fixes an issue) 23 | - New feature (non-breaking change which adds functionality) 24 | - Breaking change (fix or feature that would cause existing functionality to not work as expected) 25 | 26 | ## How Has This Been Tested 27 | 28 | 33 | 34 | - [ ] Test A 35 | - [ ] Test B 36 | 37 | **Test Configuration**: 38 | 39 | - Browser: 40 | 41 | ## Checklist 42 | 43 | - [ ] My code follows the style guidelines of this project 44 | - [ ] I have performed a self-review of my own code 45 | - [ ] I have commented my code, particularly in hard-to-understand areas 46 | - [ ] I have made corresponding changes to the documentation 47 | - [ ] My changes generate no new warnings 48 | - [ ] I have added tests that prove my fix is effective or that my feature works 49 | - [ ] New and existing unit tests pass locally with my changes 50 | - [ ] Any dependent changes have been merged and published in downstream modules 51 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Rebuild Dist 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - v2.x 8 | 9 | env: 10 | CI: true 11 | 12 | jobs: 13 | build-and-commit: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - name: Clone repository 18 | uses: actions/checkout@v2 19 | 20 | - run: node --version 21 | - run: yarn --version 22 | 23 | - name: Install dependencies 24 | run: yarn install --frozen-lockfile 25 | 26 | - name: Build 27 | run: yarn build 28 | 29 | - name: Push changes 30 | uses: actions-go/push@v1 31 | with: 32 | author-name: ${{ github.actor }} 33 | author-email: ${{ github.actor }}@users.noreply.github.com 34 | commit-message: ':package: Rebuild dist @ ${{ github.sha }}' 35 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Update Docs 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | 8 | env: 9 | CI: true 10 | 11 | jobs: 12 | build-and-deploy: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - name: Clone repository 17 | uses: actions/checkout@v2 18 | 19 | - run: node --version 20 | - run: yarn --version 21 | 22 | - name: Install dependencies 23 | run: yarn install --frozen-lockfile 24 | 25 | - name: Build docs 26 | run: yarn docs:build 27 | 28 | - name: Deploy to gh-pages 29 | uses: JamesIves/github-pages-deploy-action@releases/v3 30 | with: 31 | ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }} 32 | BRANCH: gh-pages # The branch the action should deploy to. 33 | FOLDER: styleguide # The folder the action should deploy. 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea/ 3 | build/ 4 | node_modules/ 5 | styleguide/ 6 | -------------------------------------------------------------------------------- /.mochasetup.js: -------------------------------------------------------------------------------- 1 | require('babel-register')(); 2 | require ('ignore-styles'); 3 | -------------------------------------------------------------------------------- /.size-snapshot.json: -------------------------------------------------------------------------------- 1 | { 2 | "dist/index.js": { 3 | "bundled": 58038, 4 | "minified": 26083, 5 | "gzipped": 6105 6 | }, 7 | "dist/index.es.js": { 8 | "bundled": 56840, 9 | "minified": 25059, 10 | "gzipped": 5983, 11 | "treeshaked": { 12 | "rollup": { 13 | "code": 10987, 14 | "import_statements": 1540 15 | }, 16 | "webpack": { 17 | "code": 22114 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # material-ui-dropzone 2 | 3 | Thanks to all contributers who improved **material-ui-dropzone** by opening an issue/PR. 4 | 5 | This changelog refers to `master` branch (currently `v3.x`), for `v2.x`-specific changelog see [`v2.x CHANGELOG.md`](https://github.com/Yuvaleros/material-ui-dropzone/blob/v2.x/CHANGELOG.md). 6 | 7 | ## `v3.5.0` 8 | 9 | ###### To be released 10 | 11 | #### :sparkles: Features 12 | 13 | * Update DropzoneDialog to allow for a custom dialog title (PR [#234](https://github.com/Yuvaleros/material-ui-dropzone/pull/234) by **@4-Eyes**) 14 | 15 | #### :bug: Bugfixes 16 | 17 | * Use binary system instead of decimal for size error (PR [#245](https://github.com/Yuvaleros/material-ui-dropzone/pull/245) by **@AntoineGrandchamp**) 18 | 19 | #### :label: Typings 20 | 21 | * Add disableRejectionFeedback to DropzoneAreaBaseProps type (PR [#247](https://github.com/Yuvaleros/material-ui-dropzone/pull/247) by **@Parya-Jafari**, related to issue [#141](https://github.com/Yuvaleros/material-ui-dropzone/issues/141) by **@PabloCanalSuarez**) 22 | 23 | ## `v3.4.1` 24 | 25 | ###### Unreleased 26 | 27 | #### :bug: Bugfixes 28 | 29 | * Fix `react-dropzone` props not being passed properly (PR [#239](https://github.com/Yuvaleros/material-ui-dropzone/pull/239) by **@panz3r**, fixes [#235](https://github.com/Yuvaleros/material-ui-dropzone/issues/235) by **@grahamlangford**) 30 | 31 | ## `v3.4.0` 32 | 33 | ###### August 18, 2020 34 | 35 | #### :sparkles: New Features 36 | 37 | * Allow all filetypes by default (PR [#226](https://github.com/Yuvaleros/material-ui-dropzone/pull/226) by **@panz3r**, fixes [#214](https://github.com/Yuvaleros/material-ui-dropzone/issues/214) by **@FilipeCosta06**) 38 | * Allow passing a custom Icon to be displayed inside Dropzone area (PR [#227](https://github.com/Yuvaleros/material-ui-dropzone/pull/227) by **@panz3r**, closes [#48](https://github.com/Yuvaleros/material-ui-dropzone/issues/48) by **@N4Design**) 39 | 40 | #### :lipstick: UI Changes 41 | 42 | * Fix Snackbar icons margins (PR [#223](https://github.com/Yuvaleros/material-ui-dropzone/pull/223) by **@Armanio**) 43 | 44 | ## `v3.3.1` 45 | 46 | ###### July 24, 2020 47 | 48 | #### :label: Typings 49 | 50 | * Update PropTypes for `initialFiles` to avoid issues with `NextJS` (Fixes [#208](https://github.com/Yuvaleros/material-ui-dropzone/issues/208)) 51 | 52 | ## `v3.3.0` 53 | 54 | ###### June 20, 2020 55 | 56 | #### :sparkles: New Features 57 | 58 | * Make `previewChips` use the grid system as well (PR [#173](https://github.com/Yuvaleros/material-ui-dropzone/pull/173) by **@anthonyraymond**) 59 | * Set `initialFiles` with `File` or `URL string` (PR [#194](https://github.com/Yuvaleros/material-ui-dropzone/pull/194) by **@isaacbuckman**, reported as [#192](https://github.com/Yuvaleros/material-ui-dropzone/issues/192) by **@isaacbuckman**) 60 | * Add `onAlert` callback prop to hook into Snackbar messaging (PR [#205](https://github.com/Yuvaleros/material-ui-dropzone/pull/205) by **@mattcorner**, reported as [#200](https://github.com/Yuvaleros/material-ui-dropzone/issues/200) by **@mattcorner**) 61 | 62 | ## `v3.2.1` 63 | 64 | ###### Unreleased 65 | 66 | #### :bug: Bugfixes 67 | 68 | * Show remove buttons when they have focus (PR [#191](https://github.com/Yuvaleros/material-ui-dropzone/pull/191) by **@MatthijsMud**, reported as [#190](https://github.com/Yuvaleros/material-ui-dropzone/issues/190) by **@MatthijsMud**) 69 | * Change `SnackbarContentWrapper` class names for variants to avoid conflicts with Material UI internals (PR [#198](https://github.com/Yuvaleros/material-ui-dropzone/pull/198) by **@panz3r**, reported as [#183](https://github.com/Yuvaleros/material-ui-dropzone/issues/183) by **@mattcorner**) 70 | * Fix error message when dropping more than `filesLimit` files (PR [#199](https://github.com/Yuvaleros/material-ui-dropzone/pull/199) by **@panz3r**, reported as [#196](https://github.com/Yuvaleros/material-ui-dropzone/issues/196) by **@edricwu**) 71 | 72 | #### :label: Typings 73 | 74 | * Update Typescript typings 75 | * Fixes issue [#172](https://github.com/Yuvaleros/material-ui-dropzone/issues/172) by **@amirmishani** 76 | * Fixes issue [#184](https://github.com/Yuvaleros/material-ui-dropzone/issues/184) by **@zikaeroh** 77 | 78 | #### :arrow_up: Dependencies Update 79 | 80 | * Bump `websocket-extensions` from `0.1.3` to `0.1.4` 81 | * Bump `@babel/*` devDeps to `7.10.x` 82 | 83 | ## `v3.2.0` 84 | 85 | ###### June 03, 2020 86 | 87 | #### :sparkles: New Features 88 | 89 | * Add `DropzoneAreaBase` and `DropzoneDialogBase` controlled components (PR [#175](https://github.com/Yuvaleros/material-ui-dropzone/pull/175) by **@panz3r**) 90 | 91 | ## `v3.1.0` 92 | 93 | ###### May 27, 2020 94 | 95 | #### :sparkles: New Features 96 | 97 | * Add `getPreviewIcon` prop to DropzoneArea component to customize file preview (PR [#154](https://github.com/Yuvaleros/material-ui-dropzone/pull/154) by **@max-carroll**) 98 | * Add support for style with MUI Theme, see [docs](https://yuvaleros.github.io/material-ui-dropzone/#section-theme) for more details (PR [#158](https://github.com/Yuvaleros/material-ui-dropzone/pull/158) by **@panz3r**): 99 | * Closes issue [#73](https://github.com/Yuvaleros/material-ui-dropzone/issues/73) by **@sirsaeta** 100 | * Closes issue [#80](https://github.com/Yuvaleros/material-ui-dropzone/issues/80) by **@mikiasmohamed** 101 | * Closes issue [#125](https://github.com/Yuvaleros/material-ui-dropzone/issues/125) by **@suiaing** 102 | * Closes issue [#146](https://github.com/Yuvaleros/material-ui-dropzone/issues/146) by **@mattcorner** 103 | * Add `showAlerts` property to show alerts only on error (PR [#170](https://github.com/Yuvaleros/material-ui-dropzone/pull/170) by **@blouin**): 104 | * `showAlerts` can be a boolean ("global" `true` or `false` for all alerts). 105 | * `showAlerts` can be an array, with values `error`, `info`, `success`: 106 | * `showAlerts={['error']}` for only `errors` 107 | * `showAlerts={['error', 'info']}` for both `errors` and `info` 108 | * `showAlerts={['error', 'success', 'info']}` is same as `showAlerts={true}` 109 | * `showAlerts={[]}` is same as `showAlerts={false}` 110 | 111 | #### :bug: Bugfixes 112 | 113 | * Avoid appending extension if present when loading external files (PR [#150](https://github.com/Yuvaleros/material-ui-dropzone/pull/150) by **@panz3r**, reported as [#135](https://github.com/Yuvaleros/material-ui-dropzone/issues/135) by **@mballeng91**) 114 | * Prevent control focus rubber band (PR [#156](https://github.com/Yuvaleros/material-ui-dropzone/pull/156) by **@max-carroll**, reported as [#145](https://github.com/Yuvaleros/material-ui-dropzone/issues/145) by **@topninja**) 115 | 116 | ## `v3.0.0` 117 | 118 | ###### April 25, 2020 119 | 120 | #### :boom: **BREAKING** 121 | 122 | * Upgrade `react-dropzone` to version 10 (PR [#120](https://github.com/Yuvaleros/material-ui-dropzone/pull/120) by **@panz3r**) 123 | * Drop support for React `<16.8` and Material-UI `v3` (PR [#120](https://github.com/Yuvaleros/material-ui-dropzone/pull/120) by **@panz3r**) 124 | * After the code refactor of PR [#121](https://github.com/Yuvaleros/material-ui-dropzone/pull/121), the `onChange` handler is invoked also on component mount (with or without files depending on the value of the `initialFiles` prop) - see issue [#153](https://github.com/Yuvaleros/material-ui-dropzone/issues/153) for more details. 125 | 126 | #### :sparkles: New Features 127 | 128 | * Addition of `previewText` prop to `DropzoneArea` component (PR [#121](https://github.com/Yuvaleros/material-ui-dropzone/pull/121) by **@panz3r**, same as [#112](https://github.com/Yuvaleros/material-ui-dropzone/pull/112) by **@charlot567**) 129 | * Add `disableRejectionFeedback` prop to `DropzoneArea` component (PR [#142](https://github.com/Yuvaleros/material-ui-dropzone/pull/142) by **@panz3r**, reported as [#141](https://github.com/Yuvaleros/material-ui-dropzone/issues/141) by **@PabloCanalSuarez**) 130 | * Add `inputProps` prop to `DropzoneArea` component (PR [#134](https://github.com/Yuvaleros/material-ui-dropzone/pull/134) by **@panz3r**), fixes: 131 | * set name for `` element ([#92](https://github.com/Yuvaleros/material-ui-dropzone/issues/92) by **@mnlbox**) 132 | * Upload Directory/folder ([#122](https://github.com/Yuvaleros/material-ui-dropzone/issues/122) by **@antares-va-tech**) 133 | * Add `dropzoneProps` prop to `DropzoneArea` component (PR [#134](https://github.com/Yuvaleros/material-ui-dropzone/pull/134) by **@panz3r**), fixes: 134 | * Dropzone disable attribute not working ([#103](https://github.com/Yuvaleros/material-ui-dropzone/issues/103) by **@stefanstankovic995**) 135 | * Add `alertSnackbarProps` prop to `DropzoneArea` component (PR [#134](https://github.com/Yuvaleros/material-ui-dropzone/pull/134) by **@panz3r**), fixes: 136 | * Ability to override snackbard background colours ([#45](https://github.com/Yuvaleros/material-ui-dropzone/issues/45) by **@IsabellaRey**) 137 | * Allow the abillity of change anchorOrigin of snackbar ([#64](https://github.com/Yuvaleros/material-ui-dropzone/issues/64) by **@widomin**) 138 | 139 | #### :bug: Bugfixes 140 | 141 | * Avoid appending extension if present when loading external files (PR [#137](https://github.com/Yuvaleros/material-ui-dropzone/pull/137) by **@panz3r**, reported as [#135](https://github.com/Yuvaleros/material-ui-dropzone/issues/135) by **@mballeng91**) 142 | * onDrop returns each file one at a time (PR [#121](https://github.com/Yuvaleros/material-ui-dropzone/pull/121) by **@panz3r**, reported as [#65](https://github.com/Yuvaleros/material-ui-dropzone/issues/65) by **@AlanOrtega91**) 143 | * Fully support setting `acceptedFiles` as `.fileending` (PR [#121](https://github.com/Yuvaleros/material-ui-dropzone/pull/121) by **@panz3r**, reported as [#107](https://github.com/Yuvaleros/material-ui-dropzone/issues/107) by **@wirmar**) 144 | 145 | #### :lipstick: UI 146 | 147 | * Should be using Typography instead of `

` (PR [#121](https://github.com/Yuvaleros/material-ui-dropzone/pull/121) by **@panz3r**, reported as [#31](https://github.com/Yuvaleros/material-ui-dropzone/issues/31) by **@PolGuixe** and **@IsabellaRey**) 148 | 149 | #### :recycle: Refactoring 150 | 151 | * Code refactor ([#121](https://github.com/Yuvaleros/material-ui-dropzone/pull/121) by **@panz3r**) 152 | 153 | #### :pencil: Docs 154 | 155 | * Improved docs ([#128](https://github.com/Yuvaleros/material-ui-dropzone/pull/128) by **@panz3r**) 156 | 157 |
158 | 159 | ## `v2.5.0` 160 | 161 | ###### April 15, 2020 162 | 163 | #### :sparkles: New Features 164 | 165 | * Add `previewGridClasses`, `previewGridProps` props (PR [#124](https://github.com/Yuvaleros/material-ui-dropzone/pull/124) by **@loongyh**, reported as [#85](https://github.com/Yuvaleros/material-ui-dropzone/issues/85) by **@zeckdude**) 166 | * Add `dialogProps` prop to customize `DropzoneDialog` appearance (PR [#105](https://github.com/Yuvaleros/material-ui-dropzone/pull/105) by **@chattling**) 167 | 168 | #### :bug: Bugfixes 169 | 170 | * ReferenceError: regeneratorRuntime is not defined (PR [#111](https://github.com/Yuvaleros/material-ui-dropzone/pull/111) by **@panz3r**, reported as [#77](https://github.com/Yuvaleros/material-ui-dropzone/issues/77) by **@rooch84**, **@Tassfighter** and **@eluchsinger**) 171 | 172 | #### :zap: Improvements 173 | 174 | * Review dependencies (PR [#111](https://github.com/Yuvaleros/material-ui-dropzone/pull/111) by **@panz3r**) 175 | * Tooling upgrade (PR [#115](https://github.com/Yuvaleros/material-ui-dropzone/pull/115) by **@panz3r**) 176 | 177 | #### :pencil: Docs 178 | 179 | * Added new `dialogProps` prop to `README` (PR [#113](https://github.com/Yuvaleros/material-ui-dropzone/pull/113) by **@chattling**) 180 | * Fix submit/cancel typo (PR [#126](https://github.com/Yuvaleros/material-ui-dropzone/pull/126) by **@Maxim-Mazurok**) 181 | 182 |
183 | 184 | ## `v2.4.9` 185 | 186 | ###### March 10, 2020 187 | 188 | #### :sparkles: New Features 189 | 190 | * Add `dialogProps` prop to customize `DropzoneDialog` appearance (PR [#105](https://github.com/Yuvaleros/material-ui-dropzone/pull/105) by **@chattling**) 191 | 192 | #### :bug: Bugfixes 193 | 194 | * Move `@material-ui/icons` to `peerDependencies` (PR [#104](https://github.com/Yuvaleros/material-ui-dropzone/pull/104) by **@panz3r**, reported as [#95](https://github.com/Yuvaleros/material-ui-dropzone/issues/95) by **@char0n**) 195 | 196 |
197 | 198 | ## `v2.4.8` 199 | 200 | ###### February 22, 2020 201 | 202 | #### :bug: Bugfixes 203 | 204 | * initialFiles not shown up (PR [#101](https://github.com/Yuvaleros/material-ui-dropzone/pull/101) by **@faupol3**, reported as [#87](https://github.com/Yuvaleros/material-ui-dropzone/issues/87) by **@ameenazeemiacmedocs**) 205 | * Unable to preview png file (PR [#101](https://github.com/Yuvaleros/material-ui-dropzone/pull/101) by **@faupol3**, reported as [#90](https://github.com/Yuvaleros/material-ui-dropzone/issues/90) by **@shailesh-padave**) 206 | * remove console logs from Dropzone dialog (PR [#102](https://github.com/Yuvaleros/material-ui-dropzone/pull/102) by **@chattling**, reported as [#96](https://github.com/Yuvaleros/material-ui-dropzone/issues/96) by **@Morteza-Jenabzadeh**) 207 | 208 | #### :recycle: Refactoring 209 | 210 | * Improved code quality (PR [#94](https://github.com/Yuvaleros/material-ui-dropzone/pull/94) by **@GRcwolf**) 211 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | 2 | # Contributor Covenant Code of Conduct 3 | 4 | ## Our Pledge 5 | 6 | We as members, contributors, and leaders pledge to make participation in our 7 | community a harassment-free experience for everyone, regardless of age, body 8 | size, visible or invisible disability, ethnicity, sex characteristics, gender 9 | identity and expression, level of experience, education, socio-economic status, 10 | nationality, personal appearance, race, religion, or sexual identity 11 | and orientation. 12 | 13 | We pledge to act and interact in ways that contribute to an open, welcoming, 14 | diverse, inclusive, and healthy community. 15 | 16 | ## Our Standards 17 | 18 | Examples of behavior that contributes to a positive environment for our 19 | community include: 20 | 21 | * Demonstrating empathy and kindness toward other people 22 | * Being respectful of differing opinions, viewpoints, and experiences 23 | * Giving and gracefully accepting constructive feedback 24 | * Accepting responsibility and apologizing to those affected by our mistakes, 25 | and learning from the experience 26 | * Focusing on what is best not just for us as individuals, but for the 27 | overall community 28 | 29 | Examples of unacceptable behavior include: 30 | 31 | * The use of sexualized language or imagery, and sexual attention or 32 | advances of any kind 33 | * Trolling, insulting or derogatory comments, and personal or political attacks 34 | * Public or private harassment 35 | * Publishing others' private information, such as a physical or email 36 | address, without their explicit permission 37 | * Other conduct which could reasonably be considered inappropriate in a 38 | professional setting 39 | 40 | ## Enforcement Responsibilities 41 | 42 | Community leaders are responsible for clarifying and enforcing our standards of 43 | acceptable behavior and will take appropriate and fair corrective action in 44 | response to any behavior that they deem inappropriate, threatening, offensive, 45 | or harmful. 46 | 47 | Community leaders have the right and responsibility to remove, edit, or reject 48 | comments, commits, code, wiki edits, issues, and other contributions that are 49 | not aligned to this Code of Conduct, and will communicate reasons for moderation 50 | decisions when appropriate. 51 | 52 | ## Scope 53 | 54 | This Code of Conduct applies within all community spaces, and also applies when 55 | an individual is officially representing the community in public spaces. 56 | Examples of representing our community include using an official e-mail address, 57 | posting via an official social media account, or acting as an appointed 58 | representative at an online or offline event. 59 | 60 | ## Enforcement 61 | 62 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 63 | reported to the community leaders responsible for enforcement at Github by 64 | opening an issue [here](https://github.com/Yuvaleros/material-ui-dropzone/issues/new). 65 | All complaints will be reviewed and investigated promptly and fairly. 66 | 67 | All community leaders are obligated to respect the privacy and security of the 68 | reporter of any incident. 69 | 70 | ## Enforcement Guidelines 71 | 72 | Community leaders will follow these Community Impact Guidelines in determining 73 | the consequences for any action they deem in violation of this Code of Conduct: 74 | 75 | ### 1. Correction 76 | 77 | **Community Impact**: Use of inappropriate language or other behavior deemed 78 | unprofessional or unwelcome in the community. 79 | 80 | **Consequence**: A private, written warning from community leaders, providing 81 | clarity around the nature of the violation and an explanation of why the 82 | behavior was inappropriate. A public apology may be requested. 83 | 84 | ### 2. Warning 85 | 86 | **Community Impact**: A violation through a single incident or series 87 | of actions. 88 | 89 | **Consequence**: A warning with consequences for continued behavior. No 90 | interaction with the people involved, including unsolicited interaction with 91 | those enforcing the Code of Conduct, for a specified period of time. This 92 | includes avoiding interactions in community spaces as well as external channels 93 | like social media. Violating these terms may lead to a temporary or 94 | permanent ban. 95 | 96 | ### 3. Temporary Ban 97 | 98 | **Community Impact**: A serious violation of community standards, including 99 | sustained inappropriate behavior. 100 | 101 | **Consequence**: A temporary ban from any sort of interaction or public 102 | communication with the community for a specified period of time. No public or 103 | private interaction with the people involved, including unsolicited interaction 104 | with those enforcing the Code of Conduct, is allowed during this period. 105 | Violating these terms may lead to a permanent ban. 106 | 107 | ### 4. Permanent Ban 108 | 109 | **Community Impact**: Demonstrating a pattern of violation of community 110 | standards, including sustained inappropriate behavior, harassment of an 111 | individual, or aggression toward or disparagement of classes of individuals. 112 | 113 | **Consequence**: A permanent ban from any sort of public interaction within 114 | the community. 115 | 116 | ## Attribution 117 | 118 | This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), 119 | version 2.0, available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | For answers to common questions about this code of conduct, see the FAQ at 125 | https://www.contributor-covenant.org/faq. Translations are available at 126 | https://www.contributor-covenant.org/translations. 127 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | We're really glad you're reading this because we need volunteer developers to help this project come to fruition. 👏 4 | 5 | ## Instructions 6 | 7 | These steps will guide you through contributing to this project: 8 | 9 | - Fork the repo 10 | - Clone it and install dependencies 11 | 12 | ```shell 13 | git clone https://github.com/[YOUR-USERNAME]/material-ui-dropzone 14 | 15 | yarn install 16 | ``` 17 | 18 | - Launch the interactive docs by running 19 | 20 | ```shell 21 | yarn docs:dev 22 | ``` 23 | 24 | - Make and commit your changes 25 | - Make sure the command `yarn build` is working 26 | - Finally send a [GitHub Pull Request](https://github.com/Yuvaleros/material-ui-dropzone/compare) with a clear list of what you've done (read more [about pull requests](https://help.github.com/articles/about-pull-requests/)) 27 | 28 | ### Notes 29 | 30 | Make sure all of your commits are atomic (one feature per commit) and follows the [Gitmoji](https://gitmoji.carloscuesta.me) convention. 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Yuvaleros 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # material-ui-dropzone 2 | 3 | > Material-UI-dropzone is a [React](https://github.com/facebook/react) component using [Material-UI](https://github.com/mui-org/material-ui) and is based on the excellent [react-dropzone](https://github.com/react-dropzone/react-dropzone) library. 4 | 5 | [![License](https://img.shields.io/github/license/yuvaleros/material-ui-dropzone)](https://github.com/Yuvaleros/material-ui-dropzone/blob/master/LICENSE) [![All Contributors](https://img.shields.io/badge/all_contributors-9-orange.svg)](#contributors) 6 | 7 | 8 | [![Rebuild Dist Workflow Status](https://img.shields.io/github/workflow/status/yuvaleros/material-ui-dropzone/Rebuild%20Dist?label=build)](https://github.com/Yuvaleros/material-ui-dropzone/actions?query=workflow%3A%22Rebuild+Dist%22) [![Update Docs Workflow Status](https://img.shields.io/github/workflow/status/yuvaleros/material-ui-dropzone/Update%20Docs?label=docs)](https://github.com/Yuvaleros/material-ui-dropzone/actions?query=workflow%3A%22Update+Docs%22) 9 | 10 | [![npm package](https://img.shields.io/npm/v/material-ui-dropzone)](https://www.npmjs.com/package/material-ui-dropzone) [![npm downloads](https://img.shields.io/npm/dm/material-ui-dropzone.svg)](https://www.npmjs.com/package/material-ui-dropzone) 11 | 12 | This components provide either a file-upload dropzone or a file-upload dropzone inside of a dialog. 13 | 14 | The file-upload dropzone features some snazzy "File Allowed/Not Allowed" effects, previews and alerts. 15 | 16 | ## Installation 17 | 18 | ```shell 19 | npm install --save material-ui-dropzone 20 | ``` 21 | 22 | or 23 | 24 | ```shell 25 | yarn add material-ui-dropzone 26 | ``` 27 | 28 | ## Support 29 | 30 | `material-ui-dropzone` complies to the following support matrix. 31 | 32 | | version | React | Material-UI | 33 | | ------- | ---------------- | -------------- | 34 | | `3.x` | `16.8+` | `4.x` | 35 | | `2.x` | `15.x` or `16.x` | `3.x` or `4.x` | 36 | 37 | ## Screenshots 38 | 39 | This is the Dialog component: 40 | 41 | ![Dialog](https://raw.githubusercontent.com/Yuvaleros/material-ui-dropzone/master/pics/demo_pic.jpg) 42 | ![Dialog with Previews](https://raw.githubusercontent.com/Yuvaleros/material-ui-dropzone/master/pics/demo_pic5.JPG) 43 | 44 | When you drag a file onto the dropzone, you get a neat effect: 45 | 46 | ![Drag Overlay](https://raw.githubusercontent.com/Yuvaleros/material-ui-dropzone/master/pics/demo_pic2.JPG) 47 | 48 | And if you drag in a wrong type of file, you'll get yelled at: 49 | 50 | ![Drag Error Overlay](https://raw.githubusercontent.com/Yuvaleros/material-ui-dropzone/master/pics/demo_pic4.JPG) 51 | 52 | **N.B. This has some limitations (see [here](https://github.com/react-dropzone/react-dropzone/tree/master/examples/accept#browser-limitations) for more details).** 53 | 54 | ## Documentation and Examples 55 | 56 | See [https://yuvaleros.github.io/material-ui-dropzone](https://yuvaleros.github.io/material-ui-dropzone) for Documentation and Examples. 57 | 58 | ## Components 59 | 60 | ### DropzoneArea 61 | 62 | This components creates the dropzone, previews and snackbar notifications without a dialog 63 | 64 | ```jsx static 65 | import React, {Component} from 'react' 66 | import {DropzoneArea} from 'material-ui-dropzone' 67 | 68 | class DropzoneAreaExample extends Component{ 69 | constructor(props){ 70 | super(props); 71 | this.state = { 72 | files: [] 73 | }; 74 | } 75 | handleChange(files){ 76 | this.setState({ 77 | files: files 78 | }); 79 | } 80 | render(){ 81 | return ( 82 | 85 | ) 86 | } 87 | } 88 | 89 | export default DropzoneAreaExample; 90 | ``` 91 | 92 | ### DropzoneDialog 93 | 94 | This component provides the DropzoneArea inside of a MaterialUI Dialog. 95 | 96 | ```jsx static 97 | import React, { Component } from 'react' 98 | import {DropzoneDialog} from 'material-ui-dropzone' 99 | import Button from '@mui/material/Button'; 100 | 101 | export default class DropzoneDialogExample extends Component { 102 | constructor(props) { 103 | super(props); 104 | this.state = { 105 | open: false, 106 | files: [] 107 | }; 108 | } 109 | 110 | handleClose() { 111 | this.setState({ 112 | open: false 113 | }); 114 | } 115 | 116 | handleSave(files) { 117 | //Saving files to state for further use and closing Modal. 118 | this.setState({ 119 | files: files, 120 | open: false 121 | }); 122 | } 123 | 124 | handleOpen() { 125 | this.setState({ 126 | open: true, 127 | }); 128 | } 129 | 130 | render() { 131 | return ( 132 |
133 | 136 | 144 |
145 | ); 146 | } 147 | } 148 | ``` 149 | 150 | ## License 151 | 152 | MIT 153 | 154 | ## Contributors 155 | 156 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 |

Yuvaleros

🤔 💻 🎨 📖 💬 👀 🚧

Mattia Panzeri

🤔 💻 🎨 📖 💡 🚇 🐛 💬 👀 🚧

Max Carroll

🤔 💻 🎨 💡 👀

Matthew Corner

🐛 🤔 💻

Barry Loong

🤔 💻

JF Blouin

🤔 💻

Anthony Raymond

💻 💡

isaacbuckman

🐛 💻 💡

MatthijsMud

🐛 💻
178 | 179 | 180 | 181 | 182 | 183 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! 184 | -------------------------------------------------------------------------------- /dist/index.d.ts: -------------------------------------------------------------------------------- 1 | import { ChipProps } from '@mui/material/Chip'; 2 | import { DialogProps } from '@mui/material/Dialog'; 3 | import { GridProps } from '@mui/material/Grid'; 4 | import { SnackbarProps } from '@mui/material/Snackbar'; 5 | import * as React from 'react'; 6 | import { DropEvent, DropzoneProps } from 'react-dropzone'; 7 | 8 | type Omit = Pick>; 9 | 10 | export interface FileObject { 11 | readonly file: File; 12 | readonly data: string | ArrayBuffer | null; 13 | } 14 | 15 | export interface PreviewIconProps { 16 | readonly classes: string; 17 | } 18 | 19 | export type AlertType = 'error' | 'success' | 'info'; 20 | 21 | // DropzoneAreaBase 22 | 23 | export type DropzoneAreaBaseClasses = { 24 | /** Material-UI class applied to the root Dropzone div */ 25 | root: string; 26 | /** Material-UI class applied to the Dropzone when 'active' */ 27 | active: string; 28 | /** Material-UI class applied to the Dropzone when 'invalid' */ 29 | invalid: string; 30 | /** Material-UI class applied to the Dropzone text container div */ 31 | textContainer: string; 32 | /** Material-UI class applied to the Dropzone text */ 33 | text: string; 34 | /** Material-UI class applied to the Dropzone icon */ 35 | icon: string; 36 | }; 37 | 38 | export type DropzoneAreaBaseProps = { 39 | classes?: Partial; 40 | acceptedFiles?: string[]; 41 | fileObjects: FileObject[]; 42 | filesLimit?: number; 43 | Icon?: React.ComponentType; 44 | maxFileSize?: number; 45 | dropzoneText?: string; 46 | previewText?: string; 47 | showPreviews?: boolean; 48 | showPreviewsInDropzone?: boolean; 49 | showFileNamesInPreview?: boolean; 50 | showFileNames?: boolean; 51 | useChipsForPreview?: boolean; 52 | previewChipProps?: ChipProps; 53 | previewGridClasses?: { 54 | container?: string; 55 | item?: string; 56 | image?: string; 57 | }; 58 | previewGridProps?: { 59 | container?: GridProps; 60 | item?: GridProps; 61 | }; 62 | showAlerts?: boolean | AlertType[]; 63 | alertSnackbarProps?: SnackbarProps; 64 | dropzoneProps?: DropzoneProps; 65 | inputProps?: React.HTMLProps; 66 | clearOnUnmount?: boolean; 67 | dropzoneClass?: string; 68 | dropzoneParagraphClass?: string; 69 | disableRejectionFeedback?: boolean; 70 | onAdd?: (newFiles: FileObject[]) => void; 71 | onDelete?: (deletedFileObject: FileObject, index: number) => void; 72 | onDrop?: (files: File[], event: DropEvent) => void; 73 | onDropRejected?: (files: File[], event: DropEvent) => void; 74 | onAlert?: (message: string, variant: AlertType) => void; 75 | getFileLimitExceedMessage?: (filesLimit: number) => string; 76 | getFileAddedMessage?: (fileName: string) => string; 77 | getFileRemovedMessage?: (fileName: string) => string; 78 | getDropRejectMessage?: ( 79 | rejectedFile: File, 80 | acceptedFiles: string[], 81 | maxFileSize: number 82 | ) => string; 83 | getPreviewIcon?: ( 84 | file: FileObject, 85 | classes: PreviewIconProps 86 | ) => React.ReactElement; 87 | }; 88 | 89 | export const DropzoneAreaBase: React.ComponentType; 90 | 91 | // DropzoneArea 92 | 93 | export type DropzoneAreaProps = Omit< 94 | DropzoneAreaBaseProps, 95 | 'fileObjects' | 'onAdd' | 'onDelete' 96 | > & { 97 | clearOnUnmount?: boolean; 98 | initialFiles?: (File | string)[]; 99 | onChange?: (files: File[]) => void; 100 | onDelete?: (file: File) => void; 101 | }; 102 | 103 | export const DropzoneArea: React.ComponentType; 104 | 105 | // DropzoneDialogBase 106 | 107 | export type DropzoneDialogBaseProps = DropzoneAreaBaseProps & { 108 | cancelButtonText?: string; 109 | dialogProps?: DialogProps; 110 | dialogTitle?: string | JSX.Element; 111 | fullWidth?: boolean; 112 | maxWidth?: string; 113 | onClose?: (event: React.SyntheticEvent) => void; 114 | onSave?: (event: React.SyntheticEvent) => void; 115 | open?: boolean; 116 | submitButtonText?: string; 117 | }; 118 | 119 | export const DropzoneDialogBase: React.ComponentType; 120 | 121 | // DropzoneDialog 122 | 123 | export type DropzoneDialogProps = Omit< 124 | DropzoneDialogBaseProps, 125 | 'fileObjects' | 'onAdd' | 'onDelete' | 'onSave' 126 | > & { 127 | clearOnUnmount?: boolean; 128 | initialFiles?: (File | string)[]; 129 | onSave?: (files: File[], event: React.SyntheticEvent) => void; 130 | onDelete?: (file: File) => void; 131 | }; 132 | 133 | export const DropzoneDialog: React.ComponentType; 134 | -------------------------------------------------------------------------------- /dist/index.es.js: -------------------------------------------------------------------------------- 1 | import _extends from '@babel/runtime/helpers/extends'; 2 | import _slicedToArray from '@babel/runtime/helpers/slicedToArray'; 3 | import _regeneratorRuntime from '@babel/runtime/regenerator'; 4 | import _asyncToGenerator from '@babel/runtime/helpers/asyncToGenerator'; 5 | import _classCallCheck from '@babel/runtime/helpers/classCallCheck'; 6 | import _createClass from '@babel/runtime/helpers/createClass'; 7 | import _inherits from '@babel/runtime/helpers/inherits'; 8 | import _possibleConstructorReturn from '@babel/runtime/helpers/possibleConstructorReturn'; 9 | import _getPrototypeOf from '@babel/runtime/helpers/getPrototypeOf'; 10 | import _objectWithoutProperties from '@babel/runtime/helpers/objectWithoutProperties'; 11 | import PropTypes from 'prop-types'; 12 | import { createElement, Fragment, isValidElement, PureComponent } from 'react'; 13 | import Snackbar from '@mui/material/Snackbar'; 14 | import Typography from '@mui/material/Typography'; 15 | import Button from '@material-ui/core/Button'; 16 | import { withStyles } from '@mui/styles'; 17 | import AttachFileIcon from '@mui/icons-material/AttachFile'; 18 | import CloudUploadIcon from '@mui/icons-material/CloudUpload'; 19 | import clsx from 'clsx'; 20 | import Dropzone from 'react-dropzone'; 21 | import Chip from '@mui/material/Chip'; 22 | import Fab from '@mui/material/Fab'; 23 | import Grid from '@mui/material/Grid'; 24 | import DeleteIcon from '@mui/icons-material/Delete'; 25 | import IconButton from '@mui/material/IconButton'; 26 | import SnackbarContent from '@mui/material/SnackbarContent'; 27 | import CheckCircleIcon from '@mui/icons-material/CheckCircle'; 28 | import CloseIcon from '@mui/icons-material/Close'; 29 | import ErrorIcon from '@mui/icons-material/Error'; 30 | import InfoIcon from '@mui/icons-material/Info'; 31 | import WarningIcon from '@mui/icons-material/Warning'; 32 | import Button$1 from '@mui/material/Button'; 33 | import Dialog from '@mui/material/Dialog'; 34 | import DialogActions from '@mui/material/DialogActions'; 35 | import DialogContent from '@mui/material/DialogContent'; 36 | import DialogTitle from '@mui/material/DialogTitle'; 37 | 38 | function isImage(file) { 39 | if (file.type.split('/')[0] === 'image') { 40 | return true; 41 | } 42 | } 43 | function convertBytesToMbsOrKbs(filesize) { 44 | var size = ''; 45 | 46 | if (filesize >= 1048576) { 47 | size = filesize / 1048576 + ' megabytes'; 48 | } else if (filesize >= 1024) { 49 | size = filesize / 1024 + ' kilobytes'; 50 | } else { 51 | size = filesize + ' bytes'; 52 | } 53 | 54 | return size; 55 | } 56 | function createFileFromUrl(_x) { 57 | return _createFileFromUrl.apply(this, arguments); 58 | } 59 | 60 | function _createFileFromUrl() { 61 | _createFileFromUrl = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee(url) { 62 | var response, data, metadata, filename; 63 | return _regeneratorRuntime.wrap(function _callee$(_context) { 64 | while (1) { 65 | switch (_context.prev = _context.next) { 66 | case 0: 67 | _context.next = 2; 68 | return fetch(url); 69 | 70 | case 2: 71 | response = _context.sent; 72 | _context.next = 5; 73 | return response.blob(); 74 | 75 | case 5: 76 | data = _context.sent; 77 | metadata = { 78 | type: data.type 79 | }; 80 | filename = url.replace(/\?.+/, '').split('/').pop(); 81 | return _context.abrupt("return", new File([data], filename, metadata)); 82 | 83 | case 9: 84 | case "end": 85 | return _context.stop(); 86 | } 87 | } 88 | }, _callee); 89 | })); 90 | return _createFileFromUrl.apply(this, arguments); 91 | } 92 | 93 | function readFile(file) { 94 | return new Promise(function (resolve, reject) { 95 | var reader = new FileReader(); 96 | 97 | reader.onload = function (event) { 98 | var _event$target; 99 | 100 | resolve(event === null || event === void 0 ? void 0 : (_event$target = event.target) === null || _event$target === void 0 ? void 0 : _event$target.result); 101 | }; 102 | 103 | reader.onerror = function (event) { 104 | reader.abort(); 105 | reject(event); 106 | }; 107 | 108 | reader.readAsDataURL(file); 109 | }); 110 | } 111 | 112 | var styles = function styles(_ref) { 113 | var palette = _ref.palette, 114 | shape = _ref.shape, 115 | spacing = _ref.spacing; 116 | return { 117 | root: {}, 118 | imageContainer: { 119 | position: 'relative', 120 | zIndex: 10, 121 | textAlign: 'center', 122 | '&:hover $image': { 123 | opacity: 0.3 124 | }, 125 | '&:hover $removeButton': { 126 | opacity: 1 127 | } 128 | }, 129 | image: { 130 | height: 100, 131 | width: 'initial', 132 | maxWidth: '100%', 133 | color: palette.text.primary, 134 | transition: 'all 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms', 135 | boxSizing: 'border-box', 136 | boxShadow: 'rgba(0, 0, 0, 0.12) 0 1px 6px, rgba(0, 0, 0, 0.12) 0 1px 4px', 137 | borderRadius: shape.borderRadius, 138 | zIndex: 5, 139 | opacity: 1 140 | }, 141 | removeButton: { 142 | transition: '.5s ease', 143 | position: 'absolute', 144 | opacity: 0, 145 | top: spacing(-1), 146 | right: spacing(-1), 147 | width: 40, 148 | height: 40, 149 | '&:focus': { 150 | opacity: 1 151 | } 152 | } 153 | }; 154 | }; 155 | 156 | var _ref3 = /*#__PURE__*/createElement(DeleteIcon, null); 157 | 158 | function PreviewList(_ref2) { 159 | var fileObjects = _ref2.fileObjects, 160 | handleRemove = _ref2.handleRemove, 161 | showFileNames = _ref2.showFileNames, 162 | useChipsForPreview = _ref2.useChipsForPreview, 163 | previewChipProps = _ref2.previewChipProps, 164 | previewGridClasses = _ref2.previewGridClasses, 165 | previewGridProps = _ref2.previewGridProps, 166 | classes = _ref2.classes, 167 | getPreviewIcon = _ref2.getPreviewIcon; 168 | 169 | if (useChipsForPreview) { 170 | return /*#__PURE__*/createElement(Grid, _extends({ 171 | spacing: 1, 172 | direction: "row" 173 | }, previewGridProps.container, { 174 | container: true, 175 | className: clsx(classes.root, previewGridClasses.container) 176 | }), fileObjects.map(function (fileObject, i) { 177 | var _fileObject$file$name, _fileObject$file; 178 | 179 | return /*#__PURE__*/createElement(Grid, _extends({}, previewGridProps.item, { 180 | item: true, 181 | key: "".concat((_fileObject$file$name = (_fileObject$file = fileObject.file) === null || _fileObject$file === void 0 ? void 0 : _fileObject$file.name) !== null && _fileObject$file$name !== void 0 ? _fileObject$file$name : 'file', "-").concat(i), 182 | className: classes.imageContainer 183 | }), /*#__PURE__*/createElement(Chip, _extends({ 184 | variant: "outlined" 185 | }, previewChipProps, { 186 | label: fileObject.file.name, 187 | onDelete: handleRemove(i) 188 | }))); 189 | })); 190 | } 191 | 192 | return /*#__PURE__*/createElement(Grid, _extends({ 193 | spacing: 8 194 | }, previewGridProps.container, { 195 | container: true, 196 | className: clsx(classes.root, previewGridClasses.container) 197 | }), fileObjects.map(function (fileObject, i) { 198 | var _fileObject$file$name2, _fileObject$file2; 199 | 200 | return /*#__PURE__*/createElement(Grid, _extends({ 201 | xs: 4 202 | }, previewGridProps.item, { 203 | item: true, 204 | key: "".concat((_fileObject$file$name2 = (_fileObject$file2 = fileObject.file) === null || _fileObject$file2 === void 0 ? void 0 : _fileObject$file2.name) !== null && _fileObject$file$name2 !== void 0 ? _fileObject$file$name2 : 'file', "-").concat(i), 205 | className: clsx(classes.imageContainer, previewGridClasses.item) 206 | }), getPreviewIcon(fileObject, classes), showFileNames && /*#__PURE__*/createElement(Typography, { 207 | variant: "body1", 208 | component: "p" 209 | }, fileObject.file.name), /*#__PURE__*/createElement(Fab, { 210 | onClick: handleRemove(i), 211 | "aria-label": "Delete", 212 | className: classes.removeButton 213 | }, _ref3)); 214 | })); 215 | } 216 | 217 | process.env.NODE_ENV !== "production" ? PreviewList.propTypes = { 218 | classes: PropTypes.object.isRequired, 219 | fileObjects: PropTypes.arrayOf(PropTypes.object).isRequired, 220 | getPreviewIcon: PropTypes.func.isRequired, 221 | handleRemove: PropTypes.func.isRequired, 222 | previewChipProps: PropTypes.object, 223 | previewGridClasses: PropTypes.object, 224 | previewGridProps: PropTypes.object, 225 | showFileNames: PropTypes.bool, 226 | useChipsForPreview: PropTypes.bool 227 | } : void 0; 228 | var PreviewList$1 = withStyles(styles, { 229 | name: 'MuiDropzonePreviewList' 230 | })(PreviewList); 231 | 232 | var variantIcon = { 233 | success: CheckCircleIcon, 234 | warning: WarningIcon, 235 | error: ErrorIcon, 236 | info: InfoIcon 237 | }; 238 | 239 | var styles$1 = function styles(theme) { 240 | return { 241 | successAlert: { 242 | backgroundColor: theme.palette.success.main 243 | }, 244 | errorAlert: { 245 | backgroundColor: theme.palette.error.main 246 | }, 247 | infoAlert: { 248 | backgroundColor: theme.palette.info.main 249 | }, 250 | warningAlert: { 251 | backgroundColor: theme.palette.warning.main 252 | }, 253 | message: { 254 | display: 'flex', 255 | alignItems: 'center', 256 | '& > svg': { 257 | marginRight: theme.spacing(1) 258 | } 259 | }, 260 | icon: { 261 | fontSize: 20, 262 | opacity: 0.9 263 | }, 264 | closeButton: {} 265 | }; 266 | }; 267 | 268 | function SnackbarContentWrapper(props) { 269 | var classes = props.classes, 270 | className = props.className, 271 | message = props.message, 272 | onClose = props.onClose, 273 | variant = props.variant, 274 | other = _objectWithoutProperties(props, ["classes", "className", "message", "onClose", "variant"]); 275 | 276 | var Icon = variantIcon[variant]; 277 | return /*#__PURE__*/createElement(SnackbarContent, _extends({ 278 | className: clsx(classes["".concat(variant, "Alert")], className), 279 | "aria-describedby": "client-snackbar", 280 | message: /*#__PURE__*/createElement("span", { 281 | id: "client-snackbar", 282 | className: classes.message 283 | }, /*#__PURE__*/createElement(Icon, { 284 | className: classes.icon 285 | }), message), 286 | action: [/*#__PURE__*/createElement(IconButton, { 287 | key: "close", 288 | "aria-label": "Close", 289 | color: "inherit", 290 | className: classes.closeButton, 291 | onClick: onClose 292 | }, /*#__PURE__*/createElement(CloseIcon, { 293 | className: classes.icon 294 | }))] 295 | }, other)); 296 | } 297 | 298 | process.env.NODE_ENV !== "production" ? SnackbarContentWrapper.propTypes = { 299 | classes: PropTypes.object.isRequired, 300 | className: PropTypes.string, 301 | message: PropTypes.node, 302 | onClose: PropTypes.func, 303 | variant: PropTypes.oneOf(['success', 'warning', 'error', 'info']).isRequired 304 | } : void 0; 305 | var SnackbarContentWrapper$1 = withStyles(styles$1, { 306 | name: 'MuiDropzoneSnackbar' 307 | })(SnackbarContentWrapper); 308 | 309 | function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } 310 | 311 | function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } } 312 | 313 | var styles$2 = function styles(_ref) { 314 | var palette = _ref.palette, 315 | shape = _ref.shape, 316 | spacing = _ref.spacing; 317 | return { 318 | '@keyframes progress': { 319 | '0%': { 320 | backgroundPosition: '0 0' 321 | }, 322 | '100%': { 323 | backgroundPosition: '-70px 0' 324 | } 325 | }, 326 | root: { 327 | position: 'relative', 328 | width: '100%', 329 | minHeight: '250px', 330 | backgroundColor: palette.background.paper, 331 | border: 'dashed', 332 | borderColor: palette.divider, 333 | borderRadius: shape.borderRadius, 334 | boxSizing: 'border-box', 335 | cursor: 'pointer', 336 | overflow: 'hidden' 337 | }, 338 | active: { 339 | animation: '$progress 2s linear infinite !important', 340 | // eslint-disable-next-line max-len 341 | backgroundImage: "repeating-linear-gradient(-45deg, ".concat(palette.background.paper, ", ").concat(palette.background.paper, " 25px, ").concat(palette.divider, " 25px, ").concat(palette.divider, " 50px)"), 342 | backgroundSize: '150% 100%', 343 | border: 'solid', 344 | borderColor: palette.primary.light 345 | }, 346 | invalid: { 347 | // eslint-disable-next-line max-len 348 | backgroundImage: "repeating-linear-gradient(-45deg, ".concat(palette.error.light, ", ").concat(palette.error.light, " 25px, ").concat(palette.error.dark, " 25px, ").concat(palette.error.dark, " 50px)"), 349 | borderColor: palette.error.main 350 | }, 351 | textContainer: { 352 | textAlign: 'center' 353 | }, 354 | text: { 355 | marginBottom: spacing(3), 356 | marginTop: spacing(3) 357 | }, 358 | icon: { 359 | width: 51, 360 | height: 51, 361 | color: palette.text.primary 362 | }, 363 | resetButton: { 364 | display: 'block', 365 | margin: '10px 0' 366 | } 367 | }; 368 | }; 369 | 370 | var defaultSnackbarAnchorOrigin = { 371 | horizontal: 'left', 372 | vertical: 'bottom' 373 | }; 374 | 375 | var defaultGetPreviewIcon = function defaultGetPreviewIcon(fileObject, classes) { 376 | if (isImage(fileObject.file)) { 377 | return /*#__PURE__*/createElement("img", { 378 | className: classes.image, 379 | role: "presentation", 380 | src: fileObject.data 381 | }); 382 | } 383 | 384 | return /*#__PURE__*/createElement(AttachFileIcon, { 385 | className: classes.image 386 | }); 387 | }; 388 | /** 389 | * This components creates a Material-UI Dropzone, with previews and snackbar notifications. 390 | */ 391 | 392 | 393 | var DropzoneAreaBase = /*#__PURE__*/function (_React$PureComponent) { 394 | _inherits(DropzoneAreaBase, _React$PureComponent); 395 | 396 | var _super = _createSuper(DropzoneAreaBase); 397 | 398 | function DropzoneAreaBase() { 399 | var _this; 400 | 401 | _classCallCheck(this, DropzoneAreaBase); 402 | 403 | for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { 404 | args[_key] = arguments[_key]; 405 | } 406 | 407 | _this = _super.call.apply(_super, [this].concat(args)); 408 | _this.state = { 409 | openSnackBar: false, 410 | snackbarMessage: '', 411 | snackbarVariant: 'success' 412 | }; 413 | 414 | _this.handleDropAccepted = /*#__PURE__*/function () { 415 | var _ref2 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee2(acceptedFiles, evt) { 416 | var _this$props, fileObjects, filesLimit, getFileAddedMessage, getFileLimitExceedMessage, onAdd, onDrop, fileObjs, message; 417 | 418 | return _regeneratorRuntime.wrap(function _callee2$(_context2) { 419 | while (1) { 420 | switch (_context2.prev = _context2.next) { 421 | case 0: 422 | _this$props = _this.props, fileObjects = _this$props.fileObjects, filesLimit = _this$props.filesLimit, getFileAddedMessage = _this$props.getFileAddedMessage, getFileLimitExceedMessage = _this$props.getFileLimitExceedMessage, onAdd = _this$props.onAdd, onDrop = _this$props.onDrop; 423 | 424 | if (!(filesLimit > 1 && fileObjects.length + acceptedFiles.length > filesLimit)) { 425 | _context2.next = 4; 426 | break; 427 | } 428 | 429 | _this.setState({ 430 | openSnackBar: true, 431 | snackbarMessage: getFileLimitExceedMessage(filesLimit), 432 | snackbarVariant: 'error' 433 | }, _this.notifyAlert); 434 | 435 | return _context2.abrupt("return"); 436 | 437 | case 4: 438 | // Notify Drop event 439 | if (onDrop) { 440 | onDrop(acceptedFiles, evt); 441 | } // Retrieve fileObjects data 442 | 443 | 444 | _context2.next = 7; 445 | return Promise.all(acceptedFiles.map( /*#__PURE__*/function () { 446 | var _ref3 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee(file) { 447 | var data; 448 | return _regeneratorRuntime.wrap(function _callee$(_context) { 449 | while (1) { 450 | switch (_context.prev = _context.next) { 451 | case 0: 452 | _context.next = 2; 453 | return readFile(file); 454 | 455 | case 2: 456 | data = _context.sent; 457 | return _context.abrupt("return", { 458 | file: file, 459 | data: data 460 | }); 461 | 462 | case 4: 463 | case "end": 464 | return _context.stop(); 465 | } 466 | } 467 | }, _callee); 468 | })); 469 | 470 | return function (_x3) { 471 | return _ref3.apply(this, arguments); 472 | }; 473 | }())); 474 | 475 | case 7: 476 | fileObjs = _context2.sent; 477 | 478 | // Notify added files 479 | if (onAdd) { 480 | onAdd(fileObjs); 481 | } // Display message 482 | 483 | 484 | message = fileObjs.reduce(function (msg, fileObj) { 485 | return msg + getFileAddedMessage(fileObj.file.name); 486 | }, ''); 487 | 488 | _this.setState({ 489 | openSnackBar: true, 490 | snackbarMessage: message, 491 | snackbarVariant: 'success' 492 | }, _this.notifyAlert); 493 | 494 | case 11: 495 | case "end": 496 | return _context2.stop(); 497 | } 498 | } 499 | }, _callee2); 500 | })); 501 | 502 | return function (_x, _x2) { 503 | return _ref2.apply(this, arguments); 504 | }; 505 | }(); 506 | 507 | _this.handleDropRejected = function (rejectedFiles, evt) { 508 | var _this$props2 = _this.props, 509 | acceptedFiles = _this$props2.acceptedFiles, 510 | filesLimit = _this$props2.filesLimit, 511 | fileObjects = _this$props2.fileObjects, 512 | getDropRejectMessage = _this$props2.getDropRejectMessage, 513 | getFileLimitExceedMessage = _this$props2.getFileLimitExceedMessage, 514 | maxFileSize = _this$props2.maxFileSize, 515 | onDropRejected = _this$props2.onDropRejected; 516 | var message = ''; 517 | 518 | if (fileObjects.length + rejectedFiles.length > filesLimit) { 519 | message = getFileLimitExceedMessage(filesLimit); 520 | } else { 521 | rejectedFiles.forEach(function (rejectedFile) { 522 | message = getDropRejectMessage(rejectedFile, acceptedFiles, maxFileSize); 523 | }); 524 | } 525 | 526 | if (onDropRejected) { 527 | onDropRejected(rejectedFiles, evt); 528 | } 529 | 530 | _this.setState({ 531 | openSnackBar: true, 532 | snackbarMessage: message, 533 | snackbarVariant: 'error' 534 | }, _this.notifyAlert); 535 | }; 536 | 537 | _this.handleRemove = function (fileIndex) { 538 | return function (event) { 539 | event.stopPropagation(); 540 | var _this$props3 = _this.props, 541 | fileObjects = _this$props3.fileObjects, 542 | getFileRemovedMessage = _this$props3.getFileRemovedMessage, 543 | onDelete = _this$props3.onDelete; // Find removed fileObject 544 | 545 | var removedFileObj = fileObjects[fileIndex]; // Notify removed file 546 | 547 | if (onDelete) { 548 | onDelete(removedFileObj, fileIndex); 549 | } 550 | 551 | _this.setState({ 552 | openSnackBar: true, 553 | snackbarMessage: getFileRemovedMessage(removedFileObj.file.name), 554 | snackbarVariant: 'info' 555 | }, _this.notifyAlert); 556 | }; 557 | }; 558 | 559 | _this.handleCloseSnackbar = function () { 560 | _this.setState({ 561 | openSnackBar: false 562 | }); 563 | }; 564 | 565 | return _this; 566 | } 567 | 568 | _createClass(DropzoneAreaBase, [{ 569 | key: "notifyAlert", 570 | value: function notifyAlert() { 571 | var onAlert = this.props.onAlert; 572 | var _this$state = this.state, 573 | openSnackBar = _this$state.openSnackBar, 574 | snackbarMessage = _this$state.snackbarMessage, 575 | snackbarVariant = _this$state.snackbarVariant; 576 | 577 | if (openSnackBar && onAlert) { 578 | onAlert(snackbarMessage, snackbarVariant); 579 | } 580 | } 581 | }, { 582 | key: "render", 583 | value: function render() { 584 | var _this2 = this; 585 | 586 | var _this$props4 = this.props, 587 | acceptedFiles = _this$props4.acceptedFiles, 588 | alertSnackbarProps = _this$props4.alertSnackbarProps, 589 | classes = _this$props4.classes, 590 | disableRejectionFeedback = _this$props4.disableRejectionFeedback, 591 | dropzoneClass = _this$props4.dropzoneClass, 592 | dropzoneParagraphClass = _this$props4.dropzoneParagraphClass, 593 | dropzoneProps = _this$props4.dropzoneProps, 594 | dropzoneText = _this$props4.dropzoneText, 595 | fileObjects = _this$props4.fileObjects, 596 | filesLimit = _this$props4.filesLimit, 597 | getPreviewIcon = _this$props4.getPreviewIcon, 598 | Icon = _this$props4.Icon, 599 | inputProps = _this$props4.inputProps, 600 | maxFileSize = _this$props4.maxFileSize, 601 | previewChipProps = _this$props4.previewChipProps, 602 | previewGridClasses = _this$props4.previewGridClasses, 603 | previewGridProps = _this$props4.previewGridProps, 604 | previewText = _this$props4.previewText, 605 | showAlerts = _this$props4.showAlerts, 606 | showFileNames = _this$props4.showFileNames, 607 | showFileNamesInPreview = _this$props4.showFileNamesInPreview, 608 | showPreviews = _this$props4.showPreviews, 609 | showPreviewsInDropzone = _this$props4.showPreviewsInDropzone, 610 | useChipsForPreview = _this$props4.useChipsForPreview, 611 | reset = _this$props4.reset; 612 | var _this$state2 = this.state, 613 | openSnackBar = _this$state2.openSnackBar, 614 | snackbarMessage = _this$state2.snackbarMessage, 615 | snackbarVariant = _this$state2.snackbarVariant; 616 | var acceptFiles = acceptedFiles === null || acceptedFiles === void 0 ? void 0 : acceptedFiles.join(','); 617 | var isMultiple = filesLimit > 1; 618 | var previewsVisible = showPreviews && fileObjects.length > 0; 619 | var previewsInDropzoneVisible = showPreviewsInDropzone && fileObjects.length > 0; 620 | return /*#__PURE__*/createElement(Fragment, null, /*#__PURE__*/createElement(Dropzone, _extends({}, dropzoneProps, { 621 | accept: acceptFiles, 622 | onDropAccepted: this.handleDropAccepted, 623 | onDropRejected: this.handleDropRejected, 624 | maxSize: maxFileSize, 625 | multiple: isMultiple 626 | }), function (_ref4) { 627 | var getRootProps = _ref4.getRootProps, 628 | getInputProps = _ref4.getInputProps, 629 | isDragActive = _ref4.isDragActive, 630 | isDragReject = _ref4.isDragReject; 631 | return /*#__PURE__*/createElement("div", getRootProps({ 632 | className: clsx(classes.root, dropzoneClass, isDragActive && classes.active, !disableRejectionFeedback && isDragReject && classes.invalid) 633 | }), /*#__PURE__*/createElement("input", getInputProps(inputProps)), /*#__PURE__*/createElement("div", { 634 | className: classes.textContainer 635 | }, /*#__PURE__*/createElement(Typography, { 636 | variant: "h5", 637 | component: "p", 638 | className: clsx(classes.text, dropzoneParagraphClass) 639 | }, dropzoneText), Icon ? /*#__PURE__*/createElement(Icon, { 640 | className: classes.icon 641 | }) : /*#__PURE__*/createElement(CloudUploadIcon, { 642 | className: classes.icon 643 | })), previewsInDropzoneVisible && /*#__PURE__*/createElement(PreviewList$1, { 644 | fileObjects: fileObjects, 645 | handleRemove: _this2.handleRemove, 646 | getPreviewIcon: getPreviewIcon, 647 | showFileNames: showFileNames, 648 | useChipsForPreview: useChipsForPreview, 649 | previewChipProps: previewChipProps, 650 | previewGridClasses: previewGridClasses, 651 | previewGridProps: previewGridProps 652 | })); 653 | }), reset && ( /*#__PURE__*/isValidElement(reset) ? reset : /*#__PURE__*/createElement(Button, { 654 | onClick: reset.onClick, 655 | variant: "outlined", 656 | className: classes.resetButton 657 | }, reset.text || 'reset')), previewsVisible && /*#__PURE__*/createElement(Fragment, null, /*#__PURE__*/createElement(Typography, { 658 | variant: "subtitle1", 659 | component: "span" 660 | }, previewText), /*#__PURE__*/createElement(PreviewList$1, { 661 | fileObjects: fileObjects, 662 | handleRemove: this.handleRemove, 663 | getPreviewIcon: getPreviewIcon, 664 | showFileNames: showFileNamesInPreview, 665 | useChipsForPreview: useChipsForPreview, 666 | previewChipProps: previewChipProps, 667 | previewGridClasses: previewGridClasses, 668 | previewGridProps: previewGridProps 669 | })), (typeof showAlerts === 'boolean' && showAlerts || Array.isArray(showAlerts) && showAlerts.includes(snackbarVariant)) && /*#__PURE__*/createElement(Snackbar, _extends({ 670 | anchorOrigin: defaultSnackbarAnchorOrigin, 671 | autoHideDuration: 6000 672 | }, alertSnackbarProps, { 673 | open: openSnackBar, 674 | onClose: this.handleCloseSnackbar 675 | }), /*#__PURE__*/createElement(SnackbarContentWrapper$1, { 676 | onClose: this.handleCloseSnackbar, 677 | variant: snackbarVariant, 678 | message: snackbarMessage 679 | }))); 680 | } 681 | }]); 682 | 683 | return DropzoneAreaBase; 684 | }(PureComponent); 685 | 686 | DropzoneAreaBase.defaultProps = { 687 | acceptedFiles: [], 688 | filesLimit: 3, 689 | fileObjects: [], 690 | maxFileSize: 3000000, 691 | dropzoneText: 'Drag and drop a file here or click', 692 | previewText: 'Preview:', 693 | disableRejectionFeedback: false, 694 | showPreviews: false, 695 | // By default previews show up under in the dialog and inside in the standalone 696 | showPreviewsInDropzone: true, 697 | showFileNames: false, 698 | showFileNamesInPreview: false, 699 | useChipsForPreview: false, 700 | previewChipProps: {}, 701 | previewGridClasses: {}, 702 | previewGridProps: {}, 703 | reset: undefined, 704 | showAlerts: true, 705 | alertSnackbarProps: { 706 | anchorOrigin: { 707 | horizontal: 'left', 708 | vertical: 'bottom' 709 | }, 710 | autoHideDuration: 6000 711 | }, 712 | getFileLimitExceedMessage: function getFileLimitExceedMessage(filesLimit) { 713 | return "Maximum allowed number of files exceeded. Only ".concat(filesLimit, " allowed"); 714 | }, 715 | getFileAddedMessage: function getFileAddedMessage(fileName) { 716 | return "File ".concat(fileName, " successfully added."); 717 | }, 718 | getPreviewIcon: defaultGetPreviewIcon, 719 | getFileRemovedMessage: function getFileRemovedMessage(fileName) { 720 | return "File ".concat(fileName, " removed."); 721 | }, 722 | getDropRejectMessage: function getDropRejectMessage(rejectedFile, acceptedFiles, maxFileSize) { 723 | var message = "File ".concat(rejectedFile.name, " was rejected. "); 724 | 725 | if (!acceptedFiles.includes(rejectedFile.type)) { 726 | message += 'File type not supported. '; 727 | } 728 | 729 | if (rejectedFile.size > maxFileSize) { 730 | message += 'File is too big. Size limit is ' + convertBytesToMbsOrKbs(maxFileSize) + '. '; 731 | } 732 | 733 | return message; 734 | } 735 | }; 736 | var FileObjectShape = PropTypes.shape({ 737 | file: PropTypes.object, 738 | data: PropTypes.any 739 | }); 740 | process.env.NODE_ENV !== "production" ? DropzoneAreaBase.propTypes = { 741 | /** @ignore */ 742 | classes: PropTypes.object.isRequired, 743 | 744 | /** A list of file types to accept. 745 | * @see See [here](https://react-dropzone.js.org/#section-accepting-specific-file-types) for more details. 746 | */ 747 | acceptedFiles: PropTypes.arrayOf(PropTypes.string), 748 | 749 | /** Maximum number of files that can be loaded into the dropzone. */ 750 | filesLimit: PropTypes.number, 751 | 752 | /** Icon to be displayed inside the dropzone area. */ 753 | Icon: PropTypes.elementType, 754 | 755 | /** Currently loaded files. */ 756 | fileObjects: PropTypes.arrayOf(FileObjectShape), 757 | 758 | /** Maximum file size (in bytes) that the dropzone will accept. */ 759 | maxFileSize: PropTypes.number, 760 | 761 | /** Text inside the dropzone. */ 762 | dropzoneText: PropTypes.string, 763 | 764 | /** Custom CSS class name for dropzone container. */ 765 | dropzoneClass: PropTypes.string, 766 | 767 | /** Custom CSS class name for text inside the container. */ 768 | dropzoneParagraphClass: PropTypes.string, 769 | 770 | /** Disable feedback effect when dropping rejected files. */ 771 | disableRejectionFeedback: PropTypes.bool, 772 | 773 | /** Shows previews **BELOW** the dropzone. */ 774 | showPreviews: PropTypes.bool, 775 | 776 | /** Shows preview **INSIDE** the dropzone area. */ 777 | showPreviewsInDropzone: PropTypes.bool, 778 | 779 | /** Shows file name under the dropzone image. */ 780 | showFileNames: PropTypes.bool, 781 | 782 | /** Shows file name under the image. */ 783 | showFileNamesInPreview: PropTypes.bool, 784 | 785 | /** Uses deletable Material-UI Chip components to display file names. */ 786 | useChipsForPreview: PropTypes.bool, 787 | 788 | /** 789 | * Props to pass to the Material-UI Chip components.
Requires `useChipsForPreview` prop to be `true`. 790 | * 791 | * @see See [Material-UI Chip](https://material-ui.com/api/chip/#props) for available values. 792 | */ 793 | previewChipProps: PropTypes.object, 794 | 795 | /** 796 | * Custom CSS classNames for preview Grid components.
797 | * Should be in the form {container: string, item: string, image: string}. 798 | */ 799 | previewGridClasses: PropTypes.object, 800 | 801 | /** 802 | * Props to pass to the Material-UI Grid components.
803 | * Should be in the form {container: GridProps, item: GridProps}. 804 | * 805 | * @see See [Material-UI Grid](https://material-ui.com/api/grid/#props) for available GridProps values. 806 | */ 807 | previewGridProps: PropTypes.object, 808 | 809 | /** The label for the file preview section. */ 810 | previewText: PropTypes.string, 811 | 812 | /** 813 | * The node of button to clear dropzone. 814 | * 815 | * - can be a node to mount in a placeholder. 816 | * - can be an object: 817 | * - text (string) - text of the button 818 | * - onClick (function) - callback fired when reset button clicked 819 | */ 820 | reset: PropTypes.oneOfType([PropTypes.node, PropTypes.shape({ 821 | text: PropTypes.string, 822 | onClick: PropTypes.func 823 | })]), 824 | 825 | /** 826 | * Shows styled Material-UI Snackbar when files are dropped, deleted or rejected. 827 | * 828 | * - can be a boolean ("global" `true` or `false` for all alerts). 829 | * - can be an array, with values 'error', 'info', 'success' to select to view only certain alerts: 830 | * - showAlerts={['error']} for only errors. 831 | * - showAlerts={['error', 'info']} for both errors and info. 832 | * - showAlerts={['error', 'success', 'info']} is same as showAlerts={true}. 833 | * - showAlerts={[]} is same as showAlerts={false}. 834 | */ 835 | showAlerts: PropTypes.oneOfType([PropTypes.bool, PropTypes.arrayOf(PropTypes.oneOf(['error', 'success', 'info']))]), 836 | 837 | /** 838 | * Props to pass to the Material-UI Snackbar components.
Requires `showAlerts` prop to be `true`. 839 | * 840 | * @see See [Material-UI Snackbar](https://material-ui.com/api/snackbar/#props) for available values. 841 | */ 842 | alertSnackbarProps: PropTypes.object, 843 | 844 | /** 845 | * Props to pass to the Dropzone component. 846 | * 847 | * @see See [Dropzone props](https://react-dropzone.js.org/#src) for available values. 848 | */ 849 | dropzoneProps: PropTypes.object, 850 | 851 | /** 852 | * Attributes applied to the input element. 853 | * 854 | * @see See [MDN Input File attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#Additional_attributes) for available values. 855 | */ 856 | inputProps: PropTypes.object, 857 | 858 | /** 859 | * Get alert message to display when files limit is exceed. 860 | * 861 | * *Default*: "Maximum allowed number of files exceeded. Only ${filesLimit} allowed" 862 | * 863 | * @param {number} filesLimit The `filesLimit` currently set for the component. 864 | */ 865 | getFileLimitExceedMessage: PropTypes.func, 866 | 867 | /** 868 | * Get alert message to display when a new file is added. 869 | * 870 | * *Default*: "File ${fileName} successfully added." 871 | * 872 | * @param {string} fileName The newly added file name. 873 | */ 874 | getFileAddedMessage: PropTypes.func, 875 | 876 | /** 877 | * Get alert message to display when a file is removed. 878 | * 879 | * *Default*: "File ${fileName} removed." 880 | * 881 | * @param {string} fileName The name of the removed file. 882 | */ 883 | getFileRemovedMessage: PropTypes.func, 884 | 885 | /** 886 | * Get alert message to display when a file is rejected onDrop. 887 | * 888 | * *Default*: "File ${rejectedFile.name} was rejected." 889 | * 890 | * @param {Object} rejectedFile The file that got rejected 891 | * @param {string[]} acceptedFiles The `acceptedFiles` prop currently set for the component 892 | * @param {number} maxFileSize The `maxFileSize` prop currently set for the component 893 | */ 894 | getDropRejectMessage: PropTypes.func, 895 | 896 | /** 897 | * A function which determines which icon to display for a file preview. 898 | * 899 | * *Default*: If its an image then displays a preview the image, otherwise it will display an attachment icon 900 | * 901 | * @param {FileObject} objectFile The file which the preview will belong to 902 | * @param {Object} classes The classes for the file preview icon, in the default case we use the 'image' className. 903 | */ 904 | getPreviewIcon: PropTypes.func, 905 | 906 | /** 907 | * Fired when new files are added to dropzone. 908 | * 909 | * @param {FileObject[]} newFiles The new files added to the dropzone. 910 | */ 911 | onAdd: PropTypes.func, 912 | 913 | /** 914 | * Fired when a file is deleted from the previews panel. 915 | * 916 | * @param {FileObject} deletedFileObject The file that was removed. 917 | * @param {number} index The index of the removed file object. 918 | */ 919 | onDelete: PropTypes.func, 920 | 921 | /** 922 | * Fired when the user drops files into the dropzone. 923 | * 924 | * @param {File[]} droppedFiles All the files dropped into the dropzone. 925 | * @param {Event} event The react-dropzone drop event. 926 | */ 927 | onDrop: PropTypes.func, 928 | 929 | /** 930 | * Fired when a file is rejected because of wrong file type, size or goes beyond the filesLimit. 931 | * 932 | * @param {File[]} rejectedFiles All the rejected files. 933 | * @param {Event} event The react-dropzone drop event. 934 | */ 935 | onDropRejected: PropTypes.func, 936 | 937 | /** 938 | * Fired when an alert is triggered. 939 | * 940 | * @param {string} message Alert message. 941 | * @param {string} variant One of "error", "info", "success". 942 | */ 943 | onAlert: PropTypes.func 944 | } : void 0; 945 | var DropzoneAreaBase$1 = withStyles(styles$2, { 946 | name: 'MuiDropzoneArea' 947 | })(DropzoneAreaBase); 948 | 949 | function _createSuper$1(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct$1(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } 950 | 951 | function _isNativeReflectConstruct$1() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } } 952 | 953 | var splitDropzoneAreaProps = function splitDropzoneAreaProps(props) { 954 | var clearOnUnmount = props.clearOnUnmount, 955 | initialFiles = props.initialFiles, 956 | onChange = props.onChange, 957 | onDelete = props.onDelete, 958 | dropzoneAreaProps = _objectWithoutProperties(props, ["clearOnUnmount", "initialFiles", "onChange", "onDelete"]); 959 | 960 | return [{ 961 | clearOnUnmount: clearOnUnmount, 962 | initialFiles: initialFiles, 963 | onChange: onChange, 964 | onDelete: onDelete 965 | }, dropzoneAreaProps]; 966 | }; 967 | /** 968 | * This components creates an uncontrolled Material-UI Dropzone, with previews and snackbar notifications. 969 | * 970 | * It supports all props of `DropzoneAreaBase` but keeps the files state internally. 971 | * 972 | * **Note** To listen to file changes use `onChange` event handler and notice that `onDelete` returns a `File` instance instead of `FileObject`. 973 | */ 974 | 975 | 976 | var DropzoneArea = /*#__PURE__*/function (_React$PureComponent) { 977 | _inherits(DropzoneArea, _React$PureComponent); 978 | 979 | var _super = _createSuper$1(DropzoneArea); 980 | 981 | function DropzoneArea() { 982 | var _this; 983 | 984 | _classCallCheck(this, DropzoneArea); 985 | 986 | for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { 987 | args[_key] = arguments[_key]; 988 | } 989 | 990 | _this = _super.call.apply(_super, [this].concat(args)); 991 | _this.state = { 992 | fileObjects: [] 993 | }; 994 | 995 | _this.notifyFileChange = function () { 996 | var onChange = _this.props.onChange; 997 | var fileObjects = _this.state.fileObjects; 998 | 999 | if (onChange) { 1000 | onChange(fileObjects.map(function (fileObject) { 1001 | return fileObject.file; 1002 | })); 1003 | } 1004 | }; 1005 | 1006 | _this.loadInitialFiles = /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee2() { 1007 | var initialFiles, fileObjs; 1008 | return _regeneratorRuntime.wrap(function _callee2$(_context2) { 1009 | while (1) { 1010 | switch (_context2.prev = _context2.next) { 1011 | case 0: 1012 | initialFiles = _this.props.initialFiles; 1013 | _context2.prev = 1; 1014 | _context2.next = 4; 1015 | return Promise.all(initialFiles.map( /*#__PURE__*/function () { 1016 | var _ref2 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee(initialFile) { 1017 | var file, data; 1018 | return _regeneratorRuntime.wrap(function _callee$(_context) { 1019 | while (1) { 1020 | switch (_context.prev = _context.next) { 1021 | case 0: 1022 | if (!(typeof initialFile === 'string')) { 1023 | _context.next = 6; 1024 | break; 1025 | } 1026 | 1027 | _context.next = 3; 1028 | return createFileFromUrl(initialFile); 1029 | 1030 | case 3: 1031 | file = _context.sent; 1032 | _context.next = 7; 1033 | break; 1034 | 1035 | case 6: 1036 | file = initialFile; 1037 | 1038 | case 7: 1039 | _context.next = 9; 1040 | return readFile(file); 1041 | 1042 | case 9: 1043 | data = _context.sent; 1044 | return _context.abrupt("return", { 1045 | file: file, 1046 | data: data 1047 | }); 1048 | 1049 | case 11: 1050 | case "end": 1051 | return _context.stop(); 1052 | } 1053 | } 1054 | }, _callee); 1055 | })); 1056 | 1057 | return function (_x) { 1058 | return _ref2.apply(this, arguments); 1059 | }; 1060 | }())); 1061 | 1062 | case 4: 1063 | fileObjs = _context2.sent; 1064 | 1065 | _this.setState(function (state) { 1066 | return { 1067 | fileObjects: [].concat(state.fileObjects, fileObjs) 1068 | }; 1069 | }, _this.notifyFileChange); 1070 | 1071 | _context2.next = 11; 1072 | break; 1073 | 1074 | case 8: 1075 | _context2.prev = 8; 1076 | _context2.t0 = _context2["catch"](1); 1077 | console.log(_context2.t0); 1078 | 1079 | case 11: 1080 | case "end": 1081 | return _context2.stop(); 1082 | } 1083 | } 1084 | }, _callee2, null, [[1, 8]]); 1085 | })); 1086 | 1087 | _this.addFiles = /*#__PURE__*/function () { 1088 | var _ref3 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee3(newFileObjects) { 1089 | var filesLimit; 1090 | return _regeneratorRuntime.wrap(function _callee3$(_context3) { 1091 | while (1) { 1092 | switch (_context3.prev = _context3.next) { 1093 | case 0: 1094 | filesLimit = _this.props.filesLimit; // Update component state 1095 | 1096 | _this.setState(function (state) { 1097 | // Handle a single file 1098 | if (filesLimit <= 1) { 1099 | return { 1100 | fileObjects: [].concat(newFileObjects[0]) 1101 | }; 1102 | } // Handle multiple files 1103 | 1104 | 1105 | return { 1106 | fileObjects: [].concat(state.fileObjects, newFileObjects) 1107 | }; 1108 | }, _this.notifyFileChange); 1109 | 1110 | case 2: 1111 | case "end": 1112 | return _context3.stop(); 1113 | } 1114 | } 1115 | }, _callee3); 1116 | })); 1117 | 1118 | return function (_x2) { 1119 | return _ref3.apply(this, arguments); 1120 | }; 1121 | }(); 1122 | 1123 | _this.deleteFile = function (removedFileObj, removedFileObjIdx) { 1124 | event.stopPropagation(); 1125 | var onDelete = _this.props.onDelete; 1126 | var fileObjects = _this.state.fileObjects; // Calculate remaining fileObjects array 1127 | 1128 | var remainingFileObjs = fileObjects.filter(function (fileObject, i) { 1129 | return i !== removedFileObjIdx; 1130 | }); // Notify removed file 1131 | 1132 | if (onDelete) { 1133 | onDelete(removedFileObj.file, removedFileObjIdx); 1134 | } // Update local state 1135 | 1136 | 1137 | _this.setState({ 1138 | fileObjects: remainingFileObjs 1139 | }, _this.notifyFileChange); 1140 | }; 1141 | 1142 | return _this; 1143 | } 1144 | 1145 | _createClass(DropzoneArea, [{ 1146 | key: "componentDidMount", 1147 | value: function componentDidMount() { 1148 | this.loadInitialFiles(); 1149 | } 1150 | }, { 1151 | key: "componentWillUnmount", 1152 | value: function componentWillUnmount() { 1153 | var clearOnUnmount = this.props.clearOnUnmount; 1154 | 1155 | if (clearOnUnmount) { 1156 | this.setState({ 1157 | fileObjects: [] 1158 | }, this.notifyFileChange); 1159 | } 1160 | } 1161 | }, { 1162 | key: "render", 1163 | value: function render() { 1164 | var _splitDropzoneAreaPro = splitDropzoneAreaProps(this.props), 1165 | _splitDropzoneAreaPro2 = _slicedToArray(_splitDropzoneAreaPro, 2), 1166 | dropzoneAreaProps = _splitDropzoneAreaPro2[1]; 1167 | 1168 | var fileObjects = this.state.fileObjects; 1169 | return /*#__PURE__*/createElement(DropzoneAreaBase$1, _extends({}, dropzoneAreaProps, { 1170 | fileObjects: fileObjects, 1171 | onAdd: this.addFiles, 1172 | onDelete: this.deleteFile 1173 | })); 1174 | } 1175 | }]); 1176 | 1177 | return DropzoneArea; 1178 | }(PureComponent); 1179 | 1180 | DropzoneArea.defaultProps = { 1181 | clearOnUnmount: true, 1182 | filesLimit: 3, 1183 | initialFiles: [] 1184 | }; 1185 | process.env.NODE_ENV !== "production" ? DropzoneArea.propTypes = _extends({}, DropzoneAreaBase$1.propTypes, { 1186 | /** Clear uploaded files when component is unmounted. */ 1187 | clearOnUnmount: PropTypes.bool, 1188 | 1189 | /** List containing File objects or URL strings.
1190 | * **Note:** Please take care of CORS. 1191 | */ 1192 | initialFiles: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.any])), 1193 | 1194 | /** Maximum number of files that can be loaded into the dropzone. */ 1195 | filesLimit: PropTypes.number, 1196 | 1197 | /** 1198 | * Fired when the files inside dropzone change. 1199 | * 1200 | * @param {File[]} loadedFiles All the files currently loaded into the dropzone. 1201 | */ 1202 | onChange: PropTypes.func, 1203 | 1204 | /** 1205 | * Fired when a file is deleted from the previews panel. 1206 | * 1207 | * @param {File} deletedFile The file that was removed. 1208 | * @param {number} index The index of the removed file object. 1209 | */ 1210 | onDelete: PropTypes.func 1211 | }) : void 0; 1212 | 1213 | function _createSuper$2(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct$2(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } 1214 | 1215 | function _isNativeReflectConstruct$2() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } } 1216 | 1217 | function splitDropzoneDialogProps(allProps) { 1218 | var cancelButtonText = allProps.cancelButtonText, 1219 | dialogProps = allProps.dialogProps, 1220 | dialogTitle = allProps.dialogTitle, 1221 | fullWidth = allProps.fullWidth, 1222 | maxWidth = allProps.maxWidth, 1223 | onClose = allProps.onClose, 1224 | onSave = allProps.onSave, 1225 | open = allProps.open, 1226 | submitButtonText = allProps.submitButtonText, 1227 | dropzoneAreaProps = _objectWithoutProperties(allProps, ["cancelButtonText", "dialogProps", "dialogTitle", "fullWidth", "maxWidth", "onClose", "onSave", "open", "submitButtonText"]); 1228 | 1229 | return [{ 1230 | cancelButtonText: cancelButtonText, 1231 | dialogProps: dialogProps, 1232 | dialogTitle: dialogTitle, 1233 | fullWidth: fullWidth, 1234 | maxWidth: maxWidth, 1235 | onClose: onClose, 1236 | onSave: onSave, 1237 | open: open, 1238 | submitButtonText: submitButtonText 1239 | }, dropzoneAreaProps]; 1240 | } 1241 | /** 1242 | * This component provides the DropzoneArea inside of a Material-UI Dialog. 1243 | * 1244 | * It supports all the Props and Methods from `DropzoneAreaBase`. 1245 | */ 1246 | 1247 | 1248 | var DropzoneDialogBase = /*#__PURE__*/function (_React$PureComponent) { 1249 | _inherits(DropzoneDialogBase, _React$PureComponent); 1250 | 1251 | var _super = _createSuper$2(DropzoneDialogBase); 1252 | 1253 | function DropzoneDialogBase() { 1254 | _classCallCheck(this, DropzoneDialogBase); 1255 | 1256 | return _super.apply(this, arguments); 1257 | } 1258 | 1259 | _createClass(DropzoneDialogBase, [{ 1260 | key: "render", 1261 | value: function render() { 1262 | var _splitDropzoneDialogP = splitDropzoneDialogProps(this.props), 1263 | _splitDropzoneDialogP2 = _slicedToArray(_splitDropzoneDialogP, 2), 1264 | dropzoneDialogProps = _splitDropzoneDialogP2[0], 1265 | dropzoneAreaProps = _splitDropzoneDialogP2[1]; 1266 | 1267 | var cancelButtonText = dropzoneDialogProps.cancelButtonText, 1268 | dialogProps = dropzoneDialogProps.dialogProps, 1269 | dialogTitle = dropzoneDialogProps.dialogTitle, 1270 | fullWidth = dropzoneDialogProps.fullWidth, 1271 | maxWidth = dropzoneDialogProps.maxWidth, 1272 | onClose = dropzoneDialogProps.onClose, 1273 | onSave = dropzoneDialogProps.onSave, 1274 | open = dropzoneDialogProps.open, 1275 | submitButtonText = dropzoneDialogProps.submitButtonText; // Submit button state 1276 | 1277 | var submitDisabled = dropzoneAreaProps.fileObjects.length === 0; 1278 | return /*#__PURE__*/createElement(Dialog, _extends({}, dialogProps, { 1279 | fullWidth: fullWidth, 1280 | maxWidth: maxWidth, 1281 | onClose: onClose, 1282 | open: open 1283 | }), /*#__PURE__*/createElement(DialogTitle, null, dialogTitle), /*#__PURE__*/createElement(DialogContent, null, /*#__PURE__*/createElement(DropzoneAreaBase$1, dropzoneAreaProps)), /*#__PURE__*/createElement(DialogActions, null, /*#__PURE__*/createElement(Button$1, { 1284 | color: "primary", 1285 | onClick: onClose 1286 | }, cancelButtonText), /*#__PURE__*/createElement(Button$1, { 1287 | color: "primary", 1288 | disabled: submitDisabled, 1289 | onClick: onSave 1290 | }, submitButtonText))); 1291 | } 1292 | }]); 1293 | 1294 | return DropzoneDialogBase; 1295 | }(PureComponent); 1296 | 1297 | DropzoneDialogBase.defaultProps = { 1298 | open: false, 1299 | dialogTitle: 'Upload file', 1300 | dialogProps: {}, 1301 | fullWidth: true, 1302 | maxWidth: 'sm', 1303 | cancelButtonText: 'Cancel', 1304 | submitButtonText: 'Submit', 1305 | showPreviews: true, 1306 | showPreviewsInDropzone: false, 1307 | showFileNamesInPreview: true 1308 | }; 1309 | process.env.NODE_ENV !== "production" ? DropzoneDialogBase.propTypes = _extends({}, DropzoneAreaBase$1.propTypes, { 1310 | /** Sets whether the dialog is open or closed. */ 1311 | open: PropTypes.bool, 1312 | 1313 | /** The Dialog title. */ 1314 | dialogTitle: PropTypes.oneOfType([PropTypes.string, PropTypes.element]), 1315 | 1316 | /** 1317 | * Props to pass to the Material-UI Dialog components. 1318 | * @see See [Material-UI Dialog](https://material-ui.com/api/dialog/#props) for available values. 1319 | */ 1320 | dialogProps: PropTypes.object, 1321 | 1322 | /** 1323 | * If `true`, the dialog stretches to `maxWidth`.
1324 | * Notice that the dialog width grow is limited by the default margin. 1325 | */ 1326 | fullWidth: PropTypes.bool, 1327 | 1328 | /** 1329 | * Determine the max-width of the dialog. The dialog width grows with the size of the screen.
1330 | * Set to `false` to disable `maxWidth`. 1331 | */ 1332 | maxWidth: PropTypes.string, 1333 | 1334 | /** Cancel button text in dialog. */ 1335 | cancelButtonText: PropTypes.string, 1336 | 1337 | /** Submit button text in dialog. */ 1338 | submitButtonText: PropTypes.string, 1339 | 1340 | /** 1341 | * Fired when the modal is closed. 1342 | * 1343 | * @param {SyntheticEvent} event The react `SyntheticEvent` 1344 | */ 1345 | onClose: PropTypes.func, 1346 | 1347 | /** 1348 | * Fired when the user clicks the Submit button. 1349 | * 1350 | * @param {SyntheticEvent} event The react `SyntheticEvent` 1351 | */ 1352 | onSave: PropTypes.func, 1353 | 1354 | /** 1355 | * Shows previews **BELOW** the dropzone.
1356 | * **Note:** By default previews show up under in the Dialog and inside in the standalone. 1357 | */ 1358 | showPreviews: PropTypes.bool, 1359 | 1360 | /** Shows preview **INSIDE** the dropzone area. */ 1361 | showPreviewsInDropzone: PropTypes.bool, 1362 | 1363 | /** Shows file name under the image. */ 1364 | showFileNamesInPreview: PropTypes.bool 1365 | }) : void 0; 1366 | 1367 | function _createSuper$3(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct$3(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } 1368 | 1369 | function _isNativeReflectConstruct$3() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } } 1370 | /** 1371 | * This component provides an uncontrolled version of the DropzoneDialogBase component. 1372 | * 1373 | * It supports all the Props and Methods from `DropzoneDialogBase` but keeps the files state internally. 1374 | * 1375 | * **Note** The `onSave` handler also returns `File[]` with all the accepted files. 1376 | */ 1377 | 1378 | var DropzoneDialog = /*#__PURE__*/function (_React$PureComponent) { 1379 | _inherits(DropzoneDialog, _React$PureComponent); 1380 | 1381 | var _super = _createSuper$3(DropzoneDialog); 1382 | 1383 | function DropzoneDialog() { 1384 | var _this; 1385 | 1386 | _classCallCheck(this, DropzoneDialog); 1387 | 1388 | for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { 1389 | args[_key] = arguments[_key]; 1390 | } 1391 | 1392 | _this = _super.call.apply(_super, [this].concat(args)); 1393 | _this.state = { 1394 | fileObjects: [] 1395 | }; 1396 | 1397 | _this.notifyFileChange = function () { 1398 | var onChange = _this.props.onChange; 1399 | var fileObjects = _this.state.fileObjects; 1400 | 1401 | if (onChange) { 1402 | onChange(fileObjects.map(function (fileObject) { 1403 | return fileObject.file; 1404 | })); 1405 | } 1406 | }; 1407 | 1408 | _this.loadInitialFiles = /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee2() { 1409 | var initialFiles, fileObjs; 1410 | return _regeneratorRuntime.wrap(function _callee2$(_context2) { 1411 | while (1) { 1412 | switch (_context2.prev = _context2.next) { 1413 | case 0: 1414 | initialFiles = _this.props.initialFiles; 1415 | _context2.prev = 1; 1416 | _context2.next = 4; 1417 | return Promise.all(initialFiles.map( /*#__PURE__*/function () { 1418 | var _ref2 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee(initialFile) { 1419 | var file, data; 1420 | return _regeneratorRuntime.wrap(function _callee$(_context) { 1421 | while (1) { 1422 | switch (_context.prev = _context.next) { 1423 | case 0: 1424 | if (!(typeof initialFile === 'string')) { 1425 | _context.next = 6; 1426 | break; 1427 | } 1428 | 1429 | _context.next = 3; 1430 | return createFileFromUrl(initialFile); 1431 | 1432 | case 3: 1433 | file = _context.sent; 1434 | _context.next = 7; 1435 | break; 1436 | 1437 | case 6: 1438 | file = initialFile; 1439 | 1440 | case 7: 1441 | _context.next = 9; 1442 | return readFile(file); 1443 | 1444 | case 9: 1445 | data = _context.sent; 1446 | return _context.abrupt("return", { 1447 | file: file, 1448 | data: data 1449 | }); 1450 | 1451 | case 11: 1452 | case "end": 1453 | return _context.stop(); 1454 | } 1455 | } 1456 | }, _callee); 1457 | })); 1458 | 1459 | return function (_x) { 1460 | return _ref2.apply(this, arguments); 1461 | }; 1462 | }())); 1463 | 1464 | case 4: 1465 | fileObjs = _context2.sent; 1466 | 1467 | _this.setState(function (state) { 1468 | return { 1469 | fileObjects: [].concat(state.fileObjects, fileObjs) 1470 | }; 1471 | }, _this.notifyFileChange); 1472 | 1473 | _context2.next = 11; 1474 | break; 1475 | 1476 | case 8: 1477 | _context2.prev = 8; 1478 | _context2.t0 = _context2["catch"](1); 1479 | console.log(_context2.t0); 1480 | 1481 | case 11: 1482 | case "end": 1483 | return _context2.stop(); 1484 | } 1485 | } 1486 | }, _callee2, null, [[1, 8]]); 1487 | })); 1488 | 1489 | _this.addFiles = /*#__PURE__*/function () { 1490 | var _ref3 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee3(newFileObjects) { 1491 | var filesLimit; 1492 | return _regeneratorRuntime.wrap(function _callee3$(_context3) { 1493 | while (1) { 1494 | switch (_context3.prev = _context3.next) { 1495 | case 0: 1496 | filesLimit = _this.props.filesLimit; // Update component state 1497 | 1498 | _this.setState(function (state) { 1499 | // Handle a single file 1500 | if (filesLimit <= 1) { 1501 | return { 1502 | fileObjects: [].concat(newFileObjects[0]) 1503 | }; 1504 | } // Handle multiple files 1505 | 1506 | 1507 | return { 1508 | fileObjects: [].concat(state.fileObjects, newFileObjects) 1509 | }; 1510 | }, _this.notifyFileChange); 1511 | 1512 | case 2: 1513 | case "end": 1514 | return _context3.stop(); 1515 | } 1516 | } 1517 | }, _callee3); 1518 | })); 1519 | 1520 | return function (_x2) { 1521 | return _ref3.apply(this, arguments); 1522 | }; 1523 | }(); 1524 | 1525 | _this.deleteFile = function (removedFileObj, removedFileObjIdx) { 1526 | event.stopPropagation(); 1527 | var onDelete = _this.props.onDelete; 1528 | var fileObjects = _this.state.fileObjects; // Calculate remaining fileObjects array 1529 | 1530 | var remainingFileObjs = fileObjects.filter(function (fileObject, i) { 1531 | return i !== removedFileObjIdx; 1532 | }); // Notify removed file 1533 | 1534 | if (onDelete) { 1535 | onDelete(removedFileObj.file); 1536 | } // Update local state 1537 | 1538 | 1539 | _this.setState({ 1540 | fileObjects: remainingFileObjs 1541 | }, _this.notifyFileChange); 1542 | }; 1543 | 1544 | _this.handleClose = function (evt) { 1545 | var _this$props = _this.props, 1546 | clearOnUnmount = _this$props.clearOnUnmount, 1547 | onClose = _this$props.onClose; 1548 | 1549 | if (onClose) { 1550 | onClose(evt); 1551 | } 1552 | 1553 | if (clearOnUnmount) { 1554 | _this.setState({ 1555 | fileObjects: [] 1556 | }, _this.notifyFileChange); 1557 | } 1558 | }; 1559 | 1560 | _this.handleSave = function (evt) { 1561 | var _this$props2 = _this.props, 1562 | clearOnUnmount = _this$props2.clearOnUnmount, 1563 | onSave = _this$props2.onSave; 1564 | var fileObjects = _this.state.fileObjects; 1565 | 1566 | if (onSave) { 1567 | onSave(fileObjects.map(function (fileObject) { 1568 | return fileObject.file; 1569 | }), evt); 1570 | } 1571 | 1572 | if (clearOnUnmount) { 1573 | _this.setState({ 1574 | fileObjects: [] 1575 | }, _this.notifyFileChange); 1576 | } 1577 | }; 1578 | 1579 | return _this; 1580 | } 1581 | 1582 | _createClass(DropzoneDialog, [{ 1583 | key: "componentDidMount", 1584 | value: function componentDidMount() { 1585 | this.loadInitialFiles(); 1586 | } 1587 | }, { 1588 | key: "componentWillUnmount", 1589 | value: function componentWillUnmount() { 1590 | var clearOnUnmount = this.props.clearOnUnmount; 1591 | 1592 | if (clearOnUnmount) { 1593 | this.setState({ 1594 | fileObjects: [] 1595 | }, this.notifyFileChange); 1596 | } 1597 | } 1598 | }, { 1599 | key: "render", 1600 | value: function render() { 1601 | var fileObjects = this.state.fileObjects; 1602 | return /*#__PURE__*/createElement(DropzoneDialogBase, _extends({}, this.props, { 1603 | fileObjects: fileObjects, 1604 | onAdd: this.addFiles, 1605 | onDelete: this.deleteFile, 1606 | onClose: this.handleClose, 1607 | onSave: this.handleSave 1608 | })); 1609 | } 1610 | }]); 1611 | 1612 | return DropzoneDialog; 1613 | }(PureComponent); 1614 | 1615 | DropzoneDialog.defaultProps = { 1616 | clearOnUnmount: true, 1617 | filesLimit: 3, 1618 | initialFiles: [] 1619 | }; 1620 | process.env.NODE_ENV !== "production" ? DropzoneDialog.propTypes = _extends({}, DropzoneDialogBase.propTypes, { 1621 | /** Clear uploaded files when component is unmounted. */ 1622 | clearOnUnmount: PropTypes.bool, 1623 | 1624 | /** Maximum number of files that can be loaded into the dropzone. */ 1625 | filesLimit: PropTypes.number, 1626 | 1627 | /** List containing File objects or URL strings.
1628 | * **Note:** Please take care of CORS. 1629 | */ 1630 | initialFiles: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.any])), 1631 | 1632 | /** 1633 | * Fired when the user clicks the Submit button. 1634 | * 1635 | * @param {File[]} files All the files currently inside the Dropzone. 1636 | * @param {SyntheticEvent} event The react `SyntheticEvent`. 1637 | */ 1638 | onSave: PropTypes.func 1639 | }) : void 0; 1640 | 1641 | export { DropzoneArea, DropzoneAreaBase$1 as DropzoneAreaBase, DropzoneDialog, DropzoneDialogBase }; 1642 | //# sourceMappingURL=index.es.js.map 1643 | -------------------------------------------------------------------------------- /docs/installation.md: -------------------------------------------------------------------------------- 1 | 2 | ```shell 3 | npm install --save material-ui-dropzone 4 | ``` 5 | 6 | or 7 | 8 | ```shell 9 | yarn add material-ui-dropzone 10 | ``` 11 | -------------------------------------------------------------------------------- /docs/intro.md: -------------------------------------------------------------------------------- 1 | # material-ui-dropzone 2 | 3 | > Material-UI-dropzone is a set of [React](https://github.com/facebook/react) components using [Material-UI](https://github.com/mui-org/material-ui) and is based on the excellent [react-dropzone](https://github.com/react-dropzone/react-dropzone) library. 4 | 5 | This components provide either a file-upload dropzone or a file-upload dropzone inside of a dialog. 6 | 7 | The file-upload dropzone features some snazzy "File Allowed/Not Allowed" effects, previews and alerts. 8 | -------------------------------------------------------------------------------- /docs/screenshots.md: -------------------------------------------------------------------------------- 1 | This is the Dialog component: 2 | 3 | ![Dialog](https://raw.githubusercontent.com/Yuvaleros/material-ui-dropzone/master/pics/demo_pic.jpg) 4 | ![Dialog with Previews](https://raw.githubusercontent.com/Yuvaleros/material-ui-dropzone/master/pics/demo_pic5.JPG) 5 | 6 | When you drag a file onto the dropzone, you get a neat effect: 7 | 8 | ![Drag Overlay](https://raw.githubusercontent.com/Yuvaleros/material-ui-dropzone/master/pics/demo_pic2.JPG) 9 | 10 | And if you drag in a wrong type of file, you'll get yelled at: 11 | 12 | ![Drag Error Overlay](https://raw.githubusercontent.com/Yuvaleros/material-ui-dropzone/master/pics/demo_pic4.JPG) 13 | 14 | **N.B. This has some limitations (see [here](https://github.com/react-dropzone/react-dropzone/tree/master/examples/accept#browser-limitations) for more details).** 15 | -------------------------------------------------------------------------------- /docs/support.md: -------------------------------------------------------------------------------- 1 | 2 | `material-ui-dropzone` complies to the following support matrix. 3 | 4 | | version | React | Material-UI | 5 | | ------- | ---------------- | -------------- | 6 | | `3.x` | `16.8+` | `4.x` | 7 | | `2.x` | `15.x` or `16.x` | `3.x` or `4.x` | 8 | -------------------------------------------------------------------------------- /docs/theming.md: -------------------------------------------------------------------------------- 1 | `material-ui-dropzone` components support theming through [`MaterialUI Theme`](https://material-ui.com/customization/theming/). 2 | 3 | #### DropzoneArea 4 | 5 | **Theme Namespace:** `MuiDropzoneArea` 6 | 7 | | Rule name | Global class | Description | 8 | | ------------- | ------------------------------ | --------------------------------------------------------------- | 9 | | root | .MuiDropzoneArea-root | The class applied to DropzoneArea container. | 10 | | active | .MuiDropzoneArea-active | The class applied when the Dropzone is 'active'. | 11 | | invalid | .MuiDropzoneArea-invalid | The class applied when the Dropzone is receiving invalid files. | 12 | | textContainer | .MuiDropzoneArea-textContainer | The class applied to the text container div. | 13 | | text | .MuiDropzoneArea-text | The class applied to the hint text. | 14 | | icon | .MuiDropzoneArea-icon | The class applied to the hint icon. | 15 | 16 | #### Preview list 17 | 18 | **Theme Namespace:** `MuiDropzonePreviewList` 19 | 20 | | Rule name | Global class | Description | 21 | | -------------- | -------------------------------------- | -------------------------------------------------------- | 22 | | root | .MuiDropzonePreviewList-root | The class applied to PreviewList container. | 23 | | imageContainer | .MuiDropzonePreviewList-imageContainer | The class applied to the single preview image container. | 24 | | image | .MuiDropzonePreviewList-image | The class applied to the single preview image. | 25 | | removeButton | .MuiDropzonePreviewList-removeButton | The class applied to the preview 'remove' FAB. | 26 | 27 | #### Alert Snackbar 28 | 29 | **Theme Namespace:** `MuiDropzoneSnackbar` 30 | 31 | | Rule name | Global class | Description | 32 | | ------------ | --------------------------------- | ------------------------------------------------------------- | 33 | | infoAlert | .MuiDropzoneSnackbar-infoAlert | The class applied to the alert snackbar in case of 'info'. | 34 | | successAlert | .MuiDropzoneSnackbar-successAlert | The class applied to the alert snackbar in case of 'success'. | 35 | | warningAlert | .MuiDropzoneSnackbar-warningAlert | The class applied to the alert snackbar in case of 'warning'. | 36 | | errorAlert | .MuiDropzoneSnackbar-errorAlert | The class applied to the alert snackbar in case of 'error'. | 37 | | message | .MuiDropzoneSnackbar-message | The class applied to the alert snackbar message. | 38 | | icon | .MuiDropzoneSnackbar-icon | The class applied to the alert snackbar icon. | 39 | | closeButton | .MuiDropzoneSnackbar-closeButton | The class applied to the alert snackbar 'close' button. | 40 | 41 | ### Sample theme override 42 | 43 | ```jsx 44 | import { MuiThemeProvider, createMuiTheme } from "@mui/styles"; 45 | 46 | import DropzoneArea from '../src/components/DropzoneArea'; 47 | 48 | const theme = createMuiTheme({ 49 | overrides: { 50 | MuiDropzoneSnackbar: { 51 | errorAlert: { 52 | backgroundColor: "#AFA", 53 | color: "#000" 54 | }, 55 | successAlert: { 56 | backgroundColor: "#FAA", 57 | color: "#000" 58 | }, 59 | } 60 | } 61 | }); 62 | 63 | 64 | 65 | 66 | ``` 67 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "material-ui-dropzone", 3 | "version": "3.5.0", 4 | "description": "A Material-UI file-upload dropzone", 5 | "license": "MIT", 6 | "author": "Yuval", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/Yuvaleros/material-ui-dropzone.git" 10 | }, 11 | "keywords": [ 12 | "react", 13 | "react-component", 14 | "material design", 15 | "material-ui", 16 | "dropzone", 17 | "upload", 18 | "react upload" 19 | ], 20 | "main": "dist/index.js", 21 | "module": "dist/index.es.js", 22 | "jsnext:main": "dist/index.es.js", 23 | "types": "dist/index.d.ts", 24 | "files": [ 25 | "dist" 26 | ], 27 | "engines": { 28 | "node": ">=8", 29 | "yarn": ">=1" 30 | }, 31 | "scripts": { 32 | "lint": "eslint src/", 33 | "lint:fix": "eslint src/ --fix", 34 | "build": "rollup -c", 35 | "start": "rollup -c -w", 36 | "prepare": "yarn build", 37 | "docs:dev": "styleguidist server", 38 | "docs:build": "styleguidist build", 39 | "predocs:deploy": "yarn docs:build", 40 | "docs:deploy": "gh-pages -d styleguide" 41 | }, 42 | "dependencies": { 43 | "@babel/runtime": "^7.4.4", 44 | "clsx": "^1.0.2", 45 | "react-dropzone": "^10.2.1" 46 | }, 47 | "peerDependencies": { 48 | "@mui/icons-material": "^5.0.0", 49 | "@mui/material": "^5.0.0", 50 | "@mui/styles": "^5.0.1", 51 | "prop-types": "^15.7.2", 52 | "react": ">= 17.0", 53 | "react-dom": ">= 17.0" 54 | }, 55 | "devDependencies": { 56 | "@babel/core": "^7.10.2", 57 | "@babel/plugin-proposal-class-properties": "^7.10.1", 58 | "@babel/plugin-proposal-object-rest-spread": "^7.10.1", 59 | "@babel/plugin-transform-object-assign": "^7.10.1", 60 | "@babel/plugin-transform-react-constant-elements": "^7.10.1", 61 | "@babel/plugin-transform-runtime": "^7.10.1", 62 | "@babel/preset-env": "^7.10.2", 63 | "@babel/preset-react": "^7.10.1", 64 | "@mui/icons-material": "^5.0.1", 65 | "@mui/material": "^5.0.2", 66 | "@rollup/plugin-commonjs": "^11.0.2", 67 | "@rollup/plugin-node-resolve": "^7.1.1", 68 | "babel-eslint": "^10.1.0", 69 | "babel-loader": "^8.1.0", 70 | "babel-plugin-optimize-clsx": "^2.6.0", 71 | "babel-plugin-transform-dev-warning": "^0.1.1", 72 | "babel-plugin-transform-react-remove-prop-types": "^0.4.24", 73 | "eslint": "^6.8.0", 74 | "eslint-plugin-babel": "^5.3.0", 75 | "eslint-plugin-react": "^7.19.0", 76 | "gh-pages": "^1.2.0", 77 | "react": "^17.0.2", 78 | "react-dom": "^17.0.2", 79 | "react-styleguidist": "^11.0.4", 80 | "rollup": "^1.32.0", 81 | "rollup-plugin-babel": "^4.4.0", 82 | "rollup-plugin-cpy": "^2.0.1", 83 | "rollup-plugin-peer-deps-external": "2.2.2", 84 | "rollup-plugin-size-snapshot": "^0.11.0", 85 | "webpack": "^4.42.1" 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /pics/demo_pic.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuvaleros/material-ui-dropzone/8f0be37c5d77012d6ec6cf9d84f4a9e1cc638d77/pics/demo_pic.jpg -------------------------------------------------------------------------------- /pics/demo_pic2.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuvaleros/material-ui-dropzone/8f0be37c5d77012d6ec6cf9d84f4a9e1cc638d77/pics/demo_pic2.JPG -------------------------------------------------------------------------------- /pics/demo_pic4.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuvaleros/material-ui-dropzone/8f0be37c5d77012d6ec6cf9d84f4a9e1cc638d77/pics/demo_pic4.JPG -------------------------------------------------------------------------------- /pics/demo_pic5.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuvaleros/material-ui-dropzone/8f0be37c5d77012d6ec6cf9d84f4a9e1cc638d77/pics/demo_pic5.JPG -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import commonjs from '@rollup/plugin-commonjs'; 2 | import resolve from '@rollup/plugin-node-resolve'; 3 | import babel from 'rollup-plugin-babel'; 4 | import copy from 'rollup-plugin-cpy'; 5 | import external from 'rollup-plugin-peer-deps-external'; 6 | import { sizeSnapshot } from 'rollup-plugin-size-snapshot'; 7 | 8 | import pkg from './package.json'; 9 | 10 | export default { 11 | input: 'src/index.js', 12 | output: [ 13 | { 14 | file: pkg.main, 15 | format: 'cjs', 16 | sourcemap: true, 17 | }, 18 | { 19 | file: pkg.module, 20 | format: 'es', 21 | sourcemap: true, 22 | }, 23 | ], 24 | plugins: [ 25 | external({ 26 | includeDependencies: true, 27 | }), 28 | babel({ 29 | exclude: /node_modules/, 30 | // We are using @babel/plugin-transform-runtime 31 | runtimeHelpers: true, 32 | }), 33 | copy({ 34 | files: ['src/index.d.ts'], 35 | dest: 'dist', 36 | }), 37 | resolve(), 38 | commonjs(), 39 | sizeSnapshot(), 40 | ], 41 | }; 42 | -------------------------------------------------------------------------------- /src/components/DropzoneArea.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import * as React from 'react'; 3 | 4 | import {createFileFromUrl, readFile} from '../helpers'; 5 | 6 | import DropzoneAreaBase from './DropzoneAreaBase'; 7 | 8 | const splitDropzoneAreaProps = (props) => { 9 | const {clearOnUnmount, initialFiles, onChange, onDelete, ...dropzoneAreaProps} = props; 10 | return [{clearOnUnmount, initialFiles, onChange, onDelete}, dropzoneAreaProps]; 11 | }; 12 | 13 | /** 14 | * This components creates an uncontrolled Material-UI Dropzone, with previews and snackbar notifications. 15 | * 16 | * It supports all props of `DropzoneAreaBase` but keeps the files state internally. 17 | * 18 | * **Note** To listen to file changes use `onChange` event handler and notice that `onDelete` returns a `File` instance instead of `FileObject`. 19 | */ 20 | class DropzoneArea extends React.PureComponent { 21 | state = { 22 | fileObjects: [], 23 | } 24 | 25 | componentDidMount() { 26 | this.loadInitialFiles(); 27 | } 28 | 29 | componentWillUnmount() { 30 | const {clearOnUnmount} = this.props; 31 | 32 | if (clearOnUnmount) { 33 | this.setState({ 34 | fileObjects: [], 35 | }, this.notifyFileChange); 36 | } 37 | } 38 | 39 | notifyFileChange = () => { 40 | const {onChange} = this.props; 41 | const {fileObjects} = this.state; 42 | 43 | if (onChange) { 44 | onChange(fileObjects.map((fileObject) => fileObject.file)); 45 | } 46 | } 47 | 48 | loadInitialFiles = async() => { 49 | const {initialFiles} = this.props; 50 | try { 51 | const fileObjs = await Promise.all( 52 | initialFiles.map(async(initialFile) => { 53 | let file; 54 | if (typeof initialFile === 'string' ) { 55 | file = await createFileFromUrl(initialFile); 56 | } else { 57 | file = initialFile; 58 | } 59 | const data = await readFile(file); 60 | 61 | return { 62 | file, 63 | data, 64 | }; 65 | }) 66 | ); 67 | 68 | this.setState((state) => ({ 69 | fileObjects: [].concat( 70 | state.fileObjects, 71 | fileObjs 72 | ), 73 | }), this.notifyFileChange); 74 | } catch (err) { 75 | console.log(err); 76 | } 77 | } 78 | 79 | addFiles = async(newFileObjects) => { 80 | const {filesLimit} = this.props; 81 | // Update component state 82 | this.setState((state) => { 83 | // Handle a single file 84 | if (filesLimit <= 1) { 85 | return { 86 | fileObjects: [].concat(newFileObjects[0]), 87 | }; 88 | } 89 | 90 | // Handle multiple files 91 | return { 92 | fileObjects: [].concat( 93 | state.fileObjects, 94 | newFileObjects 95 | ), 96 | }; 97 | }, this.notifyFileChange); 98 | } 99 | 100 | deleteFile = (removedFileObj, removedFileObjIdx) => { 101 | event.stopPropagation(); 102 | 103 | const {onDelete} = this.props; 104 | const {fileObjects} = this.state; 105 | 106 | // Calculate remaining fileObjects array 107 | const remainingFileObjs = fileObjects.filter((fileObject, i) => { 108 | return i !== removedFileObjIdx; 109 | }); 110 | 111 | // Notify removed file 112 | if (onDelete) { 113 | onDelete(removedFileObj.file, removedFileObjIdx); 114 | } 115 | 116 | // Update local state 117 | this.setState({ 118 | fileObjects: remainingFileObjs, 119 | }, this.notifyFileChange); 120 | } 121 | 122 | render() { 123 | const [, dropzoneAreaProps] = splitDropzoneAreaProps(this.props); 124 | const {fileObjects} = this.state; 125 | 126 | return ( 127 | 133 | ); 134 | } 135 | } 136 | 137 | DropzoneArea.defaultProps = { 138 | clearOnUnmount: true, 139 | filesLimit: 3, 140 | initialFiles: [], 141 | }; 142 | 143 | DropzoneArea.propTypes = { 144 | ...DropzoneAreaBase.propTypes, 145 | /** Clear uploaded files when component is unmounted. */ 146 | clearOnUnmount: PropTypes.bool, 147 | /** List containing File objects or URL strings.
148 | * **Note:** Please take care of CORS. 149 | */ 150 | initialFiles: PropTypes.arrayOf( 151 | PropTypes.oneOfType([ 152 | PropTypes.string, 153 | PropTypes.any, 154 | ]) 155 | ), 156 | /** Maximum number of files that can be loaded into the dropzone. */ 157 | filesLimit: PropTypes.number, 158 | /** 159 | * Fired when the files inside dropzone change. 160 | * 161 | * @param {File[]} loadedFiles All the files currently loaded into the dropzone. 162 | */ 163 | onChange: PropTypes.func, 164 | /** 165 | * Fired when a file is deleted from the previews panel. 166 | * 167 | * @param {File} deletedFile The file that was removed. 168 | * @param {number} index The index of the removed file object. 169 | */ 170 | onDelete: PropTypes.func, 171 | }; 172 | 173 | export default DropzoneArea; 174 | -------------------------------------------------------------------------------- /src/components/DropzoneArea.md: -------------------------------------------------------------------------------- 1 | ### Import 2 | 3 | ```jsx static 4 | import { DropzoneArea } from 'material-ui-dropzone'; 5 | ``` 6 | 7 | ### Basic usage 8 | 9 | ```jsx 10 | console.log('Files:', files)} 12 | /> 13 | ``` 14 | 15 | ### Accept only images 16 | 17 | ```jsx 18 | console.log('Files:', files)} 22 | /> 23 | ``` 24 | 25 | ### Custom Preview Icon 26 | 27 | Demonstration of how to customize the preview icon for: 28 | 29 | * PDF files 30 | * Video 31 | * Audio 32 | * Word Documents 33 | 34 | ```jsx 35 | import { AttachFile, AudioTrack, Description, PictureAsPdf, Theaters } from '@mui/icons-material'; 36 | 37 | const handlePreviewIcon = (fileObject, classes) => { 38 | const {type} = fileObject.file 39 | const iconProps = { 40 | className : classes.image, 41 | } 42 | 43 | if (type.startsWith("video/")) return 44 | if (type.startsWith("audio/")) return 45 | 46 | switch (type) { 47 | case "application/msword": 48 | case "application/vnd.openxmlformats-officedocument.wordprocessingml.document": 49 | return 50 | case "application/pdf": 51 | return 52 | default: 53 | return 54 | } 55 | } 56 | 57 | 60 | ``` 61 | 62 | ### Loading initial files 63 | 64 | ```jsx 65 | 66 | const file = new File(["foo"], "foo.txt", { 67 | type: "text/plain", 68 | }); 69 | 70 | console.log('Files:', files)} 73 | /> 74 | ``` 75 | 76 | ### Using chips for preview 77 | 78 | Chips use the Grid system as well, so you can customize the way they appears and benefit from the Material-UI grid customizations 79 | 80 | ```jsx 81 | import { createStyles, makeStyles } from '@mui/styles'; 82 | 83 | const useStyles = makeStyles(theme => createStyles({ 84 | previewChip: { 85 | minWidth: 160, 86 | maxWidth: 210 87 | }, 88 | })); 89 | 90 | const classes = useStyles(); 91 | 92 | 100 | ``` 101 | -------------------------------------------------------------------------------- /src/components/DropzoneAreaBase.js: -------------------------------------------------------------------------------- 1 | import Snackbar from '@mui/material/Snackbar'; 2 | import Typography from '@mui/material/Typography'; 3 | import Button from '@material-ui/core/Button'; 4 | import {withStyles} from '@mui/styles'; 5 | import AttachFileIcon from '@mui/icons-material/AttachFile'; 6 | import CloudUploadIcon from '@mui/icons-material/CloudUpload'; 7 | import clsx from 'clsx'; 8 | import PropTypes from 'prop-types'; 9 | import * as React from 'react'; 10 | import {Fragment} from 'react'; 11 | import Dropzone from 'react-dropzone'; 12 | import {convertBytesToMbsOrKbs, isImage, readFile} from '../helpers'; 13 | import PreviewList from './PreviewList'; 14 | import SnackbarContentWrapper from './SnackbarContentWrapper'; 15 | 16 | const styles = ({palette, shape, spacing}) => ({ 17 | '@keyframes progress': { 18 | '0%': { 19 | backgroundPosition: '0 0', 20 | }, 21 | '100%': { 22 | backgroundPosition: '-70px 0', 23 | }, 24 | }, 25 | root: { 26 | position: 'relative', 27 | width: '100%', 28 | minHeight: '250px', 29 | backgroundColor: palette.background.paper, 30 | border: 'dashed', 31 | borderColor: palette.divider, 32 | borderRadius: shape.borderRadius, 33 | boxSizing: 'border-box', 34 | cursor: 'pointer', 35 | overflow: 'hidden', 36 | }, 37 | active: { 38 | animation: '$progress 2s linear infinite !important', 39 | // eslint-disable-next-line max-len 40 | backgroundImage: `repeating-linear-gradient(-45deg, ${palette.background.paper}, ${palette.background.paper} 25px, ${palette.divider} 25px, ${palette.divider} 50px)`, 41 | backgroundSize: '150% 100%', 42 | border: 'solid', 43 | borderColor: palette.primary.light, 44 | }, 45 | invalid: { 46 | // eslint-disable-next-line max-len 47 | backgroundImage: `repeating-linear-gradient(-45deg, ${palette.error.light}, ${palette.error.light} 25px, ${palette.error.dark} 25px, ${palette.error.dark} 50px)`, 48 | borderColor: palette.error.main, 49 | }, 50 | textContainer: { 51 | textAlign: 'center', 52 | }, 53 | text: { 54 | marginBottom: spacing(3), 55 | marginTop: spacing(3), 56 | }, 57 | icon: { 58 | width: 51, 59 | height: 51, 60 | color: palette.text.primary, 61 | }, 62 | resetButton: { 63 | display: 'block', 64 | margin: '10px 0', 65 | }, 66 | }); 67 | 68 | const defaultSnackbarAnchorOrigin = { 69 | horizontal: 'left', 70 | vertical: 'bottom', 71 | }; 72 | 73 | const defaultGetPreviewIcon = (fileObject, classes) => { 74 | if (isImage(fileObject.file)) { 75 | return (); 80 | } 81 | 82 | return ; 83 | }; 84 | 85 | /** 86 | * This components creates a Material-UI Dropzone, with previews and snackbar notifications. 87 | */ 88 | class DropzoneAreaBase extends React.PureComponent { 89 | state = { 90 | openSnackBar: false, 91 | snackbarMessage: '', 92 | snackbarVariant: 'success', 93 | }; 94 | 95 | notifyAlert() { 96 | const {onAlert} = this.props; 97 | const {openSnackBar, snackbarMessage, snackbarVariant} = this.state; 98 | if (openSnackBar && onAlert) { 99 | onAlert(snackbarMessage, snackbarVariant); 100 | } 101 | } 102 | 103 | handleDropAccepted = async(acceptedFiles, evt) => { 104 | const {fileObjects, filesLimit, getFileAddedMessage, getFileLimitExceedMessage, onAdd, onDrop} = this.props; 105 | 106 | if (filesLimit > 1 && fileObjects.length + acceptedFiles.length > filesLimit) { 107 | this.setState({ 108 | openSnackBar: true, 109 | snackbarMessage: getFileLimitExceedMessage(filesLimit), 110 | snackbarVariant: 'error', 111 | }, this.notifyAlert); 112 | return; 113 | } 114 | 115 | // Notify Drop event 116 | if (onDrop) { 117 | onDrop(acceptedFiles, evt); 118 | } 119 | 120 | // Retrieve fileObjects data 121 | const fileObjs = await Promise.all( 122 | acceptedFiles.map(async(file) => { 123 | const data = await readFile(file); 124 | return { 125 | file, 126 | data, 127 | }; 128 | }) 129 | ); 130 | 131 | // Notify added files 132 | if (onAdd) { 133 | onAdd(fileObjs); 134 | } 135 | 136 | // Display message 137 | const message = fileObjs.reduce((msg, fileObj) => msg + getFileAddedMessage(fileObj.file.name), ''); 138 | this.setState({ 139 | openSnackBar: true, 140 | snackbarMessage: message, 141 | snackbarVariant: 'success', 142 | }, this.notifyAlert); 143 | } 144 | 145 | handleDropRejected = (rejectedFiles, evt) => { 146 | const { 147 | acceptedFiles, 148 | filesLimit, 149 | fileObjects, 150 | getDropRejectMessage, 151 | getFileLimitExceedMessage, 152 | maxFileSize, 153 | onDropRejected, 154 | } = this.props; 155 | 156 | let message = ''; 157 | if (fileObjects.length + rejectedFiles.length > filesLimit) { 158 | message = getFileLimitExceedMessage(filesLimit); 159 | } else { 160 | rejectedFiles.forEach((rejectedFile) => { 161 | message = getDropRejectMessage(rejectedFile, acceptedFiles, maxFileSize); 162 | }); 163 | } 164 | 165 | if (onDropRejected) { 166 | onDropRejected(rejectedFiles, evt); 167 | } 168 | 169 | this.setState({ 170 | openSnackBar: true, 171 | snackbarMessage: message, 172 | snackbarVariant: 'error', 173 | }, this.notifyAlert); 174 | } 175 | 176 | handleRemove = (fileIndex) => (event) => { 177 | event.stopPropagation(); 178 | 179 | const {fileObjects, getFileRemovedMessage, onDelete} = this.props; 180 | 181 | // Find removed fileObject 182 | const removedFileObj = fileObjects[fileIndex]; 183 | 184 | // Notify removed file 185 | if (onDelete) { 186 | onDelete(removedFileObj, fileIndex); 187 | } 188 | 189 | this.setState({ 190 | openSnackBar: true, 191 | snackbarMessage: getFileRemovedMessage(removedFileObj.file.name), 192 | snackbarVariant: 'info', 193 | }, this.notifyAlert); 194 | }; 195 | 196 | handleCloseSnackbar = () => { 197 | this.setState({ 198 | openSnackBar: false, 199 | }); 200 | }; 201 | 202 | render() { 203 | const { 204 | acceptedFiles, 205 | alertSnackbarProps, 206 | classes, 207 | disableRejectionFeedback, 208 | dropzoneClass, 209 | dropzoneParagraphClass, 210 | dropzoneProps, 211 | dropzoneText, 212 | fileObjects, 213 | filesLimit, 214 | getPreviewIcon, 215 | Icon, 216 | inputProps, 217 | maxFileSize, 218 | previewChipProps, 219 | previewGridClasses, 220 | previewGridProps, 221 | previewText, 222 | showAlerts, 223 | showFileNames, 224 | showFileNamesInPreview, 225 | showPreviews, 226 | showPreviewsInDropzone, 227 | useChipsForPreview, 228 | reset, 229 | } = this.props; 230 | const {openSnackBar, snackbarMessage, snackbarVariant} = this.state; 231 | 232 | const acceptFiles = acceptedFiles?.join(','); 233 | const isMultiple = filesLimit > 1; 234 | const previewsVisible = showPreviews && fileObjects.length > 0; 235 | const previewsInDropzoneVisible = showPreviewsInDropzone && fileObjects.length > 0; 236 | 237 | return ( 238 | 239 | 247 | {({getRootProps, getInputProps, isDragActive, isDragReject}) => ( 248 |
258 | 259 | 260 |
261 | 266 | {dropzoneText} 267 | 268 | {Icon ? ( 269 | 270 | ) : ( 271 | 272 | )} 273 |
274 | 275 | {previewsInDropzoneVisible && 276 | 286 | } 287 |
288 | )} 289 |
290 | 291 | { 292 | reset && ( 293 | React.isValidElement(reset) ? 294 | reset : 295 | 302 | ) 303 | } 304 | 305 | {previewsVisible && 306 | 307 | 308 | {previewText} 309 | 310 | 311 | 321 | 322 | } 323 | 324 | {((typeof showAlerts === 'boolean' && showAlerts) || 325 | (Array.isArray(showAlerts) && showAlerts.includes(snackbarVariant))) && 326 | 333 | 338 | 339 | } 340 |
341 | ); 342 | } 343 | } 344 | 345 | DropzoneAreaBase.defaultProps = { 346 | acceptedFiles: [], 347 | filesLimit: 3, 348 | fileObjects: [], 349 | maxFileSize: 3000000, 350 | dropzoneText: 'Drag and drop a file here or click', 351 | previewText: 'Preview:', 352 | disableRejectionFeedback: false, 353 | showPreviews: false, // By default previews show up under in the dialog and inside in the standalone 354 | showPreviewsInDropzone: true, 355 | showFileNames: false, 356 | showFileNamesInPreview: false, 357 | useChipsForPreview: false, 358 | previewChipProps: {}, 359 | previewGridClasses: {}, 360 | previewGridProps: {}, 361 | reset: undefined, 362 | showAlerts: true, 363 | alertSnackbarProps: { 364 | anchorOrigin: { 365 | horizontal: 'left', 366 | vertical: 'bottom', 367 | }, 368 | autoHideDuration: 6000, 369 | }, 370 | getFileLimitExceedMessage: (filesLimit) => (`Maximum allowed number of files exceeded. Only ${filesLimit} allowed`), 371 | getFileAddedMessage: (fileName) => (`File ${fileName} successfully added.`), 372 | getPreviewIcon: defaultGetPreviewIcon, 373 | getFileRemovedMessage: (fileName) => (`File ${fileName} removed.`), 374 | getDropRejectMessage: (rejectedFile, acceptedFiles, maxFileSize) => { 375 | let message = `File ${rejectedFile.name} was rejected. `; 376 | if (!acceptedFiles.includes(rejectedFile.type)) { 377 | message += 'File type not supported. '; 378 | } 379 | if (rejectedFile.size > maxFileSize) { 380 | message += 'File is too big. Size limit is ' + convertBytesToMbsOrKbs(maxFileSize) + '. '; 381 | } 382 | return message; 383 | }, 384 | }; 385 | 386 | export const FileObjectShape = PropTypes.shape({ 387 | file: PropTypes.object, 388 | data: PropTypes.any, 389 | }); 390 | 391 | DropzoneAreaBase.propTypes = { 392 | /** @ignore */ 393 | classes: PropTypes.object.isRequired, 394 | /** A list of file types to accept. 395 | * @see See [here](https://react-dropzone.js.org/#section-accepting-specific-file-types) for more details. 396 | */ 397 | acceptedFiles: PropTypes.arrayOf(PropTypes.string), 398 | /** Maximum number of files that can be loaded into the dropzone. */ 399 | filesLimit: PropTypes.number, 400 | /** Icon to be displayed inside the dropzone area. */ 401 | Icon: PropTypes.elementType, 402 | /** Currently loaded files. */ 403 | fileObjects: PropTypes.arrayOf(FileObjectShape), 404 | /** Maximum file size (in bytes) that the dropzone will accept. */ 405 | maxFileSize: PropTypes.number, 406 | /** Text inside the dropzone. */ 407 | dropzoneText: PropTypes.string, 408 | /** Custom CSS class name for dropzone container. */ 409 | dropzoneClass: PropTypes.string, 410 | /** Custom CSS class name for text inside the container. */ 411 | dropzoneParagraphClass: PropTypes.string, 412 | /** Disable feedback effect when dropping rejected files. */ 413 | disableRejectionFeedback: PropTypes.bool, 414 | /** Shows previews **BELOW** the dropzone. */ 415 | showPreviews: PropTypes.bool, 416 | /** Shows preview **INSIDE** the dropzone area. */ 417 | showPreviewsInDropzone: PropTypes.bool, 418 | /** Shows file name under the dropzone image. */ 419 | showFileNames: PropTypes.bool, 420 | /** Shows file name under the image. */ 421 | showFileNamesInPreview: PropTypes.bool, 422 | /** Uses deletable Material-UI Chip components to display file names. */ 423 | useChipsForPreview: PropTypes.bool, 424 | /** 425 | * Props to pass to the Material-UI Chip components.
Requires `useChipsForPreview` prop to be `true`. 426 | * 427 | * @see See [Material-UI Chip](https://material-ui.com/api/chip/#props) for available values. 428 | */ 429 | previewChipProps: PropTypes.object, 430 | /** 431 | * Custom CSS classNames for preview Grid components.
432 | * Should be in the form {container: string, item: string, image: string}. 433 | */ 434 | previewGridClasses: PropTypes.object, 435 | /** 436 | * Props to pass to the Material-UI Grid components.
437 | * Should be in the form {container: GridProps, item: GridProps}. 438 | * 439 | * @see See [Material-UI Grid](https://material-ui.com/api/grid/#props) for available GridProps values. 440 | */ 441 | previewGridProps: PropTypes.object, 442 | /** The label for the file preview section. */ 443 | previewText: PropTypes.string, 444 | /** 445 | * The node of button to clear dropzone. 446 | * 447 | * - can be a node to mount in a placeholder. 448 | * - can be an object: 449 | * - text (string) - text of the button 450 | * - onClick (function) - callback fired when reset button clicked 451 | */ 452 | reset: PropTypes.oneOfType([ 453 | PropTypes.node, 454 | PropTypes.shape({ 455 | text: PropTypes.string, 456 | onClick: PropTypes.func, 457 | }), 458 | ]), 459 | /** 460 | * Shows styled Material-UI Snackbar when files are dropped, deleted or rejected. 461 | * 462 | * - can be a boolean ("global" `true` or `false` for all alerts). 463 | * - can be an array, with values 'error', 'info', 'success' to select to view only certain alerts: 464 | * - showAlerts={['error']} for only errors. 465 | * - showAlerts={['error', 'info']} for both errors and info. 466 | * - showAlerts={['error', 'success', 'info']} is same as showAlerts={true}. 467 | * - showAlerts={[]} is same as showAlerts={false}. 468 | */ 469 | showAlerts: PropTypes.oneOfType([ 470 | PropTypes.bool, 471 | PropTypes.arrayOf(PropTypes.oneOf(['error', 'success', 'info'])), 472 | ]), 473 | /** 474 | * Props to pass to the Material-UI Snackbar components.
Requires `showAlerts` prop to be `true`. 475 | * 476 | * @see See [Material-UI Snackbar](https://material-ui.com/api/snackbar/#props) for available values. 477 | */ 478 | alertSnackbarProps: PropTypes.object, 479 | /** 480 | * Props to pass to the Dropzone component. 481 | * 482 | * @see See [Dropzone props](https://react-dropzone.js.org/#src) for available values. 483 | */ 484 | dropzoneProps: PropTypes.object, 485 | /** 486 | * Attributes applied to the input element. 487 | * 488 | * @see See [MDN Input File attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#Additional_attributes) for available values. 489 | */ 490 | inputProps: PropTypes.object, 491 | /** 492 | * Get alert message to display when files limit is exceed. 493 | * 494 | * *Default*: "Maximum allowed number of files exceeded. Only ${filesLimit} allowed" 495 | * 496 | * @param {number} filesLimit The `filesLimit` currently set for the component. 497 | */ 498 | getFileLimitExceedMessage: PropTypes.func, 499 | /** 500 | * Get alert message to display when a new file is added. 501 | * 502 | * *Default*: "File ${fileName} successfully added." 503 | * 504 | * @param {string} fileName The newly added file name. 505 | */ 506 | getFileAddedMessage: PropTypes.func, 507 | /** 508 | * Get alert message to display when a file is removed. 509 | * 510 | * *Default*: "File ${fileName} removed." 511 | * 512 | * @param {string} fileName The name of the removed file. 513 | */ 514 | getFileRemovedMessage: PropTypes.func, 515 | /** 516 | * Get alert message to display when a file is rejected onDrop. 517 | * 518 | * *Default*: "File ${rejectedFile.name} was rejected." 519 | * 520 | * @param {Object} rejectedFile The file that got rejected 521 | * @param {string[]} acceptedFiles The `acceptedFiles` prop currently set for the component 522 | * @param {number} maxFileSize The `maxFileSize` prop currently set for the component 523 | */ 524 | getDropRejectMessage: PropTypes.func, 525 | /** 526 | * A function which determines which icon to display for a file preview. 527 | * 528 | * *Default*: If its an image then displays a preview the image, otherwise it will display an attachment icon 529 | * 530 | * @param {FileObject} objectFile The file which the preview will belong to 531 | * @param {Object} classes The classes for the file preview icon, in the default case we use the 'image' className. 532 | */ 533 | getPreviewIcon: PropTypes.func, 534 | /** 535 | * Fired when new files are added to dropzone. 536 | * 537 | * @param {FileObject[]} newFiles The new files added to the dropzone. 538 | */ 539 | onAdd: PropTypes.func, 540 | /** 541 | * Fired when a file is deleted from the previews panel. 542 | * 543 | * @param {FileObject} deletedFileObject The file that was removed. 544 | * @param {number} index The index of the removed file object. 545 | */ 546 | onDelete: PropTypes.func, 547 | /** 548 | * Fired when the user drops files into the dropzone. 549 | * 550 | * @param {File[]} droppedFiles All the files dropped into the dropzone. 551 | * @param {Event} event The react-dropzone drop event. 552 | */ 553 | onDrop: PropTypes.func, 554 | /** 555 | * Fired when a file is rejected because of wrong file type, size or goes beyond the filesLimit. 556 | * 557 | * @param {File[]} rejectedFiles All the rejected files. 558 | * @param {Event} event The react-dropzone drop event. 559 | */ 560 | onDropRejected: PropTypes.func, 561 | /** 562 | * Fired when an alert is triggered. 563 | * 564 | * @param {string} message Alert message. 565 | * @param {string} variant One of "error", "info", "success". 566 | */ 567 | onAlert: PropTypes.func, 568 | }; 569 | 570 | export default withStyles(styles, {name: 'MuiDropzoneArea'})(DropzoneAreaBase); 571 | -------------------------------------------------------------------------------- /src/components/DropzoneAreaBase.md: -------------------------------------------------------------------------------- 1 | ### Import 2 | 3 | ```jsx static 4 | import { DropzoneAreaBase } from 'material-ui-dropzone'; 5 | ``` 6 | 7 | ### Basic usage 8 | 9 | ```jsx 10 | console.log('Added Files:', fileObjs)} 12 | onDelete={(fileObj) => console.log('Removed File:', fileObj)} 13 | onAlert={(message, variant) => console.log(`${variant}: ${message}`)} 14 | /> 15 | ``` 16 | 17 | ### Accept only images 18 | 19 | ```jsx 20 | console.log('Files:', files)} 24 | onAlert={(message, variant) => console.log(`${variant}: ${message}`)} 25 | /> 26 | ``` 27 | 28 | ### Custom Dropzone Icon 29 | 30 | ```jsx 31 | import { AttachFile } from '@mui/icons-material'; 32 | 33 | console.log('Files:', files)} 37 | onAlert={(message, variant) => console.log(`${variant}: ${message}`)} 38 | /> 39 | ``` 40 | 41 | ### Reset button 42 | 43 | ```jsx 44 | console.log('Added Files:', fileObjs)} 46 | onDelete={(fileObj) => console.log('Removed File:', fileObj)} 47 | onAlert={(message, variant) => console.log(`${variant}: ${message}`)} 48 | reset={{ 49 | onClick: () => console.log('reset'), 50 | }} 51 | /> 52 | ``` 53 | 54 | ### Custom reset button 55 | 56 | Allow to pass any valid DOM node valid to react to use custom reset button 57 | 58 | ```jsx 59 | console.log('Added Files:', fileObjs)} 61 | onDelete={(fileObj) => console.log('Removed File:', fileObj)} 62 | onAlert={(message, variant) => console.log(`${variant}: ${message}`)} 63 | reset={} 64 | /> 65 | ``` 66 | 67 | ### Custom Preview Icon 68 | 69 | Demonstration of how to customize the preview icon for: 70 | 71 | * PDF files 72 | * Video 73 | * Audio 74 | * Word Documents 75 | 76 | ```jsx 77 | import React, { useState } from 'react'; 78 | import { AttachFile, AudioTrack, Description, PictureAsPdf, Theaters } from '@mui/icons-material'; 79 | 80 | const handlePreviewIcon = (fileObject, classes) => { 81 | const {type} = fileObject.file 82 | const iconProps = { 83 | className : classes.image, 84 | } 85 | 86 | if (type.startsWith("video/")) return 87 | if (type.startsWith("audio/")) return 88 | 89 | switch (type) { 90 | case "application/msword": 91 | case "application/vnd.openxmlformats-officedocument.wordprocessingml.document": 92 | return 93 | case "application/pdf": 94 | return 95 | default: 96 | return 97 | } 98 | } 99 | 100 | const [fileObjects, setFileObjects] = useState([]); 101 | 102 | { 105 | console.log('onAdd', newFileObjs); 106 | setFileObjects([].concat(fileObjects, newFileObjs)); 107 | }} 108 | onDelete={deleteFileObj => { 109 | console.log('onDelete', deleteFileObj); 110 | }} 111 | getPreviewIcon={handlePreviewIcon} 112 | /> 113 | ``` 114 | -------------------------------------------------------------------------------- /src/components/DropzoneDialog.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import * as React from 'react'; 3 | 4 | import {createFileFromUrl, readFile} from '../helpers'; 5 | 6 | import DropzoneDialogBase from './DropzoneDialogBase'; 7 | 8 | 9 | /** 10 | * This component provides an uncontrolled version of the DropzoneDialogBase component. 11 | * 12 | * It supports all the Props and Methods from `DropzoneDialogBase` but keeps the files state internally. 13 | * 14 | * **Note** The `onSave` handler also returns `File[]` with all the accepted files. 15 | */ 16 | class DropzoneDialog extends React.PureComponent { 17 | state = { 18 | fileObjects: [], 19 | } 20 | 21 | componentDidMount() { 22 | this.loadInitialFiles(); 23 | } 24 | 25 | componentWillUnmount() { 26 | const {clearOnUnmount} = this.props; 27 | 28 | if (clearOnUnmount) { 29 | this.setState({ 30 | fileObjects: [], 31 | }, this.notifyFileChange); 32 | } 33 | } 34 | 35 | notifyFileChange = () => { 36 | const {onChange} = this.props; 37 | const {fileObjects} = this.state; 38 | 39 | if (onChange) { 40 | onChange(fileObjects.map((fileObject) => fileObject.file)); 41 | } 42 | } 43 | 44 | loadInitialFiles = async() => { 45 | const {initialFiles} = this.props; 46 | try { 47 | const fileObjs = await Promise.all( 48 | initialFiles.map(async(initialFile) => { 49 | let file; 50 | if (typeof initialFile === 'string' ) { 51 | file = await createFileFromUrl(initialFile); 52 | } else { 53 | file = initialFile; 54 | } 55 | const data = await readFile(file); 56 | 57 | return { 58 | file, 59 | data, 60 | }; 61 | }) 62 | ); 63 | 64 | this.setState((state) => ({ 65 | fileObjects: [].concat( 66 | state.fileObjects, 67 | fileObjs 68 | ), 69 | }), this.notifyFileChange); 70 | } catch (err) { 71 | console.log(err); 72 | } 73 | } 74 | 75 | addFiles = async(newFileObjects) => { 76 | const {filesLimit} = this.props; 77 | // Update component state 78 | this.setState((state) => { 79 | // Handle a single file 80 | if (filesLimit <= 1) { 81 | return { 82 | fileObjects: [].concat(newFileObjects[0]), 83 | }; 84 | } 85 | 86 | // Handle multiple files 87 | return { 88 | fileObjects: [].concat( 89 | state.fileObjects, 90 | newFileObjects 91 | ), 92 | }; 93 | }, this.notifyFileChange); 94 | } 95 | 96 | deleteFile = (removedFileObj, removedFileObjIdx) => { 97 | event.stopPropagation(); 98 | 99 | const {onDelete} = this.props; 100 | const {fileObjects} = this.state; 101 | 102 | // Calculate remaining fileObjects array 103 | const remainingFileObjs = fileObjects.filter((fileObject, i) => { 104 | return i !== removedFileObjIdx; 105 | }); 106 | 107 | // Notify removed file 108 | if (onDelete) { 109 | onDelete(removedFileObj.file); 110 | } 111 | 112 | // Update local state 113 | this.setState({ 114 | fileObjects: remainingFileObjs, 115 | }, this.notifyFileChange); 116 | } 117 | 118 | handleClose = (evt) => { 119 | const {clearOnUnmount, onClose} = this.props; 120 | 121 | if (onClose) { 122 | onClose(evt); 123 | } 124 | 125 | if (clearOnUnmount) { 126 | this.setState({ 127 | fileObjects: [], 128 | }, this.notifyFileChange); 129 | } 130 | } 131 | 132 | handleSave = (evt) => { 133 | const {clearOnUnmount, onSave} = this.props; 134 | const {fileObjects} = this.state; 135 | 136 | if (onSave) { 137 | onSave(fileObjects.map((fileObject) => fileObject.file), evt); 138 | } 139 | 140 | if (clearOnUnmount) { 141 | this.setState({ 142 | fileObjects: [], 143 | }, this.notifyFileChange); 144 | } 145 | } 146 | 147 | render() { 148 | const {fileObjects} = this.state; 149 | 150 | return ( 151 | 159 | ); 160 | } 161 | } 162 | 163 | DropzoneDialog.defaultProps = { 164 | clearOnUnmount: true, 165 | filesLimit: 3, 166 | initialFiles: [], 167 | }; 168 | 169 | DropzoneDialog.propTypes = { 170 | ...DropzoneDialogBase.propTypes, 171 | /** Clear uploaded files when component is unmounted. */ 172 | clearOnUnmount: PropTypes.bool, 173 | /** Maximum number of files that can be loaded into the dropzone. */ 174 | filesLimit: PropTypes.number, 175 | /** List containing File objects or URL strings.
176 | * **Note:** Please take care of CORS. 177 | */ 178 | initialFiles: PropTypes.arrayOf( 179 | PropTypes.oneOfType([ 180 | PropTypes.string, 181 | PropTypes.any, 182 | ]) 183 | ), 184 | /** 185 | * Fired when the user clicks the Submit button. 186 | * 187 | * @param {File[]} files All the files currently inside the Dropzone. 188 | * @param {SyntheticEvent} event The react `SyntheticEvent`. 189 | */ 190 | onSave: PropTypes.func, 191 | }; 192 | 193 | export default DropzoneDialog; 194 | -------------------------------------------------------------------------------- /src/components/DropzoneDialog.md: -------------------------------------------------------------------------------- 1 | ### Import 2 | 3 | ```jsx static 4 | import { DropzoneDialog } from 'material-ui-dropzone'; 5 | ``` 6 | 7 | ### Basic usage 8 | 9 | ```jsx 10 | import Button from '@mui/material/Button'; 11 | 12 | const [open, setOpen] = React.useState(false); 13 | 14 |
15 | 18 | 19 | setOpen(false)} 26 | onSave={(files) => { 27 | console.log('Files:', files); 28 | setOpen(false); 29 | }} 30 | showPreviews={true} 31 | showFileNamesInPreview={true} 32 | /> 33 |
34 | ``` 35 | -------------------------------------------------------------------------------- /src/components/DropzoneDialogBase.js: -------------------------------------------------------------------------------- 1 | import Button from '@mui/material/Button'; 2 | import Dialog from '@mui/material/Dialog'; 3 | import DialogActions from '@mui/material/DialogActions'; 4 | import DialogContent from '@mui/material/DialogContent'; 5 | import DialogTitle from '@mui/material/DialogTitle'; 6 | import PropTypes from 'prop-types'; 7 | import * as React from 'react'; 8 | 9 | import DropzoneAreaBase from './DropzoneAreaBase'; 10 | 11 | // Split props related to DropzoneDialog from DropzoneArea ones 12 | function splitDropzoneDialogProps(allProps) { 13 | const { 14 | cancelButtonText, 15 | dialogProps, 16 | dialogTitle, 17 | fullWidth, 18 | maxWidth, 19 | onClose, 20 | onSave, 21 | open, 22 | submitButtonText, 23 | ...dropzoneAreaProps 24 | } = allProps; 25 | 26 | return [ 27 | { 28 | cancelButtonText, 29 | dialogProps, 30 | dialogTitle, 31 | fullWidth, 32 | maxWidth, 33 | onClose, 34 | onSave, 35 | open, 36 | submitButtonText, 37 | }, 38 | dropzoneAreaProps, 39 | ]; 40 | } 41 | 42 | /** 43 | * This component provides the DropzoneArea inside of a Material-UI Dialog. 44 | * 45 | * It supports all the Props and Methods from `DropzoneAreaBase`. 46 | */ 47 | class DropzoneDialogBase extends React.PureComponent { 48 | render() { 49 | const [dropzoneDialogProps, dropzoneAreaProps] = splitDropzoneDialogProps(this.props); 50 | const { 51 | cancelButtonText, 52 | dialogProps, 53 | dialogTitle, 54 | fullWidth, 55 | maxWidth, 56 | onClose, 57 | onSave, 58 | open, 59 | submitButtonText, 60 | } = dropzoneDialogProps; 61 | 62 | // Submit button state 63 | const submitDisabled = dropzoneAreaProps.fileObjects.length === 0; 64 | 65 | return ( 66 | 73 | {dialogTitle} 74 | 75 | 76 | 79 | 80 | 81 | 82 | 88 | 89 | 96 | 97 | 98 | ); 99 | } 100 | } 101 | 102 | DropzoneDialogBase.defaultProps = { 103 | open: false, 104 | dialogTitle: 'Upload file', 105 | dialogProps: {}, 106 | fullWidth: true, 107 | maxWidth: 'sm', 108 | cancelButtonText: 'Cancel', 109 | submitButtonText: 'Submit', 110 | showPreviews: true, 111 | showPreviewsInDropzone: false, 112 | showFileNamesInPreview: true, 113 | }; 114 | 115 | DropzoneDialogBase.propTypes = { 116 | ...DropzoneAreaBase.propTypes, 117 | /** Sets whether the dialog is open or closed. */ 118 | open: PropTypes.bool, 119 | /** The Dialog title. */ 120 | dialogTitle: PropTypes.oneOfType([ 121 | PropTypes.string, 122 | PropTypes.element, 123 | ]), 124 | /** 125 | * Props to pass to the Material-UI Dialog components. 126 | * @see See [Material-UI Dialog](https://material-ui.com/api/dialog/#props) for available values. 127 | */ 128 | dialogProps: PropTypes.object, 129 | /** 130 | * If `true`, the dialog stretches to `maxWidth`.
131 | * Notice that the dialog width grow is limited by the default margin. 132 | */ 133 | fullWidth: PropTypes.bool, 134 | /** 135 | * Determine the max-width of the dialog. The dialog width grows with the size of the screen.
136 | * Set to `false` to disable `maxWidth`. 137 | */ 138 | maxWidth: PropTypes.string, 139 | /** Cancel button text in dialog. */ 140 | cancelButtonText: PropTypes.string, 141 | /** Submit button text in dialog. */ 142 | submitButtonText: PropTypes.string, 143 | /** 144 | * Fired when the modal is closed. 145 | * 146 | * @param {SyntheticEvent} event The react `SyntheticEvent` 147 | */ 148 | onClose: PropTypes.func, 149 | /** 150 | * Fired when the user clicks the Submit button. 151 | * 152 | * @param {SyntheticEvent} event The react `SyntheticEvent` 153 | */ 154 | onSave: PropTypes.func, 155 | /** 156 | * Shows previews **BELOW** the dropzone.
157 | * **Note:** By default previews show up under in the Dialog and inside in the standalone. 158 | */ 159 | showPreviews: PropTypes.bool, 160 | /** Shows preview **INSIDE** the dropzone area. */ 161 | showPreviewsInDropzone: PropTypes.bool, 162 | /** Shows file name under the image. */ 163 | showFileNamesInPreview: PropTypes.bool, 164 | }; 165 | 166 | export default DropzoneDialogBase; 167 | -------------------------------------------------------------------------------- /src/components/DropzoneDialogBase.md: -------------------------------------------------------------------------------- 1 | ### Import 2 | 3 | ```jsx static 4 | import { DropzoneDialogBase } from 'material-ui-dropzone'; 5 | ``` 6 | 7 | ### Basic usage 8 | 9 | ```jsx 10 | import Button from '@mui/material/Button'; 11 | import IconButton from '@mui/material/IconButton'; 12 | import CloseIcon from '@mui/icons-material/Close'; 13 | 14 | const [open, setOpen] = React.useState(false); 15 | const [fileObjects, setFileObjects] = React.useState([]); 16 | 17 | const dialogTitle = () => ( 18 | <> 19 | Upload file 20 | setOpen(false)}> 23 | 24 | 25 | 26 | ); 27 | 28 |
29 | 32 | 33 | { 42 | console.log('onAdd', newFileObjs); 43 | setFileObjects([].concat(fileObjects, newFileObjs)); 44 | }} 45 | onDelete={deleteFileObj => { 46 | console.log('onDelete', deleteFileObj); 47 | }} 48 | onClose={() => setOpen(false)} 49 | onSave={() => { 50 | console.log('onSave', fileObjects); 51 | setOpen(false); 52 | }} 53 | showPreviews={true} 54 | showFileNamesInPreview={true} 55 | /> 56 |
57 | ``` 58 | -------------------------------------------------------------------------------- /src/components/PreviewList.js: -------------------------------------------------------------------------------- 1 | import Chip from '@mui/material/Chip'; 2 | import Fab from '@mui/material/Fab'; 3 | import Grid from '@mui/material/Grid'; 4 | import Typography from '@mui/material/Typography'; 5 | import {withStyles} from '@mui/styles'; 6 | import DeleteIcon from '@mui/icons-material/Delete'; 7 | import clsx from 'clsx'; 8 | import * as React from 'react'; 9 | import PropTypes from 'prop-types'; 10 | 11 | const styles = ({palette, shape, spacing}) => ({ 12 | root: {}, 13 | imageContainer: { 14 | position: 'relative', 15 | zIndex: 10, 16 | textAlign: 'center', 17 | '&:hover $image': { 18 | opacity: 0.3, 19 | }, 20 | '&:hover $removeButton': { 21 | opacity: 1, 22 | }, 23 | }, 24 | image: { 25 | height: 100, 26 | width: 'initial', 27 | maxWidth: '100%', 28 | color: palette.text.primary, 29 | transition: 'all 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms', 30 | boxSizing: 'border-box', 31 | boxShadow: 'rgba(0, 0, 0, 0.12) 0 1px 6px, rgba(0, 0, 0, 0.12) 0 1px 4px', 32 | borderRadius: shape.borderRadius, 33 | zIndex: 5, 34 | opacity: 1, 35 | }, 36 | removeButton: { 37 | transition: '.5s ease', 38 | position: 'absolute', 39 | opacity: 0, 40 | top: spacing(-1), 41 | right: spacing(-1), 42 | width: 40, 43 | height: 40, 44 | '&:focus': { 45 | opacity: 1, 46 | }, 47 | }, 48 | }); 49 | 50 | function PreviewList({ 51 | fileObjects, 52 | handleRemove, 53 | showFileNames, 54 | useChipsForPreview, 55 | previewChipProps, 56 | previewGridClasses, 57 | previewGridProps, 58 | classes, 59 | getPreviewIcon, 60 | }) { 61 | if (useChipsForPreview) { 62 | return ( 63 | 70 | {fileObjects.map((fileObject, i) => { 71 | return ( 72 | 78 | 84 | 85 | ); 86 | })} 87 | 88 | ); 89 | } 90 | 91 | return ( 92 | 98 | {fileObjects.map((fileObject, i) => { 99 | return ( 100 | 107 | {getPreviewIcon(fileObject, classes)} 108 | 109 | {showFileNames && ( 110 | 111 | {fileObject.file.name} 112 | 113 | )} 114 | 115 | 120 | 121 | 122 | 123 | ); 124 | })} 125 | 126 | ); 127 | } 128 | 129 | PreviewList.propTypes = { 130 | classes: PropTypes.object.isRequired, 131 | fileObjects: PropTypes.arrayOf(PropTypes.object).isRequired, 132 | getPreviewIcon: PropTypes.func.isRequired, 133 | handleRemove: PropTypes.func.isRequired, 134 | previewChipProps: PropTypes.object, 135 | previewGridClasses: PropTypes.object, 136 | previewGridProps: PropTypes.object, 137 | showFileNames: PropTypes.bool, 138 | useChipsForPreview: PropTypes.bool, 139 | }; 140 | 141 | export default withStyles(styles, {name: 'MuiDropzonePreviewList'})(PreviewList); 142 | -------------------------------------------------------------------------------- /src/components/SnackbarContentWrapper.js: -------------------------------------------------------------------------------- 1 | import IconButton from '@mui/material/IconButton'; 2 | import SnackbarContent from '@mui/material/SnackbarContent'; 3 | import {withStyles} from '@mui/styles'; 4 | import CheckCircleIcon from '@mui/icons-material/CheckCircle'; 5 | import CloseIcon from '@mui/icons-material/Close'; 6 | import ErrorIcon from '@mui/icons-material/Error'; 7 | import InfoIcon from '@mui/icons-material/Info'; 8 | import WarningIcon from '@mui/icons-material/Warning'; 9 | import clsx from 'clsx'; 10 | import PropTypes from 'prop-types'; 11 | import * as React from 'react'; 12 | 13 | const variantIcon = { 14 | success: CheckCircleIcon, 15 | warning: WarningIcon, 16 | error: ErrorIcon, 17 | info: InfoIcon, 18 | }; 19 | 20 | const styles = (theme) => ({ 21 | successAlert: { 22 | backgroundColor: theme.palette.success.main, 23 | }, 24 | errorAlert: { 25 | backgroundColor: theme.palette.error.main, 26 | }, 27 | infoAlert: { 28 | backgroundColor: theme.palette.info.main, 29 | }, 30 | warningAlert: { 31 | backgroundColor: theme.palette.warning.main, 32 | }, 33 | message: { 34 | display: 'flex', 35 | alignItems: 'center', 36 | '& > svg': { 37 | marginRight: theme.spacing(1), 38 | }, 39 | }, 40 | icon: { 41 | fontSize: 20, 42 | opacity: 0.9, 43 | }, 44 | closeButton: {}, 45 | }); 46 | 47 | function SnackbarContentWrapper(props) { 48 | const {classes, className, message, onClose, variant, ...other} = props; 49 | const Icon = variantIcon[variant]; 50 | 51 | return ( 52 | 57 | 58 | {message} 59 | 60 | } 61 | action={[ 62 | 69 | 70 | , 71 | ]} 72 | {...other} 73 | /> 74 | ); 75 | } 76 | 77 | SnackbarContentWrapper.propTypes = { 78 | classes: PropTypes.object.isRequired, 79 | className: PropTypes.string, 80 | message: PropTypes.node, 81 | onClose: PropTypes.func, 82 | variant: PropTypes.oneOf(['success', 'warning', 'error', 'info']).isRequired, 83 | }; 84 | 85 | export default withStyles(styles, {name: 'MuiDropzoneSnackbar'})(SnackbarContentWrapper); 86 | -------------------------------------------------------------------------------- /src/helpers.js: -------------------------------------------------------------------------------- 1 | export function isImage(file) { 2 | if (file.type.split('/')[0] === 'image') { 3 | return true; 4 | } 5 | } 6 | 7 | export function convertBytesToMbsOrKbs(filesize) { 8 | let size = ''; 9 | if (filesize >= 1048576) { 10 | size = (filesize / 1048576) + ' megabytes'; 11 | } else if (filesize >= 1024) { 12 | size = (filesize / 1024) + ' kilobytes'; 13 | } else { 14 | size = filesize + ' bytes'; 15 | } 16 | return size; 17 | } 18 | 19 | export async function createFileFromUrl(url) { 20 | const response = await fetch(url); 21 | const data = await response.blob(); 22 | const metadata = {type: data.type}; 23 | const filename = url.replace(/\?.+/, '').split('/').pop(); 24 | return new File([data], filename, metadata); 25 | } 26 | 27 | export function readFile(file) { 28 | return new Promise((resolve, reject) => { 29 | const reader = new FileReader(); 30 | reader.onload = (event) => { 31 | resolve(event?.target?.result); 32 | }; 33 | reader.onerror = (event) => { 34 | reader.abort(); 35 | reject(event); 36 | }; 37 | reader.readAsDataURL(file); 38 | }); 39 | } 40 | -------------------------------------------------------------------------------- /src/index.d.ts: -------------------------------------------------------------------------------- 1 | import { ChipProps } from '@mui/material/Chip'; 2 | import { DialogProps } from '@mui/material/Dialog'; 3 | import { GridProps } from '@mui/material/Grid'; 4 | import { SnackbarProps } from '@mui/material/Snackbar'; 5 | import * as React from 'react'; 6 | import { DropEvent, DropzoneProps } from 'react-dropzone'; 7 | 8 | type Omit = Pick>; 9 | 10 | export interface FileObject { 11 | readonly file: File; 12 | readonly data: string | ArrayBuffer | null; 13 | } 14 | 15 | export interface PreviewIconProps { 16 | readonly classes: string; 17 | } 18 | 19 | export type AlertType = 'error' | 'success' | 'info'; 20 | 21 | // DropzoneAreaBase 22 | 23 | export type DropzoneAreaBaseClasses = { 24 | /** Material-UI class applied to the root Dropzone div */ 25 | root: string; 26 | /** Material-UI class applied to the Dropzone when 'active' */ 27 | active: string; 28 | /** Material-UI class applied to the Dropzone when 'invalid' */ 29 | invalid: string; 30 | /** Material-UI class applied to the Dropzone text container div */ 31 | textContainer: string; 32 | /** Material-UI class applied to the Dropzone text */ 33 | text: string; 34 | /** Material-UI class applied to the Dropzone icon */ 35 | icon: string; 36 | }; 37 | 38 | export type DropzoneAreaBaseProps = { 39 | classes?: Partial; 40 | acceptedFiles?: string[]; 41 | fileObjects: FileObject[]; 42 | filesLimit?: number; 43 | Icon?: React.ComponentType; 44 | maxFileSize?: number; 45 | dropzoneText?: string; 46 | previewText?: string; 47 | showPreviews?: boolean; 48 | showPreviewsInDropzone?: boolean; 49 | showFileNamesInPreview?: boolean; 50 | showFileNames?: boolean; 51 | useChipsForPreview?: boolean; 52 | previewChipProps?: ChipProps; 53 | previewGridClasses?: { 54 | container?: string; 55 | item?: string; 56 | image?: string; 57 | }; 58 | previewGridProps?: { 59 | container?: GridProps; 60 | item?: GridProps; 61 | }; 62 | showAlerts?: boolean | AlertType[]; 63 | alertSnackbarProps?: SnackbarProps; 64 | dropzoneProps?: DropzoneProps; 65 | inputProps?: React.HTMLProps; 66 | clearOnUnmount?: boolean; 67 | dropzoneClass?: string; 68 | dropzoneParagraphClass?: string; 69 | disableRejectionFeedback?: boolean; 70 | onAdd?: (newFiles: FileObject[]) => void; 71 | onDelete?: (deletedFileObject: FileObject, index: number) => void; 72 | onDrop?: (files: File[], event: DropEvent) => void; 73 | onDropRejected?: (files: File[], event: DropEvent) => void; 74 | onAlert?: (message: string, variant: AlertType) => void; 75 | getFileLimitExceedMessage?: (filesLimit: number) => string; 76 | getFileAddedMessage?: (fileName: string) => string; 77 | getFileRemovedMessage?: (fileName: string) => string; 78 | getDropRejectMessage?: ( 79 | rejectedFile: File, 80 | acceptedFiles: string[], 81 | maxFileSize: number 82 | ) => string; 83 | getPreviewIcon?: ( 84 | file: FileObject, 85 | classes: PreviewIconProps 86 | ) => React.ReactElement; 87 | }; 88 | 89 | export const DropzoneAreaBase: React.ComponentType; 90 | 91 | // DropzoneArea 92 | 93 | export type DropzoneAreaProps = Omit< 94 | DropzoneAreaBaseProps, 95 | 'fileObjects' | 'onAdd' | 'onDelete' 96 | > & { 97 | clearOnUnmount?: boolean; 98 | initialFiles?: (File | string)[]; 99 | onChange?: (files: File[]) => void; 100 | onDelete?: (file: File) => void; 101 | }; 102 | 103 | export const DropzoneArea: React.ComponentType; 104 | 105 | // DropzoneDialogBase 106 | 107 | export type DropzoneDialogBaseProps = DropzoneAreaBaseProps & { 108 | cancelButtonText?: string; 109 | dialogProps?: DialogProps; 110 | dialogTitle?: string | JSX.Element; 111 | fullWidth?: boolean; 112 | maxWidth?: string; 113 | onClose?: (event: React.SyntheticEvent) => void; 114 | onSave?: (event: React.SyntheticEvent) => void; 115 | open?: boolean; 116 | submitButtonText?: string; 117 | }; 118 | 119 | export const DropzoneDialogBase: React.ComponentType; 120 | 121 | // DropzoneDialog 122 | 123 | export type DropzoneDialogProps = Omit< 124 | DropzoneDialogBaseProps, 125 | 'fileObjects' | 'onAdd' | 'onDelete' | 'onSave' 126 | > & { 127 | clearOnUnmount?: boolean; 128 | initialFiles?: (File | string)[]; 129 | onSave?: (files: File[], event: React.SyntheticEvent) => void; 130 | onDelete?: (file: File) => void; 131 | }; 132 | 133 | export const DropzoneDialog: React.ComponentType; 134 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | export {default as DropzoneArea} from './components/DropzoneArea'; 2 | export {default as DropzoneAreaBase} from './components/DropzoneAreaBase'; 3 | export {default as DropzoneDialog} from './components/DropzoneDialog'; 4 | export {default as DropzoneDialogBase} from './components/DropzoneDialogBase'; 5 | -------------------------------------------------------------------------------- /styleguide.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | title: 'Material-UI Dropzone', 3 | showSidebar: false, 4 | sections: [ 5 | { 6 | name: '', 7 | content: './docs/intro.md', 8 | }, 9 | { 10 | name: 'Installation', 11 | content: './docs/installation.md', 12 | }, 13 | { 14 | name: 'Support', 15 | content: './docs/support.md', 16 | }, 17 | { 18 | name: 'Screenshots', 19 | content: './docs/screenshots.md', 20 | }, 21 | { 22 | name: 'Components', 23 | components: './src/components/**.js', 24 | }, 25 | { 26 | name: 'Theme', 27 | content: './docs/theming.md', 28 | }, 29 | ], 30 | skipComponentsWithoutExample: true, 31 | usageMode: 'expand', 32 | sortProps: (props) => props, 33 | webpackConfig: { 34 | module: { 35 | rules: [ 36 | { 37 | test: /\.jsx?$/, 38 | exclude: /node_modules/, 39 | loader: 'babel-loader', 40 | }, 41 | ], 42 | }, 43 | }, 44 | }; 45 | --------------------------------------------------------------------------------