1 | import { useDropzone } from 'react-dropzone';
|
---|
2 | // @mui
|
---|
3 | import { alpha } from '@mui/material/styles';
|
---|
4 | import Box from '@mui/material/Box';
|
---|
5 | import Stack from '@mui/material/Stack';
|
---|
6 | import Button from '@mui/material/Button';
|
---|
7 | import IconButton from '@mui/material/IconButton';
|
---|
8 | import Typography from '@mui/material/Typography';
|
---|
9 | // assets
|
---|
10 | import { UploadIllustration } from 'src/assets/illustrations';
|
---|
11 | //
|
---|
12 | import Iconify from '../iconify';
|
---|
13 | //
|
---|
14 | import { UploadProps } from './types';
|
---|
15 | import RejectionFiles from './errors-rejection-files';
|
---|
16 | import MultiFilePreview from './preview-multi-file';
|
---|
17 | import SingleFilePreview from './preview-single-file';
|
---|
18 |
|
---|
19 | // ----------------------------------------------------------------------
|
---|
20 |
|
---|
21 | export default function Upload({
|
---|
22 | disabled,
|
---|
23 | multiple = false,
|
---|
24 | error,
|
---|
25 | helperText,
|
---|
26 | //
|
---|
27 | file,
|
---|
28 | onDelete,
|
---|
29 | //
|
---|
30 | files,
|
---|
31 | thumbnail,
|
---|
32 | onUpload,
|
---|
33 | onRemove,
|
---|
34 | onRemoveAll,
|
---|
35 | sx,
|
---|
36 | ...other
|
---|
37 | }: UploadProps) {
|
---|
38 | const { getRootProps, getInputProps, isDragActive, isDragReject, fileRejections } = useDropzone({
|
---|
39 | multiple,
|
---|
40 | disabled,
|
---|
41 | ...other,
|
---|
42 | });
|
---|
43 |
|
---|
44 | const hasFile = !!file && !multiple;
|
---|
45 |
|
---|
46 | const hasFiles = !!files && multiple && !!files.length;
|
---|
47 |
|
---|
48 | const hasError = isDragReject || !!error;
|
---|
49 |
|
---|
50 | const renderPlaceholder = (
|
---|
51 | <Stack spacing={3} alignItems="center" justifyContent="center" flexWrap="wrap">
|
---|
52 | <UploadIllustration sx={{ width: 1, maxWidth: 200 }} />
|
---|
53 | <Stack spacing={1} sx={{ textAlign: 'center' }}>
|
---|
54 | <Typography variant="h6">Drop or Select file</Typography>
|
---|
55 | <Typography variant="body2" sx={{ color: 'text.secondary' }}>
|
---|
56 | Drop files here or click
|
---|
57 | <Box
|
---|
58 | component="span"
|
---|
59 | sx={{
|
---|
60 | mx: 0.5,
|
---|
61 | color: 'primary.main',
|
---|
62 | textDecoration: 'underline',
|
---|
63 | }}
|
---|
64 | >
|
---|
65 | browse
|
---|
66 | </Box>
|
---|
67 | thorough your machine
|
---|
68 | </Typography>
|
---|
69 | </Stack>
|
---|
70 | </Stack>
|
---|
71 | );
|
---|
72 |
|
---|
73 | const renderSinglePreview = (
|
---|
74 | <SingleFilePreview imgUrl={typeof file === 'string' ? file : file?.preview} />
|
---|
75 | );
|
---|
76 |
|
---|
77 | const removeSinglePreview = hasFile && onDelete && (
|
---|
78 | <IconButton
|
---|
79 | size="small"
|
---|
80 | onClick={onDelete}
|
---|
81 | sx={{
|
---|
82 | top: 16,
|
---|
83 | right: 16,
|
---|
84 | zIndex: 9,
|
---|
85 | position: 'absolute',
|
---|
86 | color: (theme) => alpha(theme.palette.common.white, 0.8),
|
---|
87 | bgcolor: (theme) => alpha(theme.palette.grey[900], 0.72),
|
---|
88 | '&:hover': {
|
---|
89 | bgcolor: (theme) => alpha(theme.palette.grey[900], 0.48),
|
---|
90 | },
|
---|
91 | }}
|
---|
92 | >
|
---|
93 | <Iconify icon="mingcute:close-line" width={18} />
|
---|
94 | </IconButton>
|
---|
95 | );
|
---|
96 |
|
---|
97 | const renderMultiPreview = hasFiles && (
|
---|
98 | <>
|
---|
99 | <Box sx={{ my: 3 }}>
|
---|
100 | <MultiFilePreview files={files} thumbnail={thumbnail} onRemove={onRemove} />
|
---|
101 | </Box>
|
---|
102 |
|
---|
103 | <Stack direction="row" justifyContent="flex-end" spacing={1.5}>
|
---|
104 | {onRemoveAll && (
|
---|
105 | <Button color="inherit" variant="outlined" size="small" onClick={onRemoveAll}>
|
---|
106 | Remove All
|
---|
107 | </Button>
|
---|
108 | )}
|
---|
109 |
|
---|
110 | {onUpload && (
|
---|
111 | <Button
|
---|
112 | size="small"
|
---|
113 | variant="contained"
|
---|
114 | onClick={onUpload}
|
---|
115 | startIcon={<Iconify icon="eva:cloud-upload-fill" />}
|
---|
116 | >
|
---|
117 | Upload
|
---|
118 | </Button>
|
---|
119 | )}
|
---|
120 | </Stack>
|
---|
121 | </>
|
---|
122 | );
|
---|
123 |
|
---|
124 | return (
|
---|
125 | <Box sx={{ width: 1, position: 'relative', ...sx }}>
|
---|
126 | <Box
|
---|
127 | {...getRootProps()}
|
---|
128 | sx={{
|
---|
129 | p: 5,
|
---|
130 | outline: 'none',
|
---|
131 | borderRadius: 1,
|
---|
132 | cursor: 'pointer',
|
---|
133 | overflow: 'hidden',
|
---|
134 | position: 'relative',
|
---|
135 | bgcolor: (theme) => alpha(theme.palette.grey[500], 0.08),
|
---|
136 | border: (theme) => `1px dashed ${alpha(theme.palette.grey[500], 0.2)}`,
|
---|
137 | transition: (theme) => theme.transitions.create(['opacity', 'padding']),
|
---|
138 | '&:hover': {
|
---|
139 | opacity: 0.72,
|
---|
140 | },
|
---|
141 | ...(isDragActive && {
|
---|
142 | opacity: 0.72,
|
---|
143 | }),
|
---|
144 | ...(disabled && {
|
---|
145 | opacity: 0.48,
|
---|
146 | pointerEvents: 'none',
|
---|
147 | }),
|
---|
148 | ...(hasError && {
|
---|
149 | color: 'error.main',
|
---|
150 | borderColor: 'error.main',
|
---|
151 | bgcolor: (theme) => alpha(theme.palette.error.main, 0.08),
|
---|
152 | }),
|
---|
153 | ...(hasFile && {
|
---|
154 | padding: '24% 0',
|
---|
155 | }),
|
---|
156 | }}
|
---|
157 | >
|
---|
158 | <input {...getInputProps()} />
|
---|
159 |
|
---|
160 | {hasFile ? renderSinglePreview : renderPlaceholder}
|
---|
161 | </Box>
|
---|
162 |
|
---|
163 | {removeSinglePreview}
|
---|
164 |
|
---|
165 | {helperText && helperText}
|
---|
166 |
|
---|
167 | <RejectionFiles fileRejections={fileRejections} />
|
---|
168 |
|
---|
169 | {renderMultiPreview}
|
---|
170 | </Box>
|
---|
171 | );
|
---|
172 | }
|
---|