kamotive_ui 1.2.15 → 1.2.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/FileAttach/FileAttach.js +10 -73
- package/dist/components/FileAttach/FileAttach.module.css +18 -33
- package/dist/components/FileItem/FileItem.js +100 -9
- package/dist/components/FileItem/FileItem.module.css +56 -34
- package/dist/components/FileListAttached/FileListAttached.module.css +46 -0
- package/dist/components/FileListAttached/FileListAtta/321/201hed.d.ts +3 -0
- package/dist/components/FileListAttached/FileListAtta/321/201hed.js +14 -0
- package/dist/components/FileLoader/FileLoader.d.ts +3 -0
- package/dist/components/FileLoader/FileLoader.js +162 -0
- package/dist/components/FileLoader/FileLoader.module.css +69 -0
- package/dist/components/IconButton/IconButton.js +8 -3
- package/dist/components/IconButton/IconButton.module.css +26 -16
- package/dist/components/Input/Input.module.css +4 -4
- package/dist/components/ProgressBar/ProgressBar.js +8 -4
- package/dist/components/Snackbar/Snackbar.js +2 -2
- package/dist/components/Snackbar/Snackbar.module.css +13 -6
- package/dist/index.d.ts +5 -2
- package/dist/index.js +4 -1
- package/dist/types/index.d.ts +84 -13
- package/package.json +1 -1
|
@@ -1,80 +1,17 @@
|
|
|
1
|
-
import React
|
|
2
|
-
import { useDropzone } from 'react-dropzone';
|
|
1
|
+
import React from 'react';
|
|
3
2
|
import styles from './FileAttach.module.css';
|
|
4
|
-
import { Typography } from '../Typography/Typography';
|
|
5
|
-
import { IconUpload } from '../../Icons';
|
|
6
|
-
import { FileItem } from '../FileItem/FileItem';
|
|
7
3
|
import classNames from 'classnames';
|
|
8
|
-
|
|
4
|
+
import { FileLoader } from '../FileLoader/FileLoader';
|
|
5
|
+
import { FileListAttaсhed } from '../FileListAttached/FileListAttaсhed';
|
|
6
|
+
export const FileAttach = ({ filesList = [], maxFileSize = 2, maxFileCount = 10, acceptedFormats = {
|
|
9
7
|
'image/*': ['.png', '.gif', '.jpeg', '.jpg'],
|
|
10
8
|
'application/pdf': ['.pdf'],
|
|
11
9
|
'application/msword': ['.doc', '.docx'],
|
|
12
|
-
}, addedFiles, setAddedFiles, onDownload,
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
if (file.size > maxFileSize * 1024 * 1024 * 1024) {
|
|
16
|
-
return {
|
|
17
|
-
code: 'name-too-large',
|
|
18
|
-
message: `Максимальный размер файла ${maxFileSize.toFixed(0)} ГБ`,
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
if (addedFiles.find((addedFile) => addedFile.name === file.name)) {
|
|
22
|
-
return {
|
|
23
|
-
code: 'repeating-file-name',
|
|
24
|
-
message: `Файл уже добавлен`,
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
if (addedFiles.length > maxFileCount - 1) {
|
|
28
|
-
return {
|
|
29
|
-
code: 'files-count-too-large',
|
|
30
|
-
message: `Максимальное количество файлов ${maxFileCount}`,
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
return null;
|
|
34
|
-
};
|
|
35
|
-
const { getRootProps, getInputProps } = useDropzone({
|
|
36
|
-
onDrop: (acceptedFiles, fileRejections) => {
|
|
37
|
-
setAddedFiles([...addedFiles, ...acceptedFiles]);
|
|
38
|
-
setErrorFiles([...errorFiles, ...fileRejections]);
|
|
39
|
-
},
|
|
40
|
-
validator: fileValidator,
|
|
41
|
-
accept: acceptedFormats,
|
|
42
|
-
maxFiles: maxFileCount,
|
|
43
|
-
disabled: disabled,
|
|
10
|
+
}, addedFiles, setAddedFiles, onDownload, onDelete, canAdd = true, canDelete = true, canDownload = true, position = 'bottom', lng = 'ru', className, style, }) => {
|
|
11
|
+
const fileAttachClasses = classNames(styles['fileAttach'], className, {
|
|
12
|
+
[styles[`fileAttach_position_${position}`]]: position,
|
|
44
13
|
});
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
setAddedFiles(addedFiles.filter((file) => file.name !== fileName));
|
|
49
|
-
};
|
|
50
|
-
const deleteRejectedFile = (errorFiles, setErrorFiles, fileName) => {
|
|
51
|
-
setErrorFiles(errorFiles.filter(({ file }) => file.name !== fileName));
|
|
52
|
-
};
|
|
53
|
-
// Функция для получения всех доступных форматов в виде строки
|
|
54
|
-
const getAcceptedFormatsString = (acceptedFormats) => {
|
|
55
|
-
const formats = [];
|
|
56
|
-
for (const key in acceptedFormats) {
|
|
57
|
-
if (acceptedFormats.hasOwnProperty(key)) {
|
|
58
|
-
formats.push(...acceptedFormats[key].map((format) => format.replace('.', '')));
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
return formats.join(', ');
|
|
62
|
-
};
|
|
63
|
-
return (React.createElement("section", { className: classNames(styles['fileAttach'], className), style: style },
|
|
64
|
-
React.createElement("div", Object.assign({}, getRootProps({ className: `${styles['dropzone']} ${disabled ? styles['disabled'] : ''}` })),
|
|
65
|
-
React.createElement("input", Object.assign({}, getInputProps())),
|
|
66
|
-
React.createElement(IconUpload, { htmlColor: disabled ? 'var(--grey-medium)' : 'var(--icons-grey)' }),
|
|
67
|
-
React.createElement(Typography, { variant: "Body1", color: disabled ? 'var(--grey-medium)' : 'var(--icons-grey)' },
|
|
68
|
-
React.createElement("span", { style: { textDecoration: 'underline' } }, "\u041D\u0430\u0436\u043C\u0438\u0442\u0435 \u043D\u0430 \u043E\u0431\u043B\u0430\u0441\u0442\u044C"),
|
|
69
|
-
React.createElement("span", null, " \u0438\u043B\u0438 \u043F\u0435\u0440\u0435\u0442\u0430\u0449\u0438\u0442\u0435 \u0444\u0430\u0439\u043B\u044B")),
|
|
70
|
-
React.createElement("div", null,
|
|
71
|
-
maxFileSize && (React.createElement(Typography, { variant: "Caption", color: "var(--grey-medium)" },
|
|
72
|
-
`Максимальный размер файла ${maxFileSize.toFixed(0)} ГБ`,
|
|
73
|
-
" ",
|
|
74
|
-
React.createElement("br", null))),
|
|
75
|
-
maxFileCount && (React.createElement(Typography, { variant: "Caption", color: "var(--grey-medium)" }, `За раз можно загрузить ${maxFileCount} ${maxFileCount > 1 ? `файлов` : `файл`}`)))),
|
|
76
|
-
acceptedFormats && (React.createElement(Typography, { variant: "Caption", color: "var(--grey-medium)" }, `Поддерживаемые форматы: ${getAcceptedFormatsString(acceptedFormats)}`)),
|
|
77
|
-
(addedFiles === null || addedFiles === void 0 ? void 0 : addedFiles.length) > 0 || (errorFiles === null || errorFiles === void 0 ? void 0 : errorFiles.length) > 0 ? (React.createElement("div", { className: styles['addedFiles'] },
|
|
78
|
-
acceptedFileItems,
|
|
79
|
-
fileRejectionItems)) : (React.createElement(Typography, { variant: "Caption", color: "var(--text-dark)" }, "\u0424\u0430\u0439\u043B\u044B \u043D\u0435 \u0434\u043E\u0431\u0430\u0432\u043B\u0435\u043D\u044B"))));
|
|
14
|
+
return (React.createElement("div", { className: fileAttachClasses, style: style },
|
|
15
|
+
React.createElement(FileLoader, { maxFileSize: maxFileSize, maxFileCount: maxFileCount, acceptedFormats: acceptedFormats, addedFiles: addedFiles, setAddedFiles: setAddedFiles, canAdd: canAdd, lng: lng }),
|
|
16
|
+
React.createElement(FileListAttaсhed, { filesList: filesList, onDelete: onDelete, onDownload: onDownload, canDelete: canDelete, canDownload: canDownload, lng: lng })));
|
|
80
17
|
};
|
|
@@ -1,36 +1,21 @@
|
|
|
1
1
|
.fileAttach {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
display: flex;
|
|
3
|
+
gap: 15px;
|
|
4
|
+
min-width: 260px;
|
|
5
|
+
flex: 1;
|
|
6
|
+
max-width: 100%;
|
|
7
|
+
}
|
|
8
|
+
.fileAttach_position_left {
|
|
9
|
+
flex-direction: row;
|
|
10
|
+
justify-content: space-between;
|
|
11
|
+
flex-wrap: wrap;
|
|
12
|
+
}
|
|
13
|
+
.fileAttach_position_right {
|
|
14
|
+
flex-direction: row-reverse;
|
|
15
|
+
justify-content: space-between;
|
|
16
|
+
flex-wrap: wrap;
|
|
6
17
|
}
|
|
7
18
|
|
|
8
|
-
.
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
align-items: center;
|
|
12
|
-
justify-content: center;
|
|
13
|
-
|
|
14
|
-
gap: 10px;
|
|
15
|
-
padding: 30px 20px;
|
|
16
|
-
border: 1px dashed var(--blue-main);
|
|
17
|
-
border-radius: 15px;
|
|
18
|
-
cursor: pointer;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
.dropzone:hover {
|
|
22
|
-
background-color: var(--fills-active);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
.dropzone.disabled {
|
|
26
|
-
background-color: var(--fills-disabled);
|
|
27
|
-
border-color: var(--grey-medium);
|
|
28
|
-
cursor: not-allowed;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
.addedFiles {
|
|
32
|
-
display: flex;
|
|
33
|
-
flex-direction: column;
|
|
34
|
-
gap: 10px;
|
|
35
|
-
overflow-y: auto;
|
|
36
|
-
}
|
|
19
|
+
.fileAttach_position_bottom {
|
|
20
|
+
flex-direction: column;
|
|
21
|
+
}
|
|
@@ -1,20 +1,111 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useEffect, useRef, useState } from 'react';
|
|
2
2
|
import styles from './FileItem.module.css';
|
|
3
3
|
import { Typography } from '../Typography/Typography';
|
|
4
4
|
import { ProgressBar } from '../ProgressBar/ProgressBar';
|
|
5
5
|
import { IconButton } from '../IconButton/IconButton';
|
|
6
6
|
import { IconClose10, IconDownload, IconFile } from '../../Icons';
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
import { Tooltip } from '../Tooltip/Tooltip';
|
|
8
|
+
import classNames from 'classnames';
|
|
9
|
+
export const FileItem = ({ file, loading = false, error = '', onDownload, onDelete, canDelete = true, canDownload = true, style, isAddedFile, isRejectedFile, }) => {
|
|
10
|
+
const [isLoadingFinished, setIsLoadingFinished] = useState(false);
|
|
11
|
+
const [animationDuration, setAnimationDuration] = useState(0);
|
|
12
|
+
const [maxLength, setMaxLength] = useState(30);
|
|
13
|
+
const fileItemRef = useRef(null);
|
|
14
|
+
const fileNameRef = useRef(null);
|
|
15
|
+
const calculateMaxLength = () => {
|
|
16
|
+
if (fileItemRef.current && fileNameRef.current) {
|
|
17
|
+
const containerWidth = fileItemRef.current.clientWidth;
|
|
18
|
+
const iconsWidth = 100; // Примерная ширина для иконок и отступов
|
|
19
|
+
const availableWidth = containerWidth - iconsWidth; // Доступная ширина для текста
|
|
20
|
+
const charWidth = 8;
|
|
21
|
+
const calculatedMaxLength = Math.floor(availableWidth / charWidth);
|
|
22
|
+
const newMaxLength = Math.max(calculatedMaxLength, 20);
|
|
23
|
+
setMaxLength(newMaxLength);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
const croppedName = (filename) => {
|
|
27
|
+
if (filename.length <= maxLength) {
|
|
28
|
+
return filename;
|
|
29
|
+
}
|
|
30
|
+
const lastDotIndex = filename.lastIndexOf('.');
|
|
31
|
+
let name, extension;
|
|
32
|
+
if (lastDotIndex === -1) {
|
|
33
|
+
name = filename;
|
|
34
|
+
extension = '';
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
name = filename.slice(0, lastDotIndex);
|
|
38
|
+
extension = filename.slice(lastDotIndex);
|
|
39
|
+
}
|
|
40
|
+
const availableLength = maxLength - extension.length;
|
|
41
|
+
if (availableLength <= 3) {
|
|
42
|
+
return filename.slice(0, maxLength - 3) + '...';
|
|
43
|
+
}
|
|
44
|
+
const charsFromStart = Math.ceil((availableLength - 3) / 2);
|
|
45
|
+
const charsFromEnd = Math.floor((availableLength - 3) / 2);
|
|
46
|
+
return name.slice(0, charsFromStart) + '...' + name.slice(name.length - charsFromEnd) + extension;
|
|
47
|
+
};
|
|
48
|
+
// Для расчета ширины контейнера и обновления maxLength при изменении размера окна
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
calculateMaxLength();
|
|
51
|
+
window.addEventListener('resize', calculateMaxLength);
|
|
52
|
+
return () => {
|
|
53
|
+
window.removeEventListener('resize', calculateMaxLength);
|
|
54
|
+
};
|
|
55
|
+
}, []);
|
|
56
|
+
// Расчет длительности анимации в зависимости от размера файла
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
if (!file.size)
|
|
59
|
+
return;
|
|
60
|
+
const baseDuration = 2000; // для маленьких файлов (до 100 КБ) 1 секунда
|
|
61
|
+
const fileSizeKB = file.size / 1024; // Размер файла в КБ
|
|
62
|
+
let calculatedDuration;
|
|
63
|
+
if (fileSizeKB <= 100) {
|
|
64
|
+
calculatedDuration = baseDuration;
|
|
65
|
+
}
|
|
66
|
+
else if (fileSizeKB <= 1024) {
|
|
67
|
+
const ratio = (fileSizeKB - 100) / (1024 - 100);
|
|
68
|
+
calculatedDuration = baseDuration + ratio * 2000; // от 2 до 4 секунд
|
|
69
|
+
}
|
|
70
|
+
else if (fileSizeKB <= 10240) {
|
|
71
|
+
const ratio = (fileSizeKB - 1024) / (10240 - 1024);
|
|
72
|
+
calculatedDuration = 4000 + ratio * 4000; // от 4 до 8 секунд
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
const ratio = Math.min((fileSizeKB - 10240) / (102400 - 10240), 1);
|
|
76
|
+
calculatedDuration = 10000 + ratio * 3000; // от 8 до 13 секунд
|
|
77
|
+
}
|
|
78
|
+
setAnimationDuration(calculatedDuration);
|
|
79
|
+
}, [file]);
|
|
80
|
+
const fileItemClasses = classNames(styles['fileItem'], {
|
|
81
|
+
[styles['loading']]: loading,
|
|
82
|
+
[styles['error']]: error,
|
|
83
|
+
[styles[`fileItem_attached`]]: !(isAddedFile || isRejectedFile),
|
|
84
|
+
});
|
|
85
|
+
const handleDeleteClick = (e, id) => {
|
|
86
|
+
e.stopPropagation();
|
|
87
|
+
if (onDelete && id) {
|
|
88
|
+
onDelete(id);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
const handleDownloadClick = (e, file) => {
|
|
92
|
+
e.stopPropagation();
|
|
93
|
+
if (onDownload && file) {
|
|
94
|
+
onDownload(file);
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
return (React.createElement("div", { className: fileItemClasses, style: style, ref: fileItemRef, onClick: () => !(isAddedFile || isRejectedFile) && canDownload && file && onDownload && onDownload(file) },
|
|
9
98
|
React.createElement("div", { className: styles['fileItemFile'] },
|
|
10
99
|
React.createElement("div", { className: styles['fileItemInfo'] },
|
|
11
100
|
React.createElement("div", { className: styles['fileItemIcon'] },
|
|
12
101
|
React.createElement(IconFile, { htmlColor: 'var(--icons-grey)' })),
|
|
13
|
-
React.createElement("div", { className: styles['fileItemName'] },
|
|
14
|
-
React.createElement(
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
102
|
+
React.createElement("div", { className: styles['fileItemName'], ref: fileNameRef },
|
|
103
|
+
file.filename.length > maxLength ? (React.createElement(Tooltip, { label: file.filename, position: "bottom-center", displayDelay: 300 },
|
|
104
|
+
React.createElement(Typography, { variant: "Body1", color: "var(--text-dark)" }, croppedName(file.filename)))) : (React.createElement(Typography, { variant: "Body1", color: "var(--text-dark)" }, croppedName(file.filename))),
|
|
105
|
+
file.size !== 0 && (React.createElement(Typography, { variant: "Caption", color: "var(--grey-medium)" }, `${file.size ? (file.size / 1024).toFixed(1) : 0} кБ`)))),
|
|
106
|
+
React.createElement("div", { className: styles['fileItemActions'] },
|
|
107
|
+
!(isAddedFile || isRejectedFile) && canDownload && (React.createElement(IconButton, { icon: React.createElement(IconDownload, null), onClick: (e) => handleDownloadClick(e, file), color: "var(--icons-grey)" })),
|
|
108
|
+
canDelete && (React.createElement(IconButton, { icon: React.createElement(IconClose10, null), onClick: (e) => handleDeleteClick(e, file.id || ''), color: "var(--icons-grey)" })))),
|
|
109
|
+
loading && !isLoadingFinished && (React.createElement(ProgressBar, { animated: true, size: "sm", value: 100, setIsLoadingFinished: setIsLoadingFinished, animationDuration: animationDuration })),
|
|
19
110
|
error && (React.createElement(Typography, { variant: "Caption", color: "var(--error-main)" }, error))));
|
|
20
111
|
};
|
|
@@ -1,45 +1,67 @@
|
|
|
1
|
-
|
|
2
1
|
.fileItem {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
align-items: flex-start;
|
|
28
|
-
overflow: hidden;
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: column;
|
|
4
|
+
gap: 5px;
|
|
5
|
+
justify-content: space-between;
|
|
6
|
+
padding: 10px 15px;
|
|
7
|
+
border: 1px solid var(--info-secondary);
|
|
8
|
+
border-radius: 10px;
|
|
9
|
+
}
|
|
10
|
+
.error {
|
|
11
|
+
border-color: var(--error-main);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.fileItemFile {
|
|
15
|
+
display: flex;
|
|
16
|
+
flex-direction: row;
|
|
17
|
+
align-items: center;
|
|
18
|
+
}
|
|
19
|
+
.fileItemInfo {
|
|
20
|
+
display: flex;
|
|
21
|
+
flex-direction: row;
|
|
22
|
+
gap: 5px;
|
|
23
|
+
flex-grow: 1;
|
|
24
|
+
align-items: flex-start;
|
|
25
|
+
overflow: hidden;
|
|
29
26
|
}
|
|
30
27
|
|
|
31
28
|
.fileItemIcon {
|
|
32
|
-
|
|
29
|
+
flex-shrink: 0;
|
|
33
30
|
}
|
|
34
31
|
|
|
35
32
|
.fileItemName {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
33
|
+
display: flex;
|
|
34
|
+
flex-direction: column;
|
|
35
|
+
flex-grow: 1;
|
|
36
|
+
align-items: flex-start;
|
|
37
|
+
overflow: auto;
|
|
41
38
|
}
|
|
42
39
|
|
|
43
40
|
.fileIcon svg path {
|
|
44
|
-
|
|
41
|
+
stroke-width: 0.8;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.fileItem_attached {
|
|
45
|
+
cursor: pointer;
|
|
46
|
+
transition: all 0.15s ease-in-out;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.fileItem_attached:hover {
|
|
50
|
+
background-color: rgba(0, 0, 0, 0.03);
|
|
51
|
+
transform: translateY(-1px);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.fileItem_attached .fileIcon,
|
|
55
|
+
.fileItem_attached button {
|
|
56
|
+
background-color: transparent;
|
|
57
|
+
transition: background-color 0.15s ease-in-out;
|
|
58
|
+
border-radius: 50%;
|
|
59
|
+
height: 28px;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.fileItemActions {
|
|
63
|
+
display: flex;
|
|
64
|
+
justify-content: center;
|
|
65
|
+
align-items: center;
|
|
66
|
+
gap: 3px;
|
|
45
67
|
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
.fileList {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: column;
|
|
4
|
+
flex: 1;
|
|
5
|
+
min-width: 260px;
|
|
6
|
+
max-height: 100%;
|
|
7
|
+
overflow-y: auto;
|
|
8
|
+
position: relative;
|
|
9
|
+
}
|
|
10
|
+
.fileListHeader {
|
|
11
|
+
position: sticky;
|
|
12
|
+
top: 0;
|
|
13
|
+
z-index: 1;
|
|
14
|
+
margin-bottom: 5px;
|
|
15
|
+
}
|
|
16
|
+
.fileListFiles {
|
|
17
|
+
display: flex;
|
|
18
|
+
flex-direction: column;
|
|
19
|
+
gap: 5px;
|
|
20
|
+
overflow-y: auto;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/* Стилизация скроллбара для WebKit (Chrome, Safari, новые версии Edge) */
|
|
24
|
+
.fileList::-webkit-scrollbar {
|
|
25
|
+
width: 6px; /* Ширина скроллбара */
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.fileList::-webkit-scrollbar-track {
|
|
29
|
+
background: rgba(0, 0, 0, 0.03); /* Светлый фон для трека */
|
|
30
|
+
border-radius: 10px;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.fileList::-webkit-scrollbar-thumb {
|
|
34
|
+
background: var(--grey-medium, #c4c4c4); /* Цвет ползунка */
|
|
35
|
+
border-radius: 10px;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.fileList::-webkit-scrollbar-thumb:hover {
|
|
39
|
+
background: var(--grey-dark, #a0a0a0); /* Цвет ползунка при наведении */
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/* Стилизация скроллбара для Firefox */
|
|
43
|
+
.fileList {
|
|
44
|
+
scrollbar-width: thin; /* "auto", "thin" или "none" */
|
|
45
|
+
scrollbar-color: var(--grey-medium, #c4c4c4) rgba(0, 0, 0, 0.03); /* цвет ползунка и трека */
|
|
46
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import styles from './FileListAttached.module.css';
|
|
3
|
+
import { Typography } from '../Typography/Typography';
|
|
4
|
+
import { FileItem } from '../FileItem/FileItem';
|
|
5
|
+
import classNames from 'classnames';
|
|
6
|
+
export const FileListAttaсhed = ({ filesList, onDelete, onDownload, canDelete, canDownload, isInfoShown = true, lng = 'ru', className, style, }) => {
|
|
7
|
+
if (!filesList || filesList.length === 0) {
|
|
8
|
+
return lng === 'ru' ? (React.createElement(Typography, { variant: "Body2-SemiBold", color: "var(--grey-medium)", style: { marginTop: '5px' } }, "\u041D\u0435\u0442 \u043F\u0440\u0438\u043A\u0440\u0435\u043F\u043B\u0435\u043D\u043D\u044B\u0445 \u0444\u0430\u0439\u043B\u043E\u0432")) : (React.createElement(Typography, { variant: "Body2-SemiBold", color: "var(--grey-medium)", style: { marginTop: '5px' } }, "No attached files"));
|
|
9
|
+
}
|
|
10
|
+
return (React.createElement("div", { className: classNames(styles['fileList'], className), style: style },
|
|
11
|
+
isInfoShown &&
|
|
12
|
+
(lng === 'ru' ? (React.createElement(Typography, { variant: "Body2-SemiBold", color: "var(--text-dark)", style: { lineHeight: '20px' }, className: styles['fileListHeader'] }, `Прикрепленные файлы (${filesList.length})`)) : (React.createElement(Typography, { variant: "Body2-SemiBold", color: "var(--text-dark)", style: { lineHeight: '20px' }, className: styles['fileListHeader'] }, `Attached files (${filesList.length})`))),
|
|
13
|
+
React.createElement("div", { className: styles['fileListFiles'] }, filesList.map((file) => (React.createElement(FileItem, { key: file.id, file: file, onDownload: onDownload, onDelete: onDelete, canDelete: canDelete, canDownload: canDownload }))))));
|
|
14
|
+
};
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import { useDropzone } from 'react-dropzone';
|
|
3
|
+
import styles from './FileLoader.module.css';
|
|
4
|
+
import { Typography } from '../Typography/Typography';
|
|
5
|
+
import { IconUpload } from '../../Icons';
|
|
6
|
+
import { FileItem } from '../FileItem/FileItem';
|
|
7
|
+
import classNames from 'classnames';
|
|
8
|
+
export const FileLoader = ({ maxFileSize = 2, maxFileCount = 10, acceptedFormats = {
|
|
9
|
+
'image/*': ['.png', '.gif', '.jpeg', '.jpg'],
|
|
10
|
+
'application/pdf': ['.pdf'],
|
|
11
|
+
'application/msword': ['.doc', '.docx'],
|
|
12
|
+
}, addedFiles, setAddedFiles, canAdd = true, lng = 'ru', className, style, }) => {
|
|
13
|
+
const [isLoadingFiles, setIsLoadingFiles] = useState(false);
|
|
14
|
+
const [loadingFilesNames, setLoadingFilesNames] = useState([]);
|
|
15
|
+
const [errorFiles, setErrorFiles] = useState([]);
|
|
16
|
+
const [addedFilesFormated, setAddedFilesFormatted] = useState([]);
|
|
17
|
+
const fileValidator = (file) => {
|
|
18
|
+
if (file.size > maxFileSize * 1024 * 1024 * 1024) {
|
|
19
|
+
return {
|
|
20
|
+
code: 'name-too-large',
|
|
21
|
+
message: lng === 'ru'
|
|
22
|
+
? `Максимальный размер файла ${maxFileSize.toFixed(0)} ГБ`
|
|
23
|
+
: `Maximum file size ${maxFileSize.toFixed(0)} GB`,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
if (addedFiles.find((addedFile) => addedFile.name === file.name)) {
|
|
27
|
+
return {
|
|
28
|
+
code: 'repeating-file-name',
|
|
29
|
+
message: lng === 'ru' ? `Файл уже добавлен` : `File already added`,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
if (addedFiles.length > maxFileCount - 1) {
|
|
33
|
+
return {
|
|
34
|
+
code: 'files-count-too-large',
|
|
35
|
+
message: lng === 'ru' ? `Максимальное количество файлов ${maxFileCount}` : `Maximum number of files ${maxFileCount}`,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
return null;
|
|
39
|
+
};
|
|
40
|
+
const { getRootProps, getInputProps } = useDropzone({
|
|
41
|
+
onDrop: (acceptedFiles, fileRejections) => {
|
|
42
|
+
setAddedFiles([...addedFiles, ...acceptedFiles]);
|
|
43
|
+
//преобразование типа файлов для отрисовки в списке
|
|
44
|
+
const newFormatAttachments = acceptedFiles.map((file) => {
|
|
45
|
+
return {
|
|
46
|
+
id: `file-${file.name}`,
|
|
47
|
+
filename: file.name,
|
|
48
|
+
size: file.size,
|
|
49
|
+
type: file.type,
|
|
50
|
+
};
|
|
51
|
+
});
|
|
52
|
+
setLoadingFilesNames(newFormatAttachments.map((file) => file.filename));
|
|
53
|
+
setIsLoadingFiles(true);
|
|
54
|
+
setAddedFilesFormatted([...addedFilesFormated, ...newFormatAttachments]);
|
|
55
|
+
let formattedRejections = [];
|
|
56
|
+
// Проверяем, есть ли ошибка превышения количества файлов
|
|
57
|
+
const hasTooManyFilesError = fileRejections.some((rejection) => rejection.errors.some((error) => error.code === 'too-many-files'));
|
|
58
|
+
if (hasTooManyFilesError) {
|
|
59
|
+
const remainingFiles = Math.max(0, maxFileCount - addedFiles.length);
|
|
60
|
+
const filesToAdd = fileRejections.slice(0, remainingFiles).map((rejection) => rejection.file);
|
|
61
|
+
setAddedFiles([...addedFiles, ...filesToAdd]);
|
|
62
|
+
const newFormatFilesToAdd = filesToAdd.map((rejectionAdd) => ({
|
|
63
|
+
id: Math.random().toString(36).substring(2, 9),
|
|
64
|
+
filename: rejectionAdd.name,
|
|
65
|
+
size: rejectionAdd.size,
|
|
66
|
+
type: rejectionAdd.type,
|
|
67
|
+
}));
|
|
68
|
+
setAddedFilesFormatted([...addedFilesFormated, ...newFormatFilesToAdd]);
|
|
69
|
+
const filesToReject = fileRejections.slice(remainingFiles);
|
|
70
|
+
formattedRejections = filesToReject.map((rejection) => ({
|
|
71
|
+
errors: [
|
|
72
|
+
{
|
|
73
|
+
code: 'files-count-too-large',
|
|
74
|
+
message: lng === 'ru'
|
|
75
|
+
? `Максимальное количество файлов ${maxFileCount}`
|
|
76
|
+
: `Maximum number of files ${maxFileCount}`,
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
file: {
|
|
80
|
+
id: Math.random().toString(36).substring(2, 9),
|
|
81
|
+
filename: rejection.file.name,
|
|
82
|
+
size: rejection.file.size,
|
|
83
|
+
path: rejection.file.path,
|
|
84
|
+
},
|
|
85
|
+
}));
|
|
86
|
+
setErrorFiles([...errorFiles, ...formattedRejections]);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
formattedRejections = fileRejections.map((rejection) => ({
|
|
90
|
+
errors: rejection.errors,
|
|
91
|
+
file: {
|
|
92
|
+
id: Math.random().toString(36).substring(2, 9),
|
|
93
|
+
filename: rejection.file.name,
|
|
94
|
+
size: rejection.file.size,
|
|
95
|
+
path: rejection.file.path,
|
|
96
|
+
},
|
|
97
|
+
}));
|
|
98
|
+
setErrorFiles([...errorFiles, ...formattedRejections]);
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
validator: fileValidator,
|
|
102
|
+
accept: acceptedFormats,
|
|
103
|
+
maxFiles: maxFileCount,
|
|
104
|
+
disabled: !canAdd,
|
|
105
|
+
});
|
|
106
|
+
const handleDeleteFiles = (id) => {
|
|
107
|
+
var _a;
|
|
108
|
+
const filename = (_a = addedFilesFormated.find((file) => file.id === id)) === null || _a === void 0 ? void 0 : _a.filename;
|
|
109
|
+
setAddedFiles(addedFiles.filter((file) => file.name !== filename));
|
|
110
|
+
setAddedFilesFormatted(addedFilesFormated.filter((file) => file.filename !== filename));
|
|
111
|
+
setLoadingFilesNames(loadingFilesNames.filter((id) => id !== id));
|
|
112
|
+
};
|
|
113
|
+
const acceptedFileItems = addedFilesFormated.map((file) => {
|
|
114
|
+
return (React.createElement(FileItem, { key: file.id, file: file, loading: loadingFilesNames.includes(file.filename), onDelete: handleDeleteFiles, isAddedFile: true }));
|
|
115
|
+
});
|
|
116
|
+
const handleDeleteRejectedFile = (id) => {
|
|
117
|
+
setErrorFiles(errorFiles.filter((rejection) => rejection.file.id !== id));
|
|
118
|
+
};
|
|
119
|
+
const fileRejectionItems = errorFiles.map(({ file, errors }) => (React.createElement(FileItem, { key: file.id, file: file, error: errors[0].message, onDelete: handleDeleteRejectedFile, isRejectedFile: true })));
|
|
120
|
+
// Функция для получения всех доступных форматов в виде строки
|
|
121
|
+
const getAcceptedFormatsString = (acceptedFormats) => {
|
|
122
|
+
const formats = [];
|
|
123
|
+
for (const key in acceptedFormats) {
|
|
124
|
+
if (acceptedFormats.hasOwnProperty(key)) {
|
|
125
|
+
formats.push(...acceptedFormats[key].map((format) => format.replace('.', '')));
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return formats.join(', ');
|
|
129
|
+
};
|
|
130
|
+
useEffect(() => {
|
|
131
|
+
if (loadingFilesNames.length === 0 && isLoadingFiles) {
|
|
132
|
+
setIsLoadingFiles(false);
|
|
133
|
+
}
|
|
134
|
+
}, [loadingFilesNames, isLoadingFiles]);
|
|
135
|
+
return (React.createElement("section", { className: classNames(styles['fileLoader'], className), style: style },
|
|
136
|
+
React.createElement("div", Object.assign({}, getRootProps({ className: `${styles['dropzone']} ${!canAdd ? styles['disabled'] : ''}` })),
|
|
137
|
+
React.createElement("input", Object.assign({}, getInputProps())),
|
|
138
|
+
React.createElement(IconUpload, { htmlColor: !canAdd ? 'var(--grey-medium)' : 'var(--icons-grey)' }),
|
|
139
|
+
React.createElement(Typography, { variant: "Body1", color: !canAdd ? 'var(--grey-medium)' : 'var(--icons-grey)', style: { textAlign: 'center' } }, lng === 'ru' ? (React.createElement(React.Fragment, null,
|
|
140
|
+
React.createElement("span", { style: { textDecoration: 'underline' } }, "\u041D\u0430\u0436\u043C\u0438\u0442\u0435 \u043D\u0430 \u043E\u0431\u043B\u0430\u0441\u0442\u044C"),
|
|
141
|
+
" ",
|
|
142
|
+
React.createElement("span", null, " \u0438\u043B\u0438 \u043F\u0435\u0440\u0435\u0442\u0430\u0449\u0438\u0442\u0435 \u0444\u0430\u0439\u043B\u044B"))) : (React.createElement(React.Fragment, null,
|
|
143
|
+
React.createElement("span", { style: { textDecoration: 'underline' } }, "\u0421lick on this area"),
|
|
144
|
+
" ",
|
|
145
|
+
React.createElement("span", null, "or drag files here")))),
|
|
146
|
+
React.createElement("div", null,
|
|
147
|
+
maxFileSize &&
|
|
148
|
+
(lng === 'ru' ? (React.createElement(Typography, { variant: "Body2", color: "var(--grey-medium)" },
|
|
149
|
+
`Максимальный размер файла ${maxFileSize.toFixed(0)} ГБ`,
|
|
150
|
+
" ",
|
|
151
|
+
React.createElement("br", null))) : (React.createElement(Typography, { variant: "Body2", color: "var(--grey-medium)" },
|
|
152
|
+
`Maximum file size ${maxFileSize.toFixed(0)} GB`,
|
|
153
|
+
" ",
|
|
154
|
+
React.createElement("br", null)))),
|
|
155
|
+
maxFileCount &&
|
|
156
|
+
(lng === 'ru' ? (React.createElement(Typography, { variant: "Body2", color: "var(--grey-medium)" }, `За раз можно загрузить ${maxFileCount} ${maxFileCount > 1 ? `файлов` : `файл`}`)) : (React.createElement(Typography, { variant: "Body2", color: "var(--grey-medium)" }, `You can upload ${maxFileCount} ${maxFileCount > 1 ? `files` : `file`}`))))),
|
|
157
|
+
acceptedFormats &&
|
|
158
|
+
(lng === 'ru' ? (React.createElement(Typography, { variant: "Body2", color: "var(--grey-medium)" }, `Поддерживаемые форматы: ${getAcceptedFormatsString(acceptedFormats)}`)) : (React.createElement(Typography, { variant: "Body2", color: "var(--grey-medium)" }, `Supported formats: ${getAcceptedFormatsString(acceptedFormats)}`))),
|
|
159
|
+
(addedFiles === null || addedFiles === void 0 ? void 0 : addedFiles.length) > 0 || (errorFiles === null || errorFiles === void 0 ? void 0 : errorFiles.length) > 0 ? (React.createElement("div", { className: styles['addedFiles'] },
|
|
160
|
+
acceptedFileItems,
|
|
161
|
+
fileRejectionItems)) : lng === 'ru' ? (React.createElement(Typography, { variant: "Body2-SemiBold", color: "var(--grey-medium)", style: { marginTop: '5px' } }, "\u0424\u0430\u0439\u043B\u044B \u043D\u0435 \u0434\u043E\u0431\u0430\u0432\u043B\u0435\u043D\u044B")) : (React.createElement(Typography, { variant: "Body2-SemiBold", color: "var(--grey-medium)", style: { marginTop: '5px' } }, "Files not added"))));
|
|
162
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
.fileLoader {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: column;
|
|
4
|
+
height: inherit;
|
|
5
|
+
gap: 5px;
|
|
6
|
+
min-width: 260px;
|
|
7
|
+
flex: 1;
|
|
8
|
+
max-width: 100%;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.dropzone {
|
|
12
|
+
display: flex;
|
|
13
|
+
flex-direction: column;
|
|
14
|
+
align-items: center;
|
|
15
|
+
justify-content: center;
|
|
16
|
+
|
|
17
|
+
gap: 10px;
|
|
18
|
+
padding: 30px 20px;
|
|
19
|
+
border: 1px dashed var(--blue-main);
|
|
20
|
+
border-radius: 15px;
|
|
21
|
+
cursor: pointer;
|
|
22
|
+
margin-right: 10px;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.dropzone:hover {
|
|
26
|
+
background-color: var(--fills-active);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.dropzone.disabled {
|
|
30
|
+
background-color: var(--fills-disabled);
|
|
31
|
+
border-color: var(--grey-medium);
|
|
32
|
+
cursor: not-allowed;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.addedFiles {
|
|
36
|
+
display: flex;
|
|
37
|
+
flex-direction: column;
|
|
38
|
+
gap: 10px;
|
|
39
|
+
min-width: 260px;
|
|
40
|
+
flex: 1;
|
|
41
|
+
max-height: 100%;
|
|
42
|
+
overflow-y: auto;
|
|
43
|
+
padding-right: 10px;
|
|
44
|
+
margin-top: 10px;
|
|
45
|
+
}
|
|
46
|
+
/* Стилизация скроллбара для WebKit (Chrome, Safari, новые версии Edge) */
|
|
47
|
+
.addedFiles::-webkit-scrollbar {
|
|
48
|
+
width: 6px; /* Ширина скроллбара */
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.addedFiles::-webkit-scrollbar-track {
|
|
52
|
+
background: rgba(0, 0, 0, 0.03); /* Светлый фон для трека */
|
|
53
|
+
border-radius: 10px;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.addedFiles::-webkit-scrollbar-thumb {
|
|
57
|
+
background: var(--grey-medium, #c4c4c4); /* Цвет ползунка */
|
|
58
|
+
border-radius: 10px;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.addedFiles::-webkit-scrollbar-thumb:hover {
|
|
62
|
+
background: var(--grey-dark, #a0a0a0); /* Цвет ползунка при наведении */
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/* Стилизация скроллбара для Firefox */
|
|
66
|
+
.addedFiles {
|
|
67
|
+
scrollbar-width: thin; /* "auto", "thin" или "none" */
|
|
68
|
+
scrollbar-color: var(--grey-medium, #c4c4c4) rgba(0, 0, 0, 0.03); /* цвет ползунка и трека */
|
|
69
|
+
}
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import classNames from 'classnames';
|
|
3
3
|
import styles from './IconButton.module.css';
|
|
4
|
-
export const IconButton = ({ icon, size = 'md', color, style, disabled = false, onClick, children, className }) => {
|
|
5
|
-
const validChildren = React.Children.toArray(children).filter(child => React.isValidElement(child));
|
|
4
|
+
export const IconButton = ({ icon, size = 'md', color, style, disabled = false, onClick, children, className, }) => {
|
|
5
|
+
const validChildren = React.Children.toArray(children).filter((child) => React.isValidElement(child));
|
|
6
6
|
const renderIcon = icon || validChildren[0];
|
|
7
|
-
|
|
7
|
+
const combinedStyle = Object.assign(Object.assign(Object.assign({}, style), ((style === null || style === void 0 ? void 0 : style.backgroundColor) && {
|
|
8
|
+
'--hover-background': `color-mix(in ${style.backgroundColor} 85%, black)`,
|
|
9
|
+
})), ((style === null || style === void 0 ? void 0 : style.borderRadius) && {
|
|
10
|
+
'--hover-border-radius': style.borderRadius,
|
|
11
|
+
}));
|
|
12
|
+
return (React.createElement("button", { className: classNames(styles['iconButton'], styles[`iconButton--${size}`], className), disabled: disabled, "aria-disabled": disabled, type: "button", onClick: (e) => onClick(e), style: combinedStyle }, renderIcon &&
|
|
8
13
|
React.cloneElement(renderIcon, {
|
|
9
14
|
htmlColor: color,
|
|
10
15
|
strokeWidth: size === 'lg' ? '0.5' : size === 'md' ? '0.3' : '0.0',
|
|
@@ -1,26 +1,36 @@
|
|
|
1
1
|
.iconButton {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
cursor: pointer;
|
|
3
|
+
box-sizing: border-box;
|
|
4
|
+
background-color: var(--white);
|
|
5
|
+
border: none;
|
|
6
|
+
display: flex;
|
|
7
|
+
align-items: center;
|
|
8
|
+
justify-content: center;
|
|
9
|
+
aspect-ratio: 1 / 1;
|
|
6
10
|
}
|
|
7
11
|
|
|
8
|
-
.iconButton--sm svg{
|
|
9
|
-
|
|
10
|
-
|
|
12
|
+
.iconButton--sm svg {
|
|
13
|
+
width: 14px;
|
|
14
|
+
height: 14px;
|
|
11
15
|
}
|
|
12
16
|
|
|
13
|
-
.iconButton--md svg{
|
|
14
|
-
|
|
15
|
-
|
|
17
|
+
.iconButton--md svg {
|
|
18
|
+
width: 16px;
|
|
19
|
+
height: 16px;
|
|
16
20
|
}
|
|
17
21
|
|
|
18
|
-
.iconButton--lg svg{
|
|
19
|
-
|
|
20
|
-
|
|
22
|
+
.iconButton--lg svg {
|
|
23
|
+
width: 18px;
|
|
24
|
+
height: 18px;
|
|
21
25
|
}
|
|
22
26
|
|
|
23
27
|
.iconButton:disabled {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
28
|
+
cursor: not-allowed;
|
|
29
|
+
box-shadow: none;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.iconButton:hover {
|
|
33
|
+
background-color: var(--hover-background, var(--grey-extraLight));
|
|
34
|
+
border-radius: var(--hover-border-radius, 50%);
|
|
35
|
+
filter: brightness(var(--hover-brightness, 0.97));
|
|
36
|
+
}
|
|
@@ -143,13 +143,13 @@
|
|
|
143
143
|
color: var(--text-dark);
|
|
144
144
|
font-weight: var(--font-weight-semiBold);
|
|
145
145
|
}
|
|
146
|
-
.label--default
|
|
146
|
+
.label--default.lg {
|
|
147
147
|
font-size: 12px;
|
|
148
148
|
}
|
|
149
149
|
.label--default.md {
|
|
150
150
|
font-size: 10px;
|
|
151
151
|
}
|
|
152
|
-
.label--default
|
|
152
|
+
.label--default.sm {
|
|
153
153
|
font-size: 16px;
|
|
154
154
|
}
|
|
155
155
|
.label--left {
|
|
@@ -159,10 +159,10 @@
|
|
|
159
159
|
.label--left.lg {
|
|
160
160
|
font-size: 14px;
|
|
161
161
|
}
|
|
162
|
-
.label--left
|
|
162
|
+
.label--left.md {
|
|
163
163
|
font-size: 12px;
|
|
164
164
|
}
|
|
165
|
-
.label--left
|
|
165
|
+
.label--left.sm {
|
|
166
166
|
font-size: 10px;
|
|
167
167
|
}
|
|
168
168
|
|
|
@@ -5,7 +5,7 @@ import classNames from 'classnames';
|
|
|
5
5
|
/**
|
|
6
6
|
* Компонент ProgressBar отображает прогресс в виде заполненной полосы.
|
|
7
7
|
*/
|
|
8
|
-
export const ProgressBar = ({ value = 0, max = 100, size = 'md', showValue = true, animated = false, }) => {
|
|
8
|
+
export const ProgressBar = ({ value = 0, max = 100, size = 'md', showValue = true, animated = false, animationDuration = 8000, setIsLoadingFinished, }) => {
|
|
9
9
|
const [percent, setPercent] = useState(value);
|
|
10
10
|
const validPercentage = Math.min(Math.max(value, 0), max);
|
|
11
11
|
const progressBarClasses = classNames(styles['progress-bar'], styles[size], {
|
|
@@ -15,7 +15,7 @@ export const ProgressBar = ({ value = 0, max = 100, size = 'md', showValue = tru
|
|
|
15
15
|
useEffect(() => {
|
|
16
16
|
if (animated) {
|
|
17
17
|
const targetPercent = validPercentage;
|
|
18
|
-
const animationDuration = 8000; // Длительность анимации в миллисекундах
|
|
18
|
+
// const animationDuration = animationDuration ?? 8000; // Длительность анимации в миллисекундах
|
|
19
19
|
const stepTime = 100; // Интервал обновления в миллисекундах
|
|
20
20
|
const totalSteps = animationDuration / stepTime;
|
|
21
21
|
const increment = targetPercent / totalSteps;
|
|
@@ -25,6 +25,10 @@ export const ProgressBar = ({ value = 0, max = 100, size = 'md', showValue = tru
|
|
|
25
25
|
setPercent(Math.round(currentPercent));
|
|
26
26
|
if (currentPercent >= targetPercent) {
|
|
27
27
|
clearInterval(intervalId);
|
|
28
|
+
// Вызываем callback, когда прогресс достиг 100%
|
|
29
|
+
if (setIsLoadingFinished) {
|
|
30
|
+
setIsLoadingFinished(true);
|
|
31
|
+
}
|
|
28
32
|
}
|
|
29
33
|
}, stepTime);
|
|
30
34
|
return () => clearInterval(intervalId);
|
|
@@ -32,8 +36,8 @@ export const ProgressBar = ({ value = 0, max = 100, size = 'md', showValue = tru
|
|
|
32
36
|
else {
|
|
33
37
|
setPercent(validPercentage);
|
|
34
38
|
}
|
|
35
|
-
}, [animated, validPercentage]);
|
|
36
|
-
return (React.createElement("div", { className: styles[
|
|
39
|
+
}, [animated, validPercentage, setIsLoadingFinished, animationDuration]);
|
|
40
|
+
return (React.createElement("div", { className: styles['progress-bar--wrapper'] },
|
|
37
41
|
React.createElement("progress", { id: "linear-progress", className: progressBarClasses, value: percent, max: max }),
|
|
38
42
|
React.createElement("label", { htmlFor: "progress", className: styles['progress-bar-percentage'] }, showValue && (React.createElement(Typography, { variant: "Body1", color: '#9CA0A7', className: styles['progress-bar-percentage'] },
|
|
39
43
|
percent,
|
|
@@ -26,7 +26,7 @@ export const title = {
|
|
|
26
26
|
warning: 'Внимание',
|
|
27
27
|
info: 'Информация',
|
|
28
28
|
};
|
|
29
|
-
export const Snackbar = ({ children, type, duration = 10000, icon = true, onClose }) => {
|
|
29
|
+
export const Snackbar = ({ children, type, duration = 10000, icon = true, onClose, style }) => {
|
|
30
30
|
const [isVisible, setIsVisible] = useState(true);
|
|
31
31
|
useEffect(() => {
|
|
32
32
|
if (duration > 0) {
|
|
@@ -44,7 +44,7 @@ export const Snackbar = ({ children, type, duration = 10000, icon = true, onClos
|
|
|
44
44
|
if (!isVisible)
|
|
45
45
|
return null;
|
|
46
46
|
const snackbarClasses = classNames(styles['snackbar-wrapper'], styles[`snackbar--${type}`]);
|
|
47
|
-
return (React.createElement("div", { className: snackbarClasses },
|
|
47
|
+
return (React.createElement("div", { className: snackbarClasses, style: style },
|
|
48
48
|
React.createElement("div", { className: styles['snackbar-textAndIcon'] },
|
|
49
49
|
icon && icons[type],
|
|
50
50
|
React.createElement("div", { className: styles['snackbar-text'] },
|
|
@@ -5,12 +5,19 @@
|
|
|
5
5
|
padding: 10px 25px;
|
|
6
6
|
gap: 20px;
|
|
7
7
|
box-sizing: border-box;
|
|
8
|
-
position: relative;
|
|
8
|
+
/* position: relative; */
|
|
9
|
+
position: fixed;
|
|
10
|
+
top: 20px;
|
|
11
|
+
left: 50%;
|
|
12
|
+
transform: translateX(-50%);
|
|
13
|
+
z-index: 1000;
|
|
14
|
+
|
|
9
15
|
min-width: 340px;
|
|
10
16
|
max-width: 500px;
|
|
11
17
|
|
|
12
18
|
border-radius: 15px;
|
|
13
|
-
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.1)
|
|
19
|
+
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.1);
|
|
20
|
+
background-color: var(--white);
|
|
14
21
|
}
|
|
15
22
|
.snackbar-textAndIcon {
|
|
16
23
|
gap: 10px;
|
|
@@ -26,16 +33,16 @@
|
|
|
26
33
|
width: 100%;
|
|
27
34
|
}
|
|
28
35
|
.snackbar--success {
|
|
29
|
-
background-color: rgba(
|
|
36
|
+
background-color: rgba(216, 244, 223, 0.9);
|
|
30
37
|
}
|
|
31
38
|
.snackbar--error {
|
|
32
|
-
background-color: rgba(255,
|
|
39
|
+
background-color: rgba(255, 214, 212, 0.9);
|
|
33
40
|
}
|
|
34
41
|
.snackbar--warning {
|
|
35
|
-
background-color: rgba(255,
|
|
42
|
+
background-color: rgba(255, 240, 195, 0.9);
|
|
36
43
|
}
|
|
37
44
|
.snackbar--info {
|
|
38
|
-
background-color:
|
|
45
|
+
background-color: rgba(244, 244, 244, 0.9);
|
|
39
46
|
border: 1px solid #f2f2f7;
|
|
40
47
|
}
|
|
41
48
|
.button {
|
package/dist/index.d.ts
CHANGED
|
@@ -14,8 +14,10 @@ export { Tabs as Tabs } from './components/Tabs/Tabs';
|
|
|
14
14
|
export { Tag as Tag } from './components/Tag/Tag';
|
|
15
15
|
export { ToggleButton as ToggleButton } from './components/ToggleButton/ToggleButton';
|
|
16
16
|
export { Typography as Typography } from './components/Typography/Typography';
|
|
17
|
-
export { FileItem as FileItem } from './components/FileItem/FileItem';
|
|
18
17
|
export { FileAttach as FileAttach } from './components/FileAttach/FileAttach';
|
|
18
|
+
export { FileListAttaсhed as FileListAttaсhed } from './components/FileListAttached/FileListAttaсhed';
|
|
19
|
+
export { FileItem as FileItem } from './components/FileItem/FileItem';
|
|
20
|
+
export { FileLoader as FileLoader } from './components/FileLoader/FileLoader';
|
|
19
21
|
export { Spinner as Spinner } from './components/Spinner/Spinner';
|
|
20
22
|
export { Dialog as Dialog } from './components/Dialog/Dialog';
|
|
21
23
|
export { IconButton as IconButton } from './components/IconButton/IconButton';
|
|
@@ -24,5 +26,6 @@ export { ListItem as ListItem } from './components/ListItem/ListItem';
|
|
|
24
26
|
export { Breadcrumb as Breadcrumb } from './components/Breadcrumb/Breadcrumb';
|
|
25
27
|
export { Breadcrumbs as Breadcrumbs } from './components/Breadcrumbs/Breadcrumbs';
|
|
26
28
|
export { Tooltip as Tooltip } from './components/Tooltip/Tooltip';
|
|
27
|
-
export type { ButtonProps, InputProps, DateInputProps, TagProps, SettingTagProps, ToggleButtonProps, BaseOptions, TOptions, DropdownProps, TypographyProps, ProgressBarProps, ProgressLoaderProps, RadioProps, TabsProps, ColorPickerProps, SnackbarProps, FileItemProps,
|
|
29
|
+
export type { ButtonProps, InputProps, DateInputProps, TagProps, SettingTagProps, ToggleButtonProps, BaseOptions, TOptions, DropdownProps, TypographyProps, ProgressBarProps, ProgressLoaderProps, RadioProps, TabsProps, ColorPickerProps, SnackbarProps, FileAttachProps, FileListAttaсhedProps, FileItemProps, FileLoaderProps, SpinnerProps, DialogProps, IconButtonProps, BaseListProps, ListProps, ListItemProps, BreadcrumbProps, BreadcrumbsProps, TooltipProps, } from './types';
|
|
28
30
|
import './fonts.css';
|
|
31
|
+
import './colors.css';
|
package/dist/index.js
CHANGED
|
@@ -14,8 +14,10 @@ export { Tabs as Tabs } from './components/Tabs/Tabs';
|
|
|
14
14
|
export { Tag as Tag } from './components/Tag/Tag';
|
|
15
15
|
export { ToggleButton as ToggleButton } from './components/ToggleButton/ToggleButton';
|
|
16
16
|
export { Typography as Typography } from './components/Typography/Typography';
|
|
17
|
-
export { FileItem as FileItem } from './components/FileItem/FileItem';
|
|
18
17
|
export { FileAttach as FileAttach } from './components/FileAttach/FileAttach';
|
|
18
|
+
export { FileListAttaсhed as FileListAttaсhed } from './components/FileListAttached/FileListAttaсhed';
|
|
19
|
+
export { FileItem as FileItem } from './components/FileItem/FileItem';
|
|
20
|
+
export { FileLoader as FileLoader } from './components/FileLoader/FileLoader';
|
|
19
21
|
export { Spinner as Spinner } from './components/Spinner/Spinner';
|
|
20
22
|
export { Dialog as Dialog } from './components/Dialog/Dialog';
|
|
21
23
|
export { IconButton as IconButton } from './components/IconButton/IconButton';
|
|
@@ -25,3 +27,4 @@ export { Breadcrumb as Breadcrumb } from './components/Breadcrumb/Breadcrumb';
|
|
|
25
27
|
export { Breadcrumbs as Breadcrumbs } from './components/Breadcrumbs/Breadcrumbs';
|
|
26
28
|
export { Tooltip as Tooltip } from './components/Tooltip/Tooltip';
|
|
27
29
|
import './fonts.css';
|
|
30
|
+
import './colors.css';
|
package/dist/types/index.d.ts
CHANGED
|
@@ -252,6 +252,10 @@ export interface ProgressBarProps {
|
|
|
252
252
|
showValue?: boolean;
|
|
253
253
|
/** Анимация */
|
|
254
254
|
animated?: boolean;
|
|
255
|
+
/** Длительность анимации */
|
|
256
|
+
animationDuration?: number;
|
|
257
|
+
/**Для выставления флага окончания загрузки */
|
|
258
|
+
setIsLoadingFinished?: (value: boolean) => void;
|
|
255
259
|
}
|
|
256
260
|
export interface ProgressLoaderProps {
|
|
257
261
|
/** Значение */
|
|
@@ -348,27 +352,94 @@ export type SnackbarProps = {
|
|
|
348
352
|
/** Иконка */
|
|
349
353
|
icon?: boolean;
|
|
350
354
|
/** Длительность показа сообщения */
|
|
351
|
-
duration
|
|
355
|
+
duration?: number;
|
|
352
356
|
/** Функция обработки закрытия сообщения */
|
|
353
357
|
onClose?: () => void;
|
|
358
|
+
/** Стили передаваемые напрямую */
|
|
359
|
+
style?: CSSProperties;
|
|
354
360
|
};
|
|
355
|
-
export
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
361
|
+
export type TAttachments = {
|
|
362
|
+
id: string;
|
|
363
|
+
filename: string;
|
|
364
|
+
uri?: string;
|
|
359
365
|
size?: number;
|
|
366
|
+
createDateTime?: string;
|
|
367
|
+
updateDateTime?: string;
|
|
368
|
+
};
|
|
369
|
+
export interface FileAttachProps {
|
|
370
|
+
filesList: TAttachments[];
|
|
371
|
+
/** Максимальный размер файла */
|
|
372
|
+
maxFileSize?: number;
|
|
373
|
+
/** Максимальное количество файлов */
|
|
374
|
+
maxFileCount?: number;
|
|
375
|
+
/**Поддерживаемые форматы файлов */
|
|
376
|
+
acceptedFormats?: Accept;
|
|
377
|
+
/**Добавленные файлы */
|
|
378
|
+
addedFiles: File[];
|
|
379
|
+
/**Сосотояние для добавления файлов */
|
|
380
|
+
setAddedFiles: (addedFiles: File[]) => void;
|
|
381
|
+
/** Функция обработки скачивания файла */
|
|
382
|
+
onDownload?: (file: TAttachments) => void;
|
|
383
|
+
/** Функция обработки удаления файла */
|
|
384
|
+
onDelete?: (id: string) => void;
|
|
385
|
+
/**Разрешени на добавление файлов*/
|
|
386
|
+
canAdd?: boolean;
|
|
387
|
+
/**Разрешение на удаление файлов */
|
|
388
|
+
canDelete?: boolean;
|
|
389
|
+
/**Разрешение на скачивание файлов */
|
|
390
|
+
canDownload?: boolean;
|
|
391
|
+
/**Позиционирование блока прикрепленных файлов */
|
|
392
|
+
position?: 'left' | 'right' | 'bottom';
|
|
393
|
+
/** Язык */
|
|
394
|
+
lng?: 'ru' | 'en';
|
|
395
|
+
/** Дополнительный класс */
|
|
396
|
+
className?: string;
|
|
397
|
+
/** Стили передаваемые напрямую */
|
|
398
|
+
style?: React.CSSProperties;
|
|
399
|
+
}
|
|
400
|
+
export interface FileListAttaсhedProps {
|
|
401
|
+
/** Список прикрепленных файлов */
|
|
402
|
+
filesList: TAttachments[] | [] | undefined;
|
|
403
|
+
/** Функция обработки удаления файла */
|
|
404
|
+
onDelete?: (id: string) => void;
|
|
405
|
+
/** Функция обработки скачивания файла */
|
|
406
|
+
onDownload?: (file: TAttachments) => void;
|
|
407
|
+
/**Разрешение на удаление файлов */
|
|
408
|
+
canDelete?: boolean;
|
|
409
|
+
/**Разрешение на скачивание файлов */
|
|
410
|
+
canDownload?: boolean;
|
|
411
|
+
/**Флаг для показа информационного текста */
|
|
412
|
+
isInfoShown?: boolean;
|
|
413
|
+
/** Язык */
|
|
414
|
+
lng?: 'ru' | 'en';
|
|
415
|
+
/** Дополнительный класс */
|
|
416
|
+
className?: string;
|
|
417
|
+
/** Стили передаваемые напрямую */
|
|
418
|
+
style?: React.CSSProperties;
|
|
419
|
+
}
|
|
420
|
+
export interface FileItemProps {
|
|
421
|
+
/** Файл */
|
|
422
|
+
file: TAttachments;
|
|
360
423
|
/** Флаг загрузки файла */
|
|
361
424
|
loading?: boolean;
|
|
362
425
|
/** Текст ошибки загрузки файла */
|
|
363
426
|
error?: string;
|
|
364
427
|
/** Функция обработки скачивания файла */
|
|
365
|
-
onDownload?: () => void;
|
|
428
|
+
onDownload?: (file: TAttachments) => void;
|
|
366
429
|
/** Функция обработки удаления файла */
|
|
367
|
-
onDelete?: () => void;
|
|
430
|
+
onDelete?: (id: string) => void;
|
|
431
|
+
/**Разрешение на удаление файлов */
|
|
432
|
+
canDelete?: boolean;
|
|
433
|
+
/**Разрешение на скачивание файлов */
|
|
434
|
+
canDownload?: boolean;
|
|
368
435
|
/** Стили передаваемые напрямую */
|
|
369
436
|
style?: CSSProperties;
|
|
437
|
+
/** Флаг добавленного файла */
|
|
438
|
+
isAddedFile?: boolean;
|
|
439
|
+
/** Флаг отклоненного файла */
|
|
440
|
+
isRejectedFile?: boolean;
|
|
370
441
|
}
|
|
371
|
-
export interface
|
|
442
|
+
export interface FileLoaderProps {
|
|
372
443
|
/** Максимальный размер файла */
|
|
373
444
|
maxFileSize?: number;
|
|
374
445
|
/** Максимальное количество файлов */
|
|
@@ -379,10 +450,10 @@ export interface FileAttachProps {
|
|
|
379
450
|
addedFiles: File[];
|
|
380
451
|
/**Сосотояние для добавления файлов */
|
|
381
452
|
setAddedFiles: (addedFiles: File[]) => void;
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
453
|
+
/**Разрешени на добавление файлов*/
|
|
454
|
+
canAdd?: boolean;
|
|
455
|
+
/** Язык */
|
|
456
|
+
lng?: 'ru' | 'en';
|
|
386
457
|
/** Дополнительный класс */
|
|
387
458
|
className?: string;
|
|
388
459
|
/** Стили передаваемые напрямую */
|
|
@@ -416,7 +487,7 @@ export interface IconButtonProps {
|
|
|
416
487
|
/** Заблокированная кнопка */
|
|
417
488
|
disabled?: boolean;
|
|
418
489
|
/** Callback, который будет вызван при клике по кнопке */
|
|
419
|
-
onClick: () => void;
|
|
490
|
+
onClick: (e: React.MouseEvent) => void;
|
|
420
491
|
/** Дочерние элементы */
|
|
421
492
|
children?: ReactNode;
|
|
422
493
|
/** Дополнительный класс */
|