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.
@@ -3,4 +3,4 @@ import { ButtonProps } from '../../types';
3
3
  /**
4
4
  * Компонент Button представляет собой кнопку, которую можно настроить с помощью различных параметров (размер, иконки, стили, состояние).
5
5
  */
6
- export declare const Button: React.FC<ButtonProps>;
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, isLoadMoreLoading, isSearchLoading, noOptionsText, lng, multiple, limitTags, }: DropdownProps<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, 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
- : getComparisonValue(selectedItem, getOptionLabel) === getComparisonValue(item, getOptionLabel);
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, isLoadMoreLoading, isSearchLoading, noOptionsText = 'Нет вариантов для выбора', lng = 'ru', multiple = false, limitTags = 1, }) => {
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 isSelected = selectedItems.some((i) => getComparisonValue(i, getOptionLabel) === getComparisonValue(item, getOptionLabel));
293
- const newSelectedItems = isSelected ?
294
- selectedItems.filter((i) => getComparisonValue(i, getOptionLabel) !== getComparisonValue(item, getOptionLabel)) :
295
- [...selectedItems, item];
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(newEvent, newSelectedItems);
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
- if (getComparisonValue(selectedItem, getOptionLabel) !== getComparisonValue(item, getOptionLabel)) {
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(newEvent, item);
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) => (prev < displayOptions.length - 1 ? prev + 1 : 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) => (prev > 0 ? prev - 1 : 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 >= 0 && activeIndex < displayOptions.length) {
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, multiple ? [] : startValue !== null && startValue !== void 0 ? startValue : null);
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 newSelectedItems = selectedItems.filter((i) => getComparisonValue(i, getOptionLabel) !== getComparisonValue(item, getOptionLabel));
413
- const newEvent = Object.assign(Object.assign({}, event), { currentTarget: Object.assign(Object.assign({}, event.currentTarget), { value: newSelectedItems }) });
414
- onChange === null || onChange === void 0 ? void 0 : onChange(newEvent, newSelectedItems);
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 && _a !== void 0 ? _a : getSelectedItemsText());
441
- const label = String((_b = getComparisonValue(opt, getOptionLabel)) !== null && _b !== void 0 ? _b : '');
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(React.Fragment, null,
493
- !multiple && !selectedItem && !searchValue && !isOpen && (React.createElement("span", null, (_a = placeholder !== null && placeholder !== void 0 ? placeholder : label) !== null && _a !== void 0 ? _a : (lng === 'ru' ? 'Выберите значение' : 'Select value'))),
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 isSearchingNow = !isInitialOpen && !!searchValue.trim();
502
- const showSpinner = isSearchLoading || (isLoadMoreLoading && optionsToRender.length === 0);
503
- const menu = isOpen && (React.createElement("div", { className: dropdownClassess },
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: isLoadMoreLoading, variant: 'outline', onClick: (e) => {
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
- } }, isLoadMoreLoading
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
- return {
108
- id: `file-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`,
109
- filename: file.name,
110
- size: file.size,
111
- type: file.type,
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' } }, "\u0421lick on this area"),
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)) : lng === 'ru' || lng.includes('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"))));
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
  });