kamotive_ui 2.3.26 → 4.5.26
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/Button/Button.d.ts +1 -1
- package/dist/components/Button/Button.js +6 -5
- package/dist/components/Button/Button.module.css +2 -1
- package/dist/components/Dropdown/Dropdown.d.ts +1 -1
- package/dist/components/Dropdown/Dropdown.js +98 -30
- package/dist/components/Dropdown/Dropdown.module.css +12 -4
- package/dist/components/FileLoader/FileLoader.js +105 -106
- package/dist/components/TextEditor/TextEditor.js +172 -573
- package/dist/components/TextEditor/TextEditor.module.css +8 -7
- package/dist/types/index.d.ts +24 -13
- package/package.json +5 -1
|
@@ -3,4 +3,4 @@ import { ButtonProps } from '../../types';
|
|
|
3
3
|
/**
|
|
4
4
|
* Компонент Button представляет собой кнопку, которую можно настроить с помощью различных параметров (размер, иконки, стили, состояние).
|
|
5
5
|
*/
|
|
6
|
-
export declare const Button: React.
|
|
6
|
+
export declare const Button: React.ForwardRefExoticComponent<Omit<ButtonProps, "ref"> & React.RefAttributes<HTMLButtonElement>>;
|
|
@@ -5,7 +5,7 @@ import { Typography } from '../Typography/Typography';
|
|
|
5
5
|
/**
|
|
6
6
|
* Компонент Button представляет собой кнопку, которую можно настроить с помощью различных параметров (размер, иконки, стили, состояние).
|
|
7
7
|
*/
|
|
8
|
-
export const Button = ({ label, variant = 'fill', size = 'md', mode, style, condition, icon, disabled = false, onClick, children, error, color, name, type = 'button', form }) => {
|
|
8
|
+
export const Button = React.forwardRef(({ label, variant = 'fill', size = 'md', mode, style, condition, icon, disabled = false, onClick, children, error, color, name, type = 'button', form, className, active, }, ref) => {
|
|
9
9
|
const btnIcon = icon || typeof children === 'object' && children;
|
|
10
10
|
let modeStyle = 'text';
|
|
11
11
|
if (mode) {
|
|
@@ -15,9 +15,10 @@ export const Button = ({ label, variant = 'fill', size = 'md', mode, style, cond
|
|
|
15
15
|
modeStyle = (!label && !children) ? 'icon' : 'default';
|
|
16
16
|
}
|
|
17
17
|
const buttonCondition = error ? 'error' : (condition || 'default');
|
|
18
|
-
const buttonClasses = classNames(styles['button'], styles[`button--${size}`], styles[`button--${modeStyle}`], {
|
|
18
|
+
const buttonClasses = classNames(styles['button'], styles[`button--${size}`], styles[`button--${modeStyle}`], className, {
|
|
19
19
|
[styles[`button--${variant}-${buttonCondition}`]]: buttonCondition && !color,
|
|
20
|
-
[styles[`button--${variant}-custom`]]: color && !error
|
|
20
|
+
[styles[`button--${variant}-custom`]]: color && !error,
|
|
21
|
+
[styles[`button--${variant}-${buttonCondition}--active`]]: active && buttonCondition && !color,
|
|
21
22
|
});
|
|
22
23
|
const iconColorFn = () => {
|
|
23
24
|
if (buttonCondition && !color) {
|
|
@@ -60,7 +61,7 @@ export const Button = ({ label, variant = 'fill', size = 'md', mode, style, cond
|
|
|
60
61
|
return (React.createElement("button", { className: buttonClasses },
|
|
61
62
|
React.createElement(Typography, { variant: "Body1" }, "\u041A\u043D\u043E\u043F\u043A\u0430")));
|
|
62
63
|
}
|
|
63
|
-
return (React.createElement("button", { className: buttonClasses, style: Object.assign(Object.assign({}, style), (color && !error ? {
|
|
64
|
+
return (React.createElement("button", { className: buttonClasses, ref: ref, style: Object.assign(Object.assign({}, style), (color && !error ? {
|
|
64
65
|
'--button-color': color,
|
|
65
66
|
'--button-hover-color': variant === 'fill' || variant === 'link' ? `color-mix(in srgb, ${color} 90%, black)` : `color-mix(in srgb, ${color} 10%, transparent)`,
|
|
66
67
|
'--button-active-color': variant === 'fill' || variant === 'link' ? `color-mix(in srgb, ${color} 80%, black)` : `color-mix(in srgb, ${color} 20%, transparent)`,
|
|
@@ -77,4 +78,4 @@ export const Button = ({ label, variant = 'fill', size = 'md', mode, style, cond
|
|
|
77
78
|
});
|
|
78
79
|
})(),
|
|
79
80
|
(modeStyle === 'text' || modeStyle === 'default') && (React.createElement(Typography, { variant: "Body1" }, label ? label : typeof children === 'string' && children))));
|
|
80
|
-
};
|
|
81
|
+
});
|
|
@@ -200,7 +200,8 @@
|
|
|
200
200
|
color: var(--blue-main);
|
|
201
201
|
border: 1px solid var(--blue-main);
|
|
202
202
|
}
|
|
203
|
-
.button--outline-default:hover
|
|
203
|
+
.button--outline-default:hover,
|
|
204
|
+
.button--outline-default--active {
|
|
204
205
|
background-color: rgba(13, 153, 255, 0.07);
|
|
205
206
|
color: var(--blue-main);
|
|
206
207
|
border: 1px solid var(--blue-main);
|
|
@@ -16,4 +16,4 @@ export interface DropdownListItemProps<T extends BaseOptions> {
|
|
|
16
16
|
isChild?: boolean;
|
|
17
17
|
}
|
|
18
18
|
export declare const DropdownListItem: <T extends BaseOptions>({ item, getOptionLabel, size, selectedItem, variant, onChange, isActive, activeIndex, index, isChild, }: DropdownListItemProps<T>) => React.JSX.Element;
|
|
19
|
-
export declare const Dropdown: <T extends BaseOptions>({ options, id, label, placeholder, required, value, defaultValue, onChange, showLoadMore, loadMore, getOptionLabel, variant, size, style, className, isLeftLabel, isDivider, disabled, readOnly, isOpened, error, helperText, onOpen, onClick, onBlur, onFocus, onClose, clearable, enableAutocomplete, onSearch,
|
|
19
|
+
export declare const Dropdown: <T extends BaseOptions>({ options, id, label, placeholder, required, value, defaultValue, onChange, showLoadMore, loadMore, getOptionLabel, variant, size, style, className, isLeftLabel, isDivider, disabled, readOnly, isOpened, error, helperText, onOpen, onClick, onBlur, onFocus, onClose, clearable, enableAutocomplete, onSearch, isOptionsLoading, isSearchLoading, noOptionsText, lng, multiple, limitTags, }: DropdownProps<T>) => React.JSX.Element;
|
|
@@ -26,9 +26,15 @@ const getComparisonValue = (item, getOptionLabel) => {
|
|
|
26
26
|
if ('id' in item)
|
|
27
27
|
return item.id;
|
|
28
28
|
}
|
|
29
|
-
// Иначе используем сам объект
|
|
30
29
|
return item;
|
|
31
30
|
};
|
|
31
|
+
const getItemId = (item) => {
|
|
32
|
+
if (!item || typeof item !== 'object')
|
|
33
|
+
return null;
|
|
34
|
+
if ('id' in item)
|
|
35
|
+
return item.id;
|
|
36
|
+
return null;
|
|
37
|
+
};
|
|
32
38
|
function checkItem(item, getOptionLabel, disabled, isDivider) {
|
|
33
39
|
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z;
|
|
34
40
|
if (typeof item === 'string' || typeof item === 'number') {
|
|
@@ -140,9 +146,12 @@ export const DropdownListItem = ({ item, getOptionLabel, size = 'md', selectedIt
|
|
|
140
146
|
}, [item, onChange]);
|
|
141
147
|
const hasChildren = item !== null && typeof item === 'object' && 'children' in item && Array.isArray(item.children) && item.children.length > 0;
|
|
142
148
|
const isDisabled = item !== null && typeof item === 'object' && 'disabled' in item && item.disabled;
|
|
149
|
+
const itemId = getItemId(item);
|
|
143
150
|
const isSelectedItem = Array.isArray(selectedItem)
|
|
144
|
-
? selectedItem.some((i) => getComparisonValue(i, getOptionLabel) === getComparisonValue(item, getOptionLabel))
|
|
145
|
-
:
|
|
151
|
+
? selectedItem.some((i) => itemId !== null ? getItemId(i) === itemId : getComparisonValue(i, getOptionLabel) === getComparisonValue(item, getOptionLabel))
|
|
152
|
+
: itemId !== null
|
|
153
|
+
? getItemId(selectedItem) === itemId
|
|
154
|
+
: getComparisonValue(selectedItem, getOptionLabel) === getComparisonValue(item, getOptionLabel);
|
|
146
155
|
const itemContainerClasses = classNames(styles[`item--container`], {
|
|
147
156
|
[styles['item--container--active']]: isActive,
|
|
148
157
|
[styles['item--container--parent']]: hasChildren && !isChild,
|
|
@@ -180,9 +189,11 @@ export const DropdownListItem = ({ item, getOptionLabel, size = 'md', selectedIt
|
|
|
180
189
|
})))));
|
|
181
190
|
return showTooltip ? (React.createElement(Tooltip, { label: ((_b = getComparisonValue(item, getOptionLabel)) === null || _b === void 0 ? void 0 : _b.toString()) || '', position: "bottom-left" }, itemContent)) : (itemContent);
|
|
182
191
|
};
|
|
183
|
-
export const Dropdown = ({ options, id, label, placeholder, required = false, value, defaultValue, onChange, showLoadMore = false, loadMore, getOptionLabel, variant = 'text', size = 'lg', style, className, isLeftLabel = false, isDivider = false, disabled = false, readOnly = false, isOpened = false, error = false, helperText, onOpen, onClick, onBlur, onFocus, onClose, clearable = true, enableAutocomplete = false, onSearch,
|
|
192
|
+
export const Dropdown = ({ options, id, label, placeholder, required = false, value, defaultValue, onChange, showLoadMore = false, loadMore, getOptionLabel, variant = 'text', size = 'lg', style, className, isLeftLabel = false, isDivider = false, disabled = false, readOnly = false, isOpened = false, error = false, helperText, onOpen, onClick, onBlur, onFocus, onClose, clearable = true, enableAutocomplete = false, onSearch, isOptionsLoading, isSearchLoading, noOptionsText, lng = 'ru', multiple = false, limitTags = 1, }) => {
|
|
184
193
|
const inputRef = useRef(null);
|
|
185
194
|
const containerRef = useRef(null);
|
|
195
|
+
const dropdownRef = useRef(null);
|
|
196
|
+
const hoveredIndexRef = useRef(-1);
|
|
186
197
|
const onCloseRef = useRef(onClose);
|
|
187
198
|
const labelChipRef = useRef(new Map());
|
|
188
199
|
const selectedItemRef = useRef(null);
|
|
@@ -266,6 +277,14 @@ export const Dropdown = ({ options, id, label, placeholder, required = false, va
|
|
|
266
277
|
setIsOpen(newIsOpen);
|
|
267
278
|
if (newIsOpen) {
|
|
268
279
|
onOpen === null || onOpen === void 0 ? void 0 : onOpen(event);
|
|
280
|
+
const currentItem = multiple ? null : selectedItem;
|
|
281
|
+
const initialIndex = currentItem
|
|
282
|
+
? displayOptions.findIndex((opt) => {
|
|
283
|
+
const id = getItemId(opt);
|
|
284
|
+
return id !== null ? id === getItemId(currentItem) : getComparisonValue(opt, getOptionLabel) === getComparisonValue(currentItem, getOptionLabel);
|
|
285
|
+
})
|
|
286
|
+
: -1;
|
|
287
|
+
setActiveIndex(initialIndex);
|
|
269
288
|
if (enableAutocomplete && onChange) {
|
|
270
289
|
const selectedValue = ((_a = getComparisonValue(selectedItem, getOptionLabel)) === null || _a === void 0 ? void 0 : _a.toString()) || '';
|
|
271
290
|
setIsInitialOpen(true);
|
|
@@ -280,6 +299,7 @@ export const Dropdown = ({ options, id, label, placeholder, required = false, va
|
|
|
280
299
|
else if (!newIsOpen) {
|
|
281
300
|
onClose === null || onClose === void 0 ? void 0 : onClose(event);
|
|
282
301
|
setSearchValue('');
|
|
302
|
+
hoveredIndexRef.current = -1;
|
|
283
303
|
}
|
|
284
304
|
};
|
|
285
305
|
const onChangeHandler = (event, item) => {
|
|
@@ -289,23 +309,32 @@ export const Dropdown = ({ options, id, label, placeholder, required = false, va
|
|
|
289
309
|
setErrorInput(false);
|
|
290
310
|
setSearchValue('');
|
|
291
311
|
setSelectedItems((selectedItems) => {
|
|
292
|
-
const
|
|
293
|
-
const
|
|
294
|
-
|
|
295
|
-
|
|
312
|
+
const itemId = getItemId(item);
|
|
313
|
+
const isSame = (i) => itemId !== null
|
|
314
|
+
? getItemId(i) === itemId
|
|
315
|
+
: getComparisonValue(i, getOptionLabel) === getComparisonValue(item, getOptionLabel);
|
|
316
|
+
const isSelected = selectedItems.some(isSame);
|
|
317
|
+
const newSelectedItems = isSelected
|
|
318
|
+
? selectedItems.filter((i) => !isSame(i))
|
|
319
|
+
: [...selectedItems, item];
|
|
296
320
|
const newEvent = Object.assign(Object.assign({}, event), { currentTarget: Object.assign(Object.assign({}, event.currentTarget), { value: newSelectedItems }) });
|
|
297
|
-
onChange === null || onChange === void 0 ? void 0 : onChange(
|
|
321
|
+
onChange === null || onChange === void 0 ? void 0 : onChange(event, newSelectedItems);
|
|
298
322
|
return newSelectedItems;
|
|
299
323
|
});
|
|
300
324
|
return;
|
|
301
325
|
}
|
|
302
326
|
const newEvent = Object.assign(Object.assign({}, event), { currentTarget: Object.assign(Object.assign({}, event.currentTarget), { value: item }) });
|
|
303
|
-
|
|
327
|
+
const selectedId = getItemId(selectedItem);
|
|
328
|
+
const itemId = getItemId(item);
|
|
329
|
+
const isDifferent = selectedId !== null && itemId !== null
|
|
330
|
+
? selectedId !== itemId
|
|
331
|
+
: getComparisonValue(selectedItem, getOptionLabel) !== getComparisonValue(item, getOptionLabel);
|
|
332
|
+
if (isDifferent) {
|
|
304
333
|
setSelectedItem(item);
|
|
305
334
|
setIsOpen(false);
|
|
306
335
|
setSearchValue('');
|
|
307
336
|
onSearch === null || onSearch === void 0 ? void 0 : onSearch('');
|
|
308
|
-
onChange === null || onChange === void 0 ? void 0 : onChange(
|
|
337
|
+
onChange === null || onChange === void 0 ? void 0 : onChange(event, item);
|
|
309
338
|
onClose === null || onClose === void 0 ? void 0 : onClose(event);
|
|
310
339
|
}
|
|
311
340
|
if (item) {
|
|
@@ -332,6 +361,10 @@ export const Dropdown = ({ options, id, label, placeholder, required = false, va
|
|
|
332
361
|
};
|
|
333
362
|
//для выбора опции из списка с клавиатуры
|
|
334
363
|
const handleKeyDown = (event) => {
|
|
364
|
+
if (isOpen && (event.key === 'ArrowDown' || event.key === 'ArrowUp')) {
|
|
365
|
+
event.preventDefault();
|
|
366
|
+
event.stopPropagation();
|
|
367
|
+
}
|
|
335
368
|
if (!isOpen) {
|
|
336
369
|
if (event.key === 'Enter' || event.key === 'ArrowDown') {
|
|
337
370
|
event.preventDefault();
|
|
@@ -352,15 +385,26 @@ export const Dropdown = ({ options, id, label, placeholder, required = false, va
|
|
|
352
385
|
switch (event.key) {
|
|
353
386
|
case 'ArrowDown':
|
|
354
387
|
event.preventDefault();
|
|
355
|
-
displayOptions && setActiveIndex((prev) =>
|
|
388
|
+
displayOptions && setActiveIndex((prev) => {
|
|
389
|
+
const start = prev < 0 ? hoveredIndexRef.current : prev;
|
|
390
|
+
const hasLoadMore = showLoadMore && !!loadMore && !isSearchingNow && !showSpinner;
|
|
391
|
+
const max = displayOptions.length - 1 + (hasLoadMore ? 1 : 0);
|
|
392
|
+
return start <= max ? start + 1 : start;
|
|
393
|
+
});
|
|
356
394
|
break;
|
|
357
395
|
case 'ArrowUp':
|
|
358
396
|
event.preventDefault();
|
|
359
|
-
displayOptions && setActiveIndex((prev) =>
|
|
397
|
+
displayOptions && setActiveIndex((prev) => {
|
|
398
|
+
const start = prev < 0 ? hoveredIndexRef.current : prev;
|
|
399
|
+
return start > 0 ? start - 1 : start;
|
|
400
|
+
});
|
|
360
401
|
break;
|
|
361
402
|
case 'Enter':
|
|
362
403
|
event.preventDefault();
|
|
363
|
-
if (activeIndex
|
|
404
|
+
if (activeIndex === displayOptions.length && showLoadMore && loadMore) {
|
|
405
|
+
loadMore();
|
|
406
|
+
}
|
|
407
|
+
else if (activeIndex >= 0 && activeIndex < displayOptions.length) {
|
|
364
408
|
const selectedOption = displayOptions[activeIndex];
|
|
365
409
|
onChangeHandler(event, selectedOption);
|
|
366
410
|
setIsOpen(false);
|
|
@@ -397,7 +441,7 @@ export const Dropdown = ({ options, id, label, placeholder, required = false, va
|
|
|
397
441
|
}
|
|
398
442
|
setSearchValue('');
|
|
399
443
|
onSearch === null || onSearch === void 0 ? void 0 : onSearch('');
|
|
400
|
-
onChange === null || onChange === void 0 ? void 0 : onChange(event,
|
|
444
|
+
onChange === null || onChange === void 0 ? void 0 : onChange(event, []);
|
|
401
445
|
close && (onClose === null || onClose === void 0 ? void 0 : onClose(event));
|
|
402
446
|
setActiveIndex(-1);
|
|
403
447
|
if (required) {
|
|
@@ -409,9 +453,9 @@ export const Dropdown = ({ options, id, label, placeholder, required = false, va
|
|
|
409
453
|
event.preventDefault();
|
|
410
454
|
event.stopPropagation();
|
|
411
455
|
item && setSelectedItems((selectedItems) => {
|
|
412
|
-
const
|
|
413
|
-
const
|
|
414
|
-
onChange === null || onChange === void 0 ? void 0 : onChange(
|
|
456
|
+
const itemId = getItemId(item);
|
|
457
|
+
const newSelectedItems = selectedItems.filter((i) => itemId !== null ? getItemId(i) !== itemId : getComparisonValue(i, getOptionLabel) !== getComparisonValue(item, getOptionLabel));
|
|
458
|
+
onChange === null || onChange === void 0 ? void 0 : onChange(event, newSelectedItems);
|
|
415
459
|
if (required && newSelectedItems.length === 0) {
|
|
416
460
|
setErrorInput(true);
|
|
417
461
|
setErrorInputHelperText((helperText !== null && helperText !== void 0 ? helperText : lng === 'ru') ? 'Поле обязательно для заполнения' : 'Field is required');
|
|
@@ -436,9 +480,9 @@ export const Dropdown = ({ options, id, label, placeholder, required = false, va
|
|
|
436
480
|
const hidden = selectedItems.length - visible.length;
|
|
437
481
|
return (React.createElement("div", { className: styles.chipsWrap },
|
|
438
482
|
visible.map((opt) => {
|
|
439
|
-
var _a, _b;
|
|
440
|
-
const key = String((_a = getComparisonValue(opt, getOptionLabel)) !== null &&
|
|
441
|
-
const label = String((
|
|
483
|
+
var _a, _b, _c;
|
|
484
|
+
const key = String((_b = (_a = getItemId(opt)) !== null && _a !== void 0 ? _a : getComparisonValue(opt, getOptionLabel)) !== null && _b !== void 0 ? _b : getSelectedItemsText());
|
|
485
|
+
const label = String((_c = getComparisonValue(opt, getOptionLabel)) !== null && _c !== void 0 ? _c : '');
|
|
442
486
|
const chip = (React.createElement("span", { className: styles.chip, onMouseEnter: () => requestAnimationFrame(() => recalcChipTooltips()), onMouseDown: (e) => e.stopPropagation(), onClick: (e) => {
|
|
443
487
|
e.preventDefault();
|
|
444
488
|
e.stopPropagation();
|
|
@@ -489,18 +533,28 @@ export const Dropdown = ({ options, id, label, placeholder, required = false, va
|
|
|
489
533
|
e.stopPropagation();
|
|
490
534
|
onBlur === null || onBlur === void 0 ? void 0 : onBlur(e);
|
|
491
535
|
}, onKeyDown: handleKeyDown, autoFocus: true })),
|
|
492
|
-
!isOpen && (React.createElement(
|
|
493
|
-
|
|
494
|
-
multiple && selectedItems.length === 0 && !isOpen && (React.createElement("span", null, (_b = placeholder !== null && placeholder !== void 0 ? placeholder : label) !== null && _b !== void 0 ? _b : (lng === 'ru' ? 'Выберите значения' : 'Select values')))))));
|
|
536
|
+
!multiple && !selectedItem && !searchValue && !(isOpen && enableAutocomplete) && (React.createElement("span", null, (_a = placeholder !== null && placeholder !== void 0 ? placeholder : label) !== null && _a !== void 0 ? _a : (lng === 'ru' ? 'Выберите значение' : 'Select value'))),
|
|
537
|
+
multiple && selectedItems.length === 0 && !searchValue && !(isOpen && enableAutocomplete) && (React.createElement("span", null, (_b = placeholder !== null && placeholder !== void 0 ? placeholder : label) !== null && _b !== void 0 ? _b : (lng === 'ru' ? 'Выберите значения' : 'Select values')))));
|
|
495
538
|
return showSelectedTooltip ? (React.createElement("div", { className: styles.textField },
|
|
496
539
|
React.createElement(Tooltip, { label: ((_c = getComparisonValue(selectedItem, getOptionLabel)) === null || _c === void 0 ? void 0 : _c.toString()) || '', position: "bottom-left", style: { width: '100% !important' } }, textFieldContent))) : (textFieldContent);
|
|
497
540
|
};
|
|
541
|
+
const isSearchingNow = !isInitialOpen && !!searchValue.trim();
|
|
542
|
+
const showSpinner = isSearchLoading || (isOptionsLoading && displayOptions.length === 0);
|
|
498
543
|
const getDropdownMenu = () => {
|
|
499
|
-
//const optionsToRender = enableAutocomplete && searchValue ? filteredOptions : modifiedOptions;
|
|
500
544
|
const optionsToRender = displayOptions;
|
|
501
|
-
const
|
|
502
|
-
|
|
503
|
-
|
|
545
|
+
const menu = isOpen && (React.createElement("div", { className: dropdownClassess, ref: dropdownRef, onMouseMove: (e) => {
|
|
546
|
+
var _a;
|
|
547
|
+
const items = (_a = dropdownRef.current) === null || _a === void 0 ? void 0 : _a.querySelectorAll('[class*="item--container"]');
|
|
548
|
+
if (!items)
|
|
549
|
+
return;
|
|
550
|
+
const target = e.target.closest('[class*="item--container"]');
|
|
551
|
+
if (!target)
|
|
552
|
+
return;
|
|
553
|
+
const idx = Array.from(items).indexOf(target);
|
|
554
|
+
if (idx !== -1)
|
|
555
|
+
hoveredIndexRef.current = idx;
|
|
556
|
+
setActiveIndex(-1);
|
|
557
|
+
} },
|
|
504
558
|
showSpinner ? (React.createElement("div", { className: `${styles['item-block']}`, style: { padding: '10px', display: 'flex', flexDirection: "column", alignItems: 'center', justifyContent: 'center', margin: '0 auto' } },
|
|
505
559
|
React.createElement(Spinner, null))) : (React.createElement(React.Fragment, null, optionsToRender && optionsToRender.length > 0 ? (optionsToRender.map((option, index) => {
|
|
506
560
|
var _a;
|
|
@@ -508,15 +562,27 @@ export const Dropdown = ({ options, id, label, placeholder, required = false, va
|
|
|
508
562
|
})) : (React.createElement("div", { className: `${styles['item-block']}`, style: { margin: '15px auto', textAlign: 'center', color: 'var(--text-grey)' } }, lng === 'ru' || lng.includes('ru')
|
|
509
563
|
? noOptionsText || 'Нет вариантов для выбора'
|
|
510
564
|
: noOptionsText || 'No options to select')))),
|
|
511
|
-
!showSpinner && !isSearchingNow && showLoadMore && loadMore && (React.createElement(Button, { style: { width: '97%', margin: '10px auto', display: 'block', boxSizing: 'border-box' }, disabled:
|
|
565
|
+
!showSpinner && !isSearchingNow && showLoadMore && loadMore && (React.createElement(Button, { ref: loadMoreRef, style: { width: '97%', margin: '10px auto', display: 'block', boxSizing: 'border-box' }, disabled: isOptionsLoading, variant: 'outline', active: activeIndex === displayOptions.length, onClick: (e) => {
|
|
512
566
|
e.preventDefault();
|
|
513
567
|
e.stopPropagation();
|
|
514
568
|
loadMore();
|
|
515
|
-
} },
|
|
569
|
+
} }, isOptionsLoading
|
|
516
570
|
? (lng === 'ru' ? 'Загрузка...' : 'Loading...')
|
|
517
571
|
: (lng === 'ru' ? 'Загрузить еще' : 'Load more')))));
|
|
518
572
|
return isOpen ? menu : null;
|
|
519
573
|
};
|
|
574
|
+
const loadMoreRef = useRef(null);
|
|
575
|
+
useEffect(() => {
|
|
576
|
+
var _a, _b;
|
|
577
|
+
if (activeIndex < 0 || !dropdownRef.current)
|
|
578
|
+
return;
|
|
579
|
+
if (activeIndex === displayOptions.length) {
|
|
580
|
+
(_a = loadMoreRef.current) === null || _a === void 0 ? void 0 : _a.scrollIntoView({ block: 'nearest' });
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
const items = dropdownRef.current.querySelectorAll('[class*="item--container"]');
|
|
584
|
+
(_b = items[activeIndex]) === null || _b === void 0 ? void 0 : _b.scrollIntoView({ block: 'nearest' });
|
|
585
|
+
}, [activeIndex, displayOptions.length]);
|
|
520
586
|
useEffect(() => {
|
|
521
587
|
onCloseRef.current = onClose;
|
|
522
588
|
}, [onClose]);
|
|
@@ -594,6 +660,8 @@ export const Dropdown = ({ options, id, label, placeholder, required = false, va
|
|
|
594
660
|
tabIndex: disabled ? -1 : 0, "aria-disabled": disabled, onKeyDown: (e) => {
|
|
595
661
|
if (disabled)
|
|
596
662
|
return;
|
|
663
|
+
if (enableAutocomplete && e.target instanceof HTMLInputElement)
|
|
664
|
+
return;
|
|
597
665
|
handleKeyDown(e);
|
|
598
666
|
} },
|
|
599
667
|
getTextField(),
|
|
@@ -46,7 +46,8 @@
|
|
|
46
46
|
position: relative;
|
|
47
47
|
width: 100%;
|
|
48
48
|
}
|
|
49
|
-
.button:hover
|
|
49
|
+
.button:hover,
|
|
50
|
+
.button--outline-default--active {
|
|
50
51
|
border: 1px solid #0d9aff00;
|
|
51
52
|
box-shadow: 0px 0px 0px 0.75px var(--blue-main);
|
|
52
53
|
/* outline: none; */
|
|
@@ -194,9 +195,15 @@
|
|
|
194
195
|
|
|
195
196
|
}
|
|
196
197
|
|
|
197
|
-
.item--container:hover
|
|
198
|
+
.item--container:hover,
|
|
199
|
+
.item--container--active {
|
|
198
200
|
background-color: rgba(120, 120, 128, 0.08);
|
|
199
|
-
width: 97% !important;
|
|
201
|
+
width: 97% !important;
|
|
202
|
+
transition: background-color 0.15s ease;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
.dropdown:has(.item--container--active) .item--container:not(.item--container--active):hover {
|
|
206
|
+
background-color: transparent;
|
|
200
207
|
}
|
|
201
208
|
.item--container:has(._item-block--disabled):hover {
|
|
202
209
|
background-color: transparent;
|
|
@@ -431,7 +438,8 @@
|
|
|
431
438
|
cursor: pointer;
|
|
432
439
|
|
|
433
440
|
}
|
|
434
|
-
.loadMore:hover
|
|
441
|
+
.loadMore:hover,
|
|
442
|
+
.loadMore--active {
|
|
435
443
|
color: var(--blue-dark);
|
|
436
444
|
}
|
|
437
445
|
|
|
@@ -5,6 +5,100 @@ import { Typography } from '../Typography/Typography';
|
|
|
5
5
|
import { IconUpload } from '../../Icons';
|
|
6
6
|
import { FileItem } from '../FileItem/FileItem';
|
|
7
7
|
import classNames from 'classnames';
|
|
8
|
+
const getFileNameWithoutExtension = (filename) => {
|
|
9
|
+
const lastDotIndex = filename.lastIndexOf('.');
|
|
10
|
+
if (lastDotIndex === -1 || lastDotIndex === 0 || lastDotIndex === filename.length - 1) {
|
|
11
|
+
return filename;
|
|
12
|
+
}
|
|
13
|
+
return filename.substring(0, lastDotIndex);
|
|
14
|
+
};
|
|
15
|
+
const getFileExtension = (filename) => {
|
|
16
|
+
const lastDotIndex = filename.lastIndexOf('.');
|
|
17
|
+
if (lastDotIndex === -1 || lastDotIndex === filename.length - 1) {
|
|
18
|
+
return '';
|
|
19
|
+
}
|
|
20
|
+
return filename.substring(lastDotIndex).toLowerCase();
|
|
21
|
+
};
|
|
22
|
+
// Функция для получения всех доступных форматов в виде строки
|
|
23
|
+
const getAcceptedFormatsString = (acceptedFormats) => {
|
|
24
|
+
const uniqueFormats = new Set();
|
|
25
|
+
for (const key in acceptedFormats) {
|
|
26
|
+
if (acceptedFormats.hasOwnProperty(key)) {
|
|
27
|
+
acceptedFormats[key].forEach((format) => {
|
|
28
|
+
uniqueFormats.add(format.replace('.', ''));
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return Array.from(uniqueFormats).join(', ');
|
|
33
|
+
};
|
|
34
|
+
const fileValidatorInner = (file, maxFileSize, maxFileCount, maxFileName, addedFilesLength, lng, acceptedFormats, rejectedFormats, fileValidator) => {
|
|
35
|
+
const fileExtension = getFileExtension(file.name);
|
|
36
|
+
const fileNameWithoutExt = getFileNameWithoutExtension(file.name);
|
|
37
|
+
const nameLength = Array.from(fileNameWithoutExt).length;
|
|
38
|
+
const fileParts = file.name.split('.');
|
|
39
|
+
const fileExt = fileParts.length > 1 ? `.${fileParts.pop().toLowerCase()}` : '';
|
|
40
|
+
const checks = {
|
|
41
|
+
isSizeTooLarge: file.size > maxFileSize * 1024 * 1024 * 1024,
|
|
42
|
+
isTooManyFiles: addedFilesLength > maxFileCount - 1,
|
|
43
|
+
isNameTooLarge: typeof maxFileName === 'number' && maxFileName > 0 && nameLength > maxFileName,
|
|
44
|
+
isAcceptedFormatValid: true,
|
|
45
|
+
isRejectedFormatValid: true
|
|
46
|
+
};
|
|
47
|
+
// Проверка форматов
|
|
48
|
+
if (acceptedFormats && !rejectedFormats) {
|
|
49
|
+
const acceptedExtensions = Object.values(acceptedFormats).reduce((acc, val) => acc.concat(val), []);
|
|
50
|
+
checks.isAcceptedFormatValid = acceptedExtensions.includes(fileExtension);
|
|
51
|
+
}
|
|
52
|
+
if (rejectedFormats) {
|
|
53
|
+
const rejectedExtensions = Object.values(rejectedFormats).reduce((acc, val) => acc.concat(val), []);
|
|
54
|
+
checks.isRejectedFormatValid = !rejectedExtensions.includes(fileExt);
|
|
55
|
+
}
|
|
56
|
+
switch (true) {
|
|
57
|
+
case checks.isSizeTooLarge:
|
|
58
|
+
return {
|
|
59
|
+
code: 'size-too-large',
|
|
60
|
+
message: lng === 'ru' || lng.includes('ru')
|
|
61
|
+
? `Максимальный размер файла ${maxFileSize.toFixed(0)} ГБ`
|
|
62
|
+
: `Maximum file size ${maxFileSize.toFixed(0)} GB`,
|
|
63
|
+
};
|
|
64
|
+
case checks.isTooManyFiles:
|
|
65
|
+
return {
|
|
66
|
+
code: 'files-count-too-large',
|
|
67
|
+
message: lng === 'ru' || lng.includes('ru')
|
|
68
|
+
? `Максимальное количество файлов ${maxFileCount}`
|
|
69
|
+
: `Maximum number of files ${maxFileCount}`,
|
|
70
|
+
};
|
|
71
|
+
case checks.isNameTooLarge:
|
|
72
|
+
return {
|
|
73
|
+
code: 'name-too-large',
|
|
74
|
+
message: lng === 'ru' || lng.includes('ru')
|
|
75
|
+
? `Имя файла не может превышать ${maxFileName} символов.`
|
|
76
|
+
: `File name must be under ${maxFileName} symbols.`,
|
|
77
|
+
};
|
|
78
|
+
case !checks.isAcceptedFormatValid:
|
|
79
|
+
return {
|
|
80
|
+
code: 'file-invalid-type',
|
|
81
|
+
message: lng === 'ru' || lng.includes('ru')
|
|
82
|
+
? `Файл должен быть одного из следующих типов: ${Object.values(acceptedFormats).reduce((acc, val) => acc.concat(val), []).join(', ')}`
|
|
83
|
+
: `File must be one of: ${Object.values(acceptedFormats).reduce((acc, val) => acc.concat(val), []).join(', ')}`,
|
|
84
|
+
};
|
|
85
|
+
case !checks.isRejectedFormatValid:
|
|
86
|
+
return {
|
|
87
|
+
code: 'file-invalid-type',
|
|
88
|
+
message: lng === 'ru' || lng.includes('ru')
|
|
89
|
+
? `Файл не должен быть одного из следующих типов: ${getAcceptedFormatsString(rejectedFormats)}`
|
|
90
|
+
: `File must not be one of: ${getAcceptedFormatsString(rejectedFormats)}`,
|
|
91
|
+
};
|
|
92
|
+
default: {
|
|
93
|
+
if (fileValidator) {
|
|
94
|
+
const customValidationResult = fileValidator(file);
|
|
95
|
+
if (customValidationResult)
|
|
96
|
+
return customValidationResult;
|
|
97
|
+
}
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
};
|
|
8
102
|
export const FileLoader = forwardRef(({ maxFileSize = 2, maxFileCount = 10, maxFileName = 0, acceptedFormats = {
|
|
9
103
|
'image/*': ['.png', '.gif', '.jpeg', '.jpg'],
|
|
10
104
|
'application/pdf': ['.pdf'],
|
|
@@ -25,92 +119,15 @@ export const FileLoader = forwardRef(({ maxFileSize = 2, maxFileCount = 10, maxF
|
|
|
25
119
|
setLoadingFilesNames([]);
|
|
26
120
|
}
|
|
27
121
|
}));
|
|
28
|
-
const fileValidatorInner = (file) => {
|
|
29
|
-
if (file.size > maxFileSize * 1024 * 1024 * 1024) {
|
|
30
|
-
return {
|
|
31
|
-
code: 'size-too-large',
|
|
32
|
-
message: lng === 'ru' || lng.includes('ru')
|
|
33
|
-
? `Максимальный размер файла ${maxFileSize.toFixed(0)} ГБ`
|
|
34
|
-
: `Maximum file size ${maxFileSize.toFixed(0)} GB`,
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
// Проверка на дубликаты в filesList
|
|
38
|
-
// if (filesList.find((existingFile: TAttachments) => existingFile.filename === file.name)) {
|
|
39
|
-
// return {
|
|
40
|
-
// code: 'repeating-file-name',
|
|
41
|
-
// message: lng === 'ru' || lng.includes('ru') ? `Файл уже существует в списке прикрепленных файлов` : `File already exists in the list of attached files`,
|
|
42
|
-
// };
|
|
43
|
-
// }
|
|
44
|
-
// Проверка на дубликаты в addedFiles
|
|
45
|
-
// if (addedFiles.find((addedFile: File) => addedFile.name === file.name)) {
|
|
46
|
-
// return {
|
|
47
|
-
// code: 'repeating-file-name',
|
|
48
|
-
// message: lng === 'ru' || lng.includes('ru') ? `Файл уже добавлен` : `File already added`,
|
|
49
|
-
// };
|
|
50
|
-
// }
|
|
51
|
-
if (addedFiles.length > maxFileCount - 1) {
|
|
52
|
-
return {
|
|
53
|
-
code: 'files-count-too-large',
|
|
54
|
-
message: lng === 'ru' || lng.includes('ru') ? `Максимальное количество файлов ${maxFileCount}` : `Maximum number of files ${maxFileCount}`,
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
if (maxFileName && file.name.length > maxFileName) {
|
|
58
|
-
return {
|
|
59
|
-
code: 'name-too-large',
|
|
60
|
-
message: lng === 'ru' || lng.includes('ru') ? `Имя файла не может превышать ${maxFileName} символов` : `File name must be under ${maxFileName} symbols`,
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
if (acceptedFormats && !rejectedFormats) {
|
|
64
|
-
const acceptedExtensions = Object.values(acceptedFormats)
|
|
65
|
-
.reduce((acc, val) => acc.concat(val), []);
|
|
66
|
-
const fileParts = file.name.split('.');
|
|
67
|
-
const fileExtension = fileParts.length > 1
|
|
68
|
-
? `.${fileParts.pop().toLowerCase()}`
|
|
69
|
-
: '';
|
|
70
|
-
if (!acceptedExtensions.includes(fileExtension)) {
|
|
71
|
-
return {
|
|
72
|
-
code: 'file-invalid-type',
|
|
73
|
-
message: lng === 'ru' || lng.includes('ru')
|
|
74
|
-
? `Файл должен быть одного из следующих типов: ${acceptedExtensions.join(', ')}`
|
|
75
|
-
: `File must be one of: ${acceptedExtensions.join(', ')}`,
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
if (rejectedFormats) {
|
|
80
|
-
const rejectedExtensions = Object.values(rejectedFormats)
|
|
81
|
-
.reduce((acc, val) => acc.concat(val), []);
|
|
82
|
-
const fileParts = file.name.split('.');
|
|
83
|
-
const fileExtension = fileParts.length > 1
|
|
84
|
-
? `.${fileParts.pop().toLowerCase()}`
|
|
85
|
-
: '';
|
|
86
|
-
if (rejectedExtensions.includes(fileExtension)) {
|
|
87
|
-
return {
|
|
88
|
-
code: 'file-invalid-type',
|
|
89
|
-
message: lng === 'ru' || lng.includes('ru')
|
|
90
|
-
? `Файл не должен быть одного из следующих типов: ${getAcceptedFormatsString(rejectedFormats)}`
|
|
91
|
-
: `File must not be one of: ${getAcceptedFormatsString(rejectedFormats)}`,
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
if (fileValidator) {
|
|
96
|
-
const customValidationResult = fileValidator(file);
|
|
97
|
-
if (customValidationResult) {
|
|
98
|
-
return customValidationResult;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
return null;
|
|
102
|
-
};
|
|
103
122
|
const { getRootProps, getInputProps } = useDropzone({
|
|
104
123
|
onDrop: (acceptedFiles, fileRejections) => {
|
|
105
124
|
setAddedFiles([...addedFiles, ...acceptedFiles]);
|
|
106
|
-
const newFormatAttachments = acceptedFiles.map((file) => {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
};
|
|
113
|
-
});
|
|
125
|
+
const newFormatAttachments = acceptedFiles.map((file) => ({
|
|
126
|
+
id: `file-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`,
|
|
127
|
+
filename: file.name,
|
|
128
|
+
size: file.size,
|
|
129
|
+
type: file.type,
|
|
130
|
+
}));
|
|
114
131
|
setLoadingFilesNames(newFormatAttachments.map((file) => { var _a; return (_a = file === null || file === void 0 ? void 0 : file.filename) !== null && _a !== void 0 ? _a : 'Без названия'; }));
|
|
115
132
|
setIsLoadingFiles(true);
|
|
116
133
|
setAddedFilesFormatted([...addedFilesFormated, ...newFormatAttachments]);
|
|
@@ -160,7 +177,7 @@ export const FileLoader = forwardRef(({ maxFileSize = 2, maxFileCount = 10, maxF
|
|
|
160
177
|
setErrorFiles([...errorFiles, ...formattedRejections]);
|
|
161
178
|
}
|
|
162
179
|
},
|
|
163
|
-
validator: fileValidatorInner,
|
|
180
|
+
validator: (file) => fileValidatorInner(file, maxFileSize, maxFileCount, maxFileName, addedFiles.length, lng, acceptedFormats, rejectedFormats, fileValidator),
|
|
164
181
|
accept: undefined,
|
|
165
182
|
maxFiles: maxFileCount,
|
|
166
183
|
disabled: !canAdd,
|
|
@@ -176,27 +193,11 @@ export const FileLoader = forwardRef(({ maxFileSize = 2, maxFileCount = 10, maxF
|
|
|
176
193
|
setLoadingFilesNames(loadingFilesNames.filter((name) => name !== fileToDelete.filename));
|
|
177
194
|
}
|
|
178
195
|
};
|
|
179
|
-
const acceptedFileItems = addedFilesFormated.map((file) => {
|
|
180
|
-
return (React.createElement(FileItem, { key: file.id, file: file,
|
|
181
|
-
//loading={loadingFilesNames.includes(file.filename)} // Показываем лоадер только для новых файлов
|
|
182
|
-
onDelete: handleDeleteFiles, isAddedFile: true, lng: lng }));
|
|
183
|
-
});
|
|
184
196
|
const handleDeleteRejectedFile = (id) => {
|
|
185
197
|
setErrorFiles(errorFiles.filter((rejection) => rejection.file.id !== id));
|
|
186
198
|
};
|
|
199
|
+
const acceptedFileItems = addedFilesFormated.map((file) => (React.createElement(FileItem, { key: file.id, file: file, onDelete: handleDeleteFiles, isAddedFile: true, lng: lng })));
|
|
187
200
|
const fileRejectionItems = errorFiles.map(({ file, errors }) => (React.createElement(FileItem, { key: file.id, file: file, error: errors[0].message, onDelete: handleDeleteRejectedFile, isRejectedFile: true, lng: lng })));
|
|
188
|
-
// Функция для получения всех доступных форматов в виде строки
|
|
189
|
-
const getAcceptedFormatsString = (acceptedFormats) => {
|
|
190
|
-
const uniqueFormats = new Set();
|
|
191
|
-
for (const key in acceptedFormats) {
|
|
192
|
-
if (acceptedFormats.hasOwnProperty(key)) {
|
|
193
|
-
acceptedFormats[key].forEach((format) => {
|
|
194
|
-
uniqueFormats.add(format.replace('.', ''));
|
|
195
|
-
});
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
return Array.from(uniqueFormats).join(', ');
|
|
199
|
-
};
|
|
200
201
|
useEffect(() => {
|
|
201
202
|
if (addedFiles.length === 0) {
|
|
202
203
|
setAddedFilesFormatted([]);
|
|
@@ -213,11 +214,9 @@ export const FileLoader = forwardRef(({ maxFileSize = 2, maxFileCount = 10, maxF
|
|
|
213
214
|
React.createElement(IconUpload, { htmlColor: !canAdd ? 'var(--grey-medium)' : 'var(--icons-grey)', width: '34', height: '34' }),
|
|
214
215
|
React.createElement(Typography, { variant: "Body1", color: !canAdd ? 'var(--grey-medium)' : 'var(--icons-grey)', style: { textAlign: 'center' } }, lng === 'ru' || lng.includes('ru') ? (React.createElement(React.Fragment, null,
|
|
215
216
|
React.createElement("span", { style: { textDecoration: 'underline' } }, "\u041D\u0430\u0436\u043C\u0438\u0442\u0435 \u043D\u0430 \u043E\u0431\u043B\u0430\u0441\u0442\u044C"),
|
|
216
|
-
" ",
|
|
217
217
|
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,
|
|
218
|
-
React.createElement("span", { style: { textDecoration: 'underline' } }, "
|
|
219
|
-
" ",
|
|
220
|
-
React.createElement("span", null, "or drag files here")))),
|
|
218
|
+
React.createElement("span", { style: { textDecoration: 'underline' } }, "Click on this area"),
|
|
219
|
+
React.createElement("span", null, " or drag files here")))),
|
|
221
220
|
React.createElement("div", null,
|
|
222
221
|
maxFileSize &&
|
|
223
222
|
(lng === 'ru' || lng.includes('ru') ? (React.createElement(Typography, { variant: "Body2", color: "var(--grey-medium)" },
|
|
@@ -233,5 +232,5 @@ export const FileLoader = forwardRef(({ maxFileSize = 2, maxFileCount = 10, maxF
|
|
|
233
232
|
rejectedFormats && (React.createElement(Typography, { variant: "Body2", color: "var(--grey-medium)" }, `${lng === 'ru' || lng.includes('ru') ? 'Неподдерживаемые форматы:' : 'Unsupported formats:'} ${getAcceptedFormatsString(rejectedFormats)}`)),
|
|
234
233
|
(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'] },
|
|
235
234
|
acceptedFileItems,
|
|
236
|
-
fileRejectionItems)) :
|
|
235
|
+
fileRejectionItems)) : (React.createElement(Typography, { variant: "Body2-SemiBold", color: "var(--grey-medium)", style: { marginTop: '5px' } }, lng === 'ru' || lng.includes('ru') ? 'Файлы не добавлены' : 'Files not added'))));
|
|
237
236
|
});
|