├── .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 | 
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 |
[1]])
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 |
--------------------------------------------------------------------------------