├── .gitattributes ├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json └── robots.txt └── src ├── App.css ├── App.js ├── assets ├── App.css ├── Demo-file-v1.pdf ├── cloud-upload-regular-240.png ├── file-blank-solid-240.png ├── file-css-solid-240.png ├── file-pdf-solid-240.png ├── file-png-solid-240.png └── package.json ├── components └── drop-file-input │ ├── DropFileInput.jsx │ └── drop-file-input.css ├── config └── ImageConfig.js └── index.js /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-drop-file-input 2 | 3 | React Drag and Drop file input 4 | 5 | # Video tutorial 6 | 7 | https://youtu.be/Aoz0eQAbEUo 8 | 9 | # Description 10 | 11 | Build React Drag and Drop file input 12 | 13 | # Resource 14 | 15 | Google font: https://fonts.google.com/ 16 | 17 | Boxicons: https://boxicons.com/ 18 | 19 | Images: https://unsplash.com/ 20 | 21 | # Preview 22 | 23 | !["React Drag and Drop file input"](https://user-images.githubusercontent.com/67447840/135160494-703a6872-3ac9-4030-ae4c-d7771186f58b.jpg "React Drag and Drop file input") 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "code", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.14.1", 7 | "@testing-library/react": "^11.2.7", 8 | "@testing-library/user-event": "^12.8.3", 9 | "react": "^17.0.2", 10 | "react-dom": "^17.0.2", 11 | "react-scripts": "4.0.3", 12 | "web-vitals": "^1.1.2" 13 | }, 14 | "scripts": { 15 | "start": "react-scripts start", 16 | "build": "react-scripts build", 17 | "test": "react-scripts test", 18 | "eject": "react-scripts eject" 19 | }, 20 | "eslintConfig": { 21 | "extends": [ 22 | "react-app", 23 | "react-app/jest" 24 | ] 25 | }, 26 | "browserslist": { 27 | "production": [ 28 | ">0.2%", 29 | "not dead", 30 | "not op_mini all" 31 | ], 32 | "development": [ 33 | "last 1 chrome version", 34 | "last 1 firefox version", 35 | "last 1 safari version" 36 | ] 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trananhtuat/react-drop-file-input/1ab50f4d1bc9ef7d5852a4f5689e9755bc0f2c85/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trananhtuat/react-drop-file-input/1ab50f4d1bc9ef7d5852a4f5689e9755bc0f2c85/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trananhtuat/react-drop-file-input/1ab50f4d1bc9ef7d5852a4f5689e9755bc0f2c85/public/logo512.png -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css2?family=Montserrat:wght@300;400;500;600&display=swap"); 2 | 3 | :root { 4 | --body-bg: #f5f8ff; 5 | --box-bg: #fff; 6 | --input-bg: #f5f8ff; 7 | --txt-color: #2f2d2f; 8 | --txt-second-color: #ccc; 9 | --border-color: #4267b2; 10 | --box-shadow: rgba(149, 157, 165, 0.2) 0px 8px 24px; 11 | } 12 | 13 | * { 14 | padding: 0; 15 | margin: 0; 16 | box-sizing: border-box; 17 | } 18 | 19 | body { 20 | font-family: "Montserrat", sans-serif; 21 | font-weight: 400; 22 | line-height: 1.5; 23 | background-color: var(--body-bg); 24 | color: var(--txt-color); 25 | 26 | display: flex; 27 | justify-content: center; 28 | padding-top: 100px; 29 | 30 | height: 100vh; 31 | } 32 | 33 | .box { 34 | background-color: var(--box-bg); 35 | padding: 30px; 36 | border-radius: 20px; 37 | box-shadow: var(--box-shadow); 38 | } 39 | 40 | .header { 41 | margin-bottom: 30px; 42 | text-align: center; 43 | } 44 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import './App.css'; 2 | 3 | import DropFileInput from './components/drop-file-input/DropFileInput'; 4 | 5 | function App() { 6 | 7 | const onFileChange = (files) => { 8 | console.log(files); 9 | } 10 | 11 | return ( 12 |
13 |

14 | React drop files input 15 |

16 | onFileChange(files)} 18 | /> 19 |
20 | ); 21 | } 22 | 23 | export default App; 24 | -------------------------------------------------------------------------------- /src/assets/App.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css2?family=Montserrat:wght@300;400;500;600&display=swap"); 2 | 3 | :root { 4 | --body-bg: #f5f8ff; 5 | --box-bg: #fff; 6 | --input-bg: #f5f8ff; 7 | --txt-color: #2f2d2f; 8 | --txt-second-color: #ccc; 9 | } 10 | 11 | * { 12 | padding: 0; 13 | margin: 0; 14 | box-sizing: border-box; 15 | } 16 | 17 | body { 18 | font-family: "Montserrat", sans-serif; 19 | background-color: var(--body-bg); 20 | color: var(--txt-color); 21 | font-weight: 400; 22 | line-height: 1.5; 23 | display: flex; 24 | align-items: flex-start; 25 | justify-content: center; 26 | height: 100vh; 27 | padding-top: 100px; 28 | } 29 | 30 | .box { 31 | background-color: var(--box-bg); 32 | padding: 30px; 33 | border-radius: 20px; 34 | box-shadow: rgba(149, 157, 165, 0.2) 0px 8px 24px; 35 | } 36 | -------------------------------------------------------------------------------- /src/assets/Demo-file-v1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trananhtuat/react-drop-file-input/1ab50f4d1bc9ef7d5852a4f5689e9755bc0f2c85/src/assets/Demo-file-v1.pdf -------------------------------------------------------------------------------- /src/assets/cloud-upload-regular-240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trananhtuat/react-drop-file-input/1ab50f4d1bc9ef7d5852a4f5689e9755bc0f2c85/src/assets/cloud-upload-regular-240.png -------------------------------------------------------------------------------- /src/assets/file-blank-solid-240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trananhtuat/react-drop-file-input/1ab50f4d1bc9ef7d5852a4f5689e9755bc0f2c85/src/assets/file-blank-solid-240.png -------------------------------------------------------------------------------- /src/assets/file-css-solid-240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trananhtuat/react-drop-file-input/1ab50f4d1bc9ef7d5852a4f5689e9755bc0f2c85/src/assets/file-css-solid-240.png -------------------------------------------------------------------------------- /src/assets/file-pdf-solid-240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trananhtuat/react-drop-file-input/1ab50f4d1bc9ef7d5852a4f5689e9755bc0f2c85/src/assets/file-pdf-solid-240.png -------------------------------------------------------------------------------- /src/assets/file-png-solid-240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trananhtuat/react-drop-file-input/1ab50f4d1bc9ef7d5852a4f5689e9755bc0f2c85/src/assets/file-png-solid-240.png -------------------------------------------------------------------------------- /src/assets/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-drop-file-input", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.14.1", 7 | "@testing-library/react": "^11.2.7", 8 | "@testing-library/user-event": "^12.8.3", 9 | "react": "^17.0.2", 10 | "react-dom": "^17.0.2", 11 | "react-scripts": "4.0.3", 12 | "web-vitals": "^1.1.2" 13 | }, 14 | "scripts": { 15 | "start": "react-scripts start", 16 | "build": "react-scripts build", 17 | "test": "react-scripts test", 18 | "eject": "react-scripts eject" 19 | }, 20 | "eslintConfig": { 21 | "extends": [ 22 | "react-app", 23 | "react-app/jest" 24 | ] 25 | }, 26 | "browserslist": { 27 | "production": [ 28 | ">0.2%", 29 | "not dead", 30 | "not op_mini all" 31 | ], 32 | "development": [ 33 | "last 1 chrome version", 34 | "last 1 firefox version", 35 | "last 1 safari version" 36 | ] 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/components/drop-file-input/DropFileInput.jsx: -------------------------------------------------------------------------------- 1 | import React, { useRef, useState } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | import './drop-file-input.css'; 5 | 6 | import { ImageConfig } from '../../config/ImageConfig'; 7 | import uploadImg from '../../assets/cloud-upload-regular-240.png'; 8 | 9 | const DropFileInput = props => { 10 | 11 | const wrapperRef = useRef(null); 12 | 13 | const [fileList, setFileList] = useState([]); 14 | 15 | const onDragEnter = () => wrapperRef.current.classList.add('dragover'); 16 | 17 | const onDragLeave = () => wrapperRef.current.classList.remove('dragover'); 18 | 19 | const onDrop = () => wrapperRef.current.classList.remove('dragover'); 20 | 21 | const onFileDrop = (e) => { 22 | const newFile = e.target.files[0]; 23 | if (newFile) { 24 | const updatedList = [...fileList, newFile]; 25 | setFileList(updatedList); 26 | props.onFileChange(updatedList); 27 | } 28 | } 29 | 30 | const fileRemove = (file) => { 31 | const updatedList = [...fileList]; 32 | updatedList.splice(fileList.indexOf(file), 1); 33 | setFileList(updatedList); 34 | props.onFileChange(updatedList); 35 | } 36 | 37 | return ( 38 | <> 39 |
46 |
47 | 48 |

Drag & Drop your files here

49 |
50 | 51 |
52 | { 53 | fileList.length > 0 ? ( 54 |
55 |

56 | Ready to upload 57 |

58 | { 59 | fileList.map((item, index) => ( 60 |
61 | 62 |
63 |

{item.name}

64 |

{item.size}B

65 |
66 | fileRemove(item)}>x 67 |
68 | )) 69 | } 70 |
71 | ) : null 72 | } 73 | 74 | ); 75 | } 76 | 77 | DropFileInput.propTypes = { 78 | onFileChange: PropTypes.func 79 | } 80 | 81 | export default DropFileInput; 82 | -------------------------------------------------------------------------------- /src/components/drop-file-input/drop-file-input.css: -------------------------------------------------------------------------------- 1 | .drop-file-input { 2 | position: relative; 3 | width: 400px; 4 | height: 200px; 5 | border: 2px dashed var(--border-color); 6 | border-radius: 20px; 7 | 8 | display: flex; 9 | align-items: center; 10 | justify-content: center; 11 | 12 | background-color: var(--input-bg); 13 | } 14 | 15 | .drop-file-input input { 16 | opacity: 0; 17 | position: absolute; 18 | top: 0; 19 | left: 0; 20 | width: 100%; 21 | height: 100%; 22 | cursor: pointer; 23 | } 24 | 25 | .drop-file-input:hover, 26 | .drop-file-input.dragover { 27 | opacity: 0.6; 28 | } 29 | 30 | .drop-file-input__label { 31 | text-align: center; 32 | color: var(--txt-second-color); 33 | font-weight: 600; 34 | padding: 10px; 35 | } 36 | 37 | .drop-file-input__label img { 38 | width: 100px; 39 | } 40 | 41 | .drop-file-preview { 42 | margin-top: 30px; 43 | } 44 | 45 | .drop-file-preview p { 46 | font-weight: 500; 47 | } 48 | 49 | .drop-file-preview__title { 50 | margin-bottom: 20px; 51 | } 52 | 53 | .drop-file-preview__item { 54 | position: relative; 55 | display: flex; 56 | margin-bottom: 10px; 57 | background-color: var(--input-bg); 58 | padding: 15px; 59 | border-radius: 20px; 60 | } 61 | 62 | .drop-file-preview__item img { 63 | width: 50px; 64 | margin-right: 20px; 65 | } 66 | 67 | .drop-file-preview__item__info { 68 | display: flex; 69 | flex-direction: column; 70 | justify-content: space-between; 71 | } 72 | 73 | .drop-file-preview__item__del { 74 | background-color: var(--box-bg); 75 | width: 40px; 76 | height: 40px; 77 | border-radius: 50%; 78 | display: flex; 79 | align-items: center; 80 | justify-content: center; 81 | position: absolute; 82 | right: 10px; 83 | top: 50%; 84 | transform: translateY(-50%); 85 | box-shadow: var(--box-shadow); 86 | cursor: pointer; 87 | opacity: 0; 88 | transition: opacity 0.3s ease; 89 | } 90 | 91 | .drop-file-preview__item:hover .drop-file-preview__item__del { 92 | opacity: 1; 93 | } 94 | -------------------------------------------------------------------------------- /src/config/ImageConfig.js: -------------------------------------------------------------------------------- 1 | import fileDefault from '../assets/file-blank-solid-240.png'; 2 | import fileCSS from '../assets/file-css-solid-240.png'; 3 | import filePdf from '../assets/file-pdf-solid-240.png'; 4 | import filePng from '../assets/file-png-solid-240.png'; 5 | 6 | export const ImageConfig = { 7 | default: fileDefault, 8 | pdf: filePdf, 9 | png: filePng, 10 | css: fileCSS 11 | } -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | ReactDOM.render( 6 | 7 | 8 | , 9 | document.getElementById('root') 10 | ); 11 | --------------------------------------------------------------------------------