kamotive_ui 15.1.26 → 21.1.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.
@@ -7,7 +7,7 @@ export interface DropdownListItemProps {
7
7
  item: TOptions | null;
8
8
  getOptionLabel?: (option: TOptions) => string;
9
9
  size: 'md' | 'lg';
10
- selectedItem: TOptions | null;
10
+ selectedItem: TOptions | null | TOptions[];
11
11
  variant?: 'icons' | 'text';
12
12
  onChange: (event: React.MouseEvent<HTMLElement>, item: TOptions | null) => void;
13
13
  isActive?: boolean;
@@ -134,6 +134,9 @@ export const DropdownListItem = ({ item, getOptionLabel, size = 'md', selectedIt
134
134
  }
135
135
  }, [item, onChange]);
136
136
  const hasChildren = (item === null || item === void 0 ? void 0 : item.children) && item.children.length > 0;
137
+ const isSelectedItem = Array.isArray(selectedItem)
138
+ ? selectedItem.some((i) => getComparisonValue(i, getOptionLabel) === getComparisonValue(item, getOptionLabel))
139
+ : getComparisonValue(selectedItem, getOptionLabel) === getComparisonValue(item, getOptionLabel);
137
140
  const itemContainerClasses = classNames(styles[`item--container`], {
138
141
  [styles['item--container--active']]: isActive,
139
142
  [styles['item--container--parent']]: hasChildren && !isChild,
@@ -148,7 +151,7 @@ export const DropdownListItem = ({ item, getOptionLabel, size = 'md', selectedIt
148
151
  const itemBlock = classNames(styles[`item-block`], styles[`item-block-${variant}`],
149
152
  // { [styles[`item-block-${variant}--selected`]]: selectedItem?.value === item?.value },
150
153
  {
151
- [styles[`item-block-${variant}--selected`]]: getComparisonValue(selectedItem, getOptionLabel) === getComparisonValue(item, getOptionLabel),
154
+ [styles[`item-block-${variant}--selected`]]: isSelectedItem,
152
155
  [styles['item-block--disabled']]: item === null || item === void 0 ? void 0 : item.disabled,
153
156
  [styles['item-block--parent']]: hasChildren && !isChild,
154
157
  [styles['item-block--child']]: isChild,
@@ -163,8 +166,7 @@ export const DropdownListItem = ({ item, getOptionLabel, size = 'md', selectedIt
163
166
  }),
164
167
  React.createElement("div", { className: styles.item, ref: itemRef },
165
168
  React.createElement("span", null, getComparisonValue(item, getOptionLabel))),
166
- !hasChildren &&
167
- getComparisonValue(selectedItem, getOptionLabel) === getComparisonValue(item, getOptionLabel) && (React.createElement(IconCheck, { strokeWidth: size === 'lg' ? '0.5' : size === 'md' ? '0.3' : '0.0', htmlColor: "#0D99FF" }))),
169
+ !hasChildren && isSelectedItem && (React.createElement(IconCheck, { strokeWidth: size === 'lg' ? '0.5' : size === 'md' ? '0.3' : '0.0', htmlColor: "#0D99FF" }))),
168
170
  (item === null || item === void 0 ? void 0 : item.isDivider) && React.createElement("div", { className: styles.divider })),
169
171
  hasChildren && (React.createElement("div", { className: styles.nestedMenu }, (_a = item.children) === null || _a === void 0 ? void 0 : _a.map((child, childIndex) => {
170
172
  var _a;
@@ -172,10 +174,11 @@ export const DropdownListItem = ({ item, getOptionLabel, size = 'md', selectedIt
172
174
  })))));
173
175
  return showTooltip ? (React.createElement(Tooltip, { label: ((_b = getComparisonValue(item, getOptionLabel)) === null || _b === void 0 ? void 0 : _b.toString()) || '', position: "bottom-left" }, itemContent)) : (itemContent);
174
176
  };
175
- 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, onClick, onBlur, onFocus, onClose, clearable = true, enableAutocomplete = false, noOptionsText = 'Нет вариантов для выбора', lng = 'ru', }) => {
177
+ 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, onClick, onBlur, onFocus, onClose, clearable = true, enableAutocomplete = false, noOptionsText = 'Нет вариантов для выбора', lng = 'ru', multiple = false, limitTags = 1, }) => {
176
178
  const [isOpen, setIsOpen] = useState(isOpened);
177
179
  const [modifiedOptions, setModifiedOptions] = useState([]);
178
180
  const [selectedItem, setSelectedItem] = useState(null);
181
+ const [selectedItems, setSelectedItems] = useState([]); //при множественном выборе
179
182
  const [errorInput, setErrorInput] = useState(false);
180
183
  const [errorInputHelperText, setErrorInputHelperText] = useState(helperText);
181
184
  const [activeIndex, setActiveIndex] = useState(-1);
@@ -205,9 +208,9 @@ export const Dropdown = ({ options, id, label, placeholder, required = false, va
205
208
  [styles['label--required']]: required,
206
209
  });
207
210
  const selectedItemClassess = classNames({
208
- [styles['item-selected']]: selectedItem,
209
- [styles['item-placeholder']]: !selectedItem && ((placeholder !== null && placeholder !== void 0 ? placeholder : label) || (!placeholder && !label)),
210
- [styles['button--icons--item-selected']]: variant === 'icons' && (selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.icon),
211
+ [styles['item-selected']]: selectedItem || selectedItems.length,
212
+ [styles['item-placeholder']]: !(selectedItem || selectedItems.length) && ((placeholder !== null && placeholder !== void 0 ? placeholder : label) || (!placeholder && !label)),
213
+ [styles['button--icons--item-selected']]: variant === 'icons' && (selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.icon) && !multiple,
211
214
  });
212
215
  // обновляет значения searchValue и filteredOptions
213
216
  const setAutocompleteValues = (value) => {
@@ -248,6 +251,19 @@ export const Dropdown = ({ options, id, label, placeholder, required = false, va
248
251
  const onChangeHandler = (event, item) => {
249
252
  event.preventDefault();
250
253
  event.stopPropagation();
254
+ if (multiple && item) {
255
+ setErrorInput(false);
256
+ setSelectedItems((selectedItems) => {
257
+ const isSelected = selectedItems.some((i) => getComparisonValue(i, getOptionLabel) === getComparisonValue(item, getOptionLabel));
258
+ const newSelectedItems = isSelected ?
259
+ selectedItems.filter((i) => getComparisonValue(i, getOptionLabel) !== getComparisonValue(item, getOptionLabel)) :
260
+ [...selectedItems, item];
261
+ const newEvent = Object.assign(Object.assign({}, event), { currentTarget: Object.assign(Object.assign({}, event.currentTarget), { value: newSelectedItems }) });
262
+ onChange === null || onChange === void 0 ? void 0 : onChange(newEvent, newSelectedItems);
263
+ return newSelectedItems;
264
+ });
265
+ return;
266
+ }
251
267
  const newEvent = Object.assign(Object.assign({}, event), { currentTarget: Object.assign(Object.assign({}, event.currentTarget), { value: item }) });
252
268
  if (getComparisonValue(selectedItem, getOptionLabel) !== getComparisonValue(item, getOptionLabel)) {
253
269
  setSelectedItem(item);
@@ -313,18 +329,23 @@ export const Dropdown = ({ options, id, label, placeholder, required = false, va
313
329
  break;
314
330
  }
315
331
  };
316
- //для сброса выбранного значения
332
+ //для сброса выбранного значения или всех (если multiple)
317
333
  const handleReset = (event) => {
318
334
  event.preventDefault();
319
335
  event.stopPropagation();
320
336
  const startValue = defaultValue ? checkItem(defaultValue) : null;
321
- setSelectedItem(startValue !== null && startValue !== void 0 ? startValue : null);
337
+ if (multiple) {
338
+ setSelectedItems([]);
339
+ }
340
+ else {
341
+ setSelectedItem(startValue !== null && startValue !== void 0 ? startValue : null);
342
+ }
322
343
  if (!enableAutocomplete) {
323
344
  setIsOpen(false);
324
345
  }
325
346
  setSearchValue('');
326
347
  setFilteredOptions(modifiedOptions);
327
- onChange === null || onChange === void 0 ? void 0 : onChange(event, startValue !== null && startValue !== void 0 ? startValue : null);
348
+ onChange === null || onChange === void 0 ? void 0 : onChange(event, multiple ? [] : startValue !== null && startValue !== void 0 ? startValue : null);
328
349
  onClose === null || onClose === void 0 ? void 0 : onClose(event);
329
350
  setActiveIndex(-1);
330
351
  if (required) {
@@ -332,8 +353,24 @@ export const Dropdown = ({ options, id, label, placeholder, required = false, va
332
353
  setErrorInputHelperText((helperText !== null && helperText !== void 0 ? helperText : lng === 'ru') ? 'Поле обязательно для заполнения' : 'Field is required');
333
354
  }
334
355
  };
356
+ const handleResetMultipleItem = (event, item) => {
357
+ event.preventDefault();
358
+ event.stopPropagation();
359
+ item && setSelectedItems((selectedItems) => {
360
+ const newSelectedItems = selectedItems.filter((i) => getComparisonValue(i, getOptionLabel) !== getComparisonValue(item, getOptionLabel));
361
+ const newEvent = Object.assign(Object.assign({}, event), { currentTarget: Object.assign(Object.assign({}, event.currentTarget), { value: newSelectedItems }) });
362
+ onChange === null || onChange === void 0 ? void 0 : onChange(newEvent, newSelectedItems);
363
+ if (required && newSelectedItems.length === 0) {
364
+ setErrorInput(true);
365
+ setErrorInputHelperText((helperText !== null && helperText !== void 0 ? helperText : lng === 'ru') ? 'Поле обязательно для заполнения' : 'Field is required');
366
+ }
367
+ return newSelectedItems;
368
+ });
369
+ };
335
370
  const [showSelectedTooltip, setShowSelectedTooltip] = useState(false);
336
371
  const selectedItemRef = useRef(null);
372
+ const [showChipTooltip, setShowChipTooltip] = useState({});
373
+ const labelChipRef = useRef(new Map());
337
374
  useEffect(() => {
338
375
  const checkOverflow = () => {
339
376
  setShowSelectedTooltip(isTextOverflowing(selectedItemRef.current));
@@ -344,16 +381,71 @@ export const Dropdown = ({ options, id, label, placeholder, required = false, va
344
381
  window.removeEventListener('resize', checkOverflow);
345
382
  };
346
383
  }, [getComparisonValue(selectedItem, getOptionLabel)]);
384
+ const recalcChipTooltips = useCallback(() => {
385
+ const next = {};
386
+ labelChipRef.current.forEach((el, key) => {
387
+ next[key] = !!el && isTextOverflowing(el);
388
+ });
389
+ setShowChipTooltip(next);
390
+ }, []);
391
+ useEffect(() => {
392
+ if (!multiple)
393
+ return;
394
+ requestAnimationFrame(() => recalcChipTooltips());
395
+ window.addEventListener('resize', recalcChipTooltips);
396
+ return () => window.removeEventListener('resize', recalcChipTooltips);
397
+ }, [multiple, selectedItems, limitTags, recalcChipTooltips]);
398
+ const getSelectedItemsText = () => {
399
+ if (multiple) {
400
+ if (selectedItems.length === 0) {
401
+ return '';
402
+ }
403
+ return selectedItems.map((item) => {
404
+ getComparisonValue(item, getOptionLabel);
405
+ });
406
+ }
407
+ return getComparisonValue(selectedItem, getOptionLabel);
408
+ };
409
+ const getChips = () => {
410
+ const visible = selectedItems.slice(0, limitTags);
411
+ const hidden = selectedItems.length - visible.length;
412
+ return (React.createElement("div", { className: styles.chipsWrap },
413
+ visible.map((opt) => {
414
+ var _a, _b;
415
+ const key = String((_a = getComparisonValue(opt, getOptionLabel)) !== null && _a !== void 0 ? _a : getSelectedItemsText());
416
+ const label = String((_b = getComparisonValue(opt, getOptionLabel)) !== null && _b !== void 0 ? _b : '');
417
+ const chip = (React.createElement("span", { className: styles.chip, onMouseEnter: () => requestAnimationFrame(() => recalcChipTooltips()), onMouseDown: (e) => e.stopPropagation(), onClick: (e) => {
418
+ e.preventDefault();
419
+ e.stopPropagation();
420
+ } },
421
+ React.createElement("span", { className: styles.chipLabel, ref: (el) => {
422
+ labelChipRef.current.set(key, el);
423
+ } }, label),
424
+ React.createElement("span", { className: styles.chipRemove, role: "button", tabIndex: 0, onMouseDown: (e) => e.stopPropagation(), onClick: (e) => handleResetMultipleItem(e, opt), onKeyDown: (e) => {
425
+ if (e.key === 'Enter' || e.key === ' ')
426
+ handleResetMultipleItem(e, opt);
427
+ }, "aria-label": lng === 'ru' ? 'Удалить' : 'Remove' }, "\u00D7")));
428
+ return showChipTooltip[key] ? (React.createElement(Tooltip, { label: label, position: "bottom-left", style: { width: '100% !important' }, key: key }, chip)) : (chip);
429
+ }),
430
+ hidden > 0 && (React.createElement("span", { className: styles.chipMore, onMouseDown: (e) => e.stopPropagation(), onClick: (e) => {
431
+ e.preventDefault();
432
+ e.stopPropagation();
433
+ } },
434
+ "+",
435
+ hidden))));
436
+ };
347
437
  const getTextField = () => {
348
- var _a, _b;
438
+ var _a, _b, _c;
439
+ const selectedText = getSelectedItemsText();
349
440
  const textFieldContent = (React.createElement("div", { className: selectedItemClassess, ref: selectedItemRef },
350
441
  variant === 'icons' &&
442
+ !multiple &&
351
443
  (selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.icon) &&
352
444
  React.cloneElement(selectedItem.icon, {
353
445
  strokeWidth: size === 'lg' ? '0.5' : size === 'md' ? '0.3' : '0.0',
354
446
  }),
355
- isOpen && enableAutocomplete ? (React.createElement("input", { ref: inputRef, type: "text", value: searchValue, className: styles.inlineSearchInput, onChange: handleSearchChange, placeholder: getComparisonValue(selectedItem, getOptionLabel)
356
- ? getComparisonValue(selectedItem, getOptionLabel).toString()
447
+ isOpen && enableAutocomplete ? (React.createElement("input", { ref: inputRef, type: "text", value: searchValue, className: styles.inlineSearchInput, onChange: handleSearchChange, placeholder: selectedText
448
+ ? selectedText
357
449
  : lng === 'ru' ? 'Поиск...' : 'Search...', onClick: (e) => {
358
450
  e.stopPropagation();
359
451
  e.preventDefault();
@@ -367,16 +459,16 @@ export const Dropdown = ({ options, id, label, placeholder, required = false, va
367
459
  }, onBlur: (e) => {
368
460
  e.stopPropagation();
369
461
  onBlur === null || onBlur === void 0 ? void 0 : onBlur(e);
370
- }, onKeyDown: handleKeyDown, autoFocus: true })) : selectedItem ? (getComparisonValue(selectedItem, getOptionLabel)) : (searchValue || ((_a = placeholder !== null && placeholder !== void 0 ? placeholder : label) !== null && _a !== void 0 ? _a : (lng === 'ru' ? 'Выберите значение' : 'Select value')))));
462
+ }, onKeyDown: handleKeyDown, autoFocus: true })) : multiple ? (selectedItems.length > 0 ? (getChips()) : (searchValue || ((_a = placeholder !== null && placeholder !== void 0 ? placeholder : label) !== null && _a !== void 0 ? _a : (lng === 'ru' ? 'Выберите значения' : 'Select values')))) : selectedItem ? (getComparisonValue(selectedItem, getOptionLabel)) : (searchValue || ((_b = placeholder !== null && placeholder !== void 0 ? placeholder : label) !== null && _b !== void 0 ? _b : (lng === 'ru' ? 'Выберите значение' : 'Select value')))));
371
463
  return showSelectedTooltip ? (React.createElement("div", { className: styles.textField },
372
- React.createElement(Tooltip, { label: ((_b = getComparisonValue(selectedItem, getOptionLabel)) === null || _b === void 0 ? void 0 : _b.toString()) || '', position: "bottom-left", style: { width: '100% !important' } }, textFieldContent))) : (textFieldContent);
464
+ 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);
373
465
  };
374
466
  const getDropdownMenu = () => {
375
467
  const optionsToRender = enableAutocomplete && searchValue ? filteredOptions : modifiedOptions;
376
468
  const menu = isOpen && (React.createElement("div", { className: dropdownClassess },
377
469
  optionsToRender && optionsToRender.length > 0 ? (optionsToRender.map((optionsToRender, index) => {
378
470
  var _a;
379
- return (React.createElement(DropdownListItem, { key: (_a = optionsToRender === null || optionsToRender === void 0 ? void 0 : optionsToRender.id) !== null && _a !== void 0 ? _a : index, item: optionsToRender, getOptionLabel: getOptionLabel, size: size, selectedItem: selectedItem, variant: variant, onChange: onChangeHandler, isActive: activeIndex === index, activeIndex: activeIndex, index: index }));
471
+ return (React.createElement(DropdownListItem, { key: (_a = optionsToRender === null || optionsToRender === void 0 ? void 0 : optionsToRender.id) !== null && _a !== void 0 ? _a : index, item: optionsToRender, getOptionLabel: getOptionLabel, size: size, selectedItem: multiple ? selectedItems : selectedItem, variant: variant, onChange: onChangeHandler, isActive: activeIndex === index, activeIndex: activeIndex, index: index }));
380
472
  })) : (React.createElement("div", { className: `${styles['item-container']} ${styles['item-block']}`, style: { paddingLeft: '15px' } }, lng === 'ru' || lng.includes('ru')
381
473
  ? noOptionsText || 'Нет вариантов для выбора'
382
474
  : noOptionsText || 'No options to select')),
@@ -429,6 +521,14 @@ export const Dropdown = ({ options, id, label, placeholder, required = false, va
429
521
  }
430
522
  }, [options]);
431
523
  useEffect(() => {
524
+ if (multiple) {
525
+ if (Array.isArray(value)) {
526
+ setSelectedItems(value => value.map(item => checkItem(item)));
527
+ }
528
+ else {
529
+ setSelectedItems([]);
530
+ }
531
+ }
432
532
  if (value || defaultValue) {
433
533
  const startValue = value
434
534
  ? checkItem(value)
@@ -440,7 +540,7 @@ export const Dropdown = ({ options, id, label, placeholder, required = false, va
440
540
  else {
441
541
  setSelectedItem(null);
442
542
  }
443
- }, [value, defaultValue]);
543
+ }, [value, defaultValue, multiple]);
444
544
  useEffect(() => {
445
545
  setErrorInput(error);
446
546
  }, [error]);
@@ -452,7 +552,10 @@ export const Dropdown = ({ options, id, label, placeholder, required = false, va
452
552
  React.createElement("button", { className: buttonClassess, onClick: readOnly ? undefined : handleToggle, disabled: disabled, tabIndex: 0, onKeyDown: handleKeyDown },
453
553
  getTextField(),
454
554
  React.createElement("div", { className: styles.actionButtons },
455
- clearable && !readOnly && !disabled && (selectedItem || (enableAutocomplete && searchValue)) && (React.createElement("div", { className: styles.resetButton },
555
+ clearable &&
556
+ !readOnly &&
557
+ !disabled &&
558
+ (selectedItem || (multiple && selectedItems.length !== 0) || (enableAutocomplete && searchValue)) && (React.createElement("div", { className: styles.resetButton },
456
559
  React.createElement(IconClose, { strokeWidth: "0.2", htmlColor: "var(--text-light)", onClick: handleReset }))),
457
560
  React.createElement("div", { className: styles.dropdownIcon }, !isOpen ? (React.createElement(ChevronDown, { strokeWidth: size === 'lg' ? '0.5' : '0.3' })) : (React.createElement(ChevronUp, { strokeWidth: size === 'lg' ? '0.5' : '0.3' })))),
458
561
  getDropdownMenu()),
@@ -350,3 +350,64 @@
350
350
  .loadMore:hover {
351
351
  color: var(--blue-dark);
352
352
  }
353
+
354
+ .chipsWrap {
355
+ display: flex;
356
+ align-items: center;
357
+ gap: 6px;
358
+ min-width: 0;
359
+ overflow: hidden;
360
+ }
361
+
362
+ .chip {
363
+ display: inline-flex;
364
+ align-items: center;
365
+ gap: 6px;
366
+ max-width: 100%;
367
+ padding: 2px 8px;
368
+ border-radius: 999px;
369
+ background: var(--fills-active);
370
+ border: 1px solid var(--icons-active);
371
+ color: var(--text-primary);
372
+ user-select: none;
373
+ min-width: 0;
374
+ }
375
+
376
+ .chipLabel {
377
+ overflow: hidden;
378
+ text-overflow: ellipsis;
379
+ white-space: nowrap;
380
+ min-width: 0;
381
+ font-size: 12px;
382
+ line-height: 16px;
383
+ }
384
+
385
+ .chipRemove {
386
+ display: inline-flex;
387
+ align-items: center;
388
+ justify-content: center;
389
+ width: 16px;
390
+ height: 16px;
391
+ border-radius: 50%;
392
+ color: var(--text-light);
393
+ cursor: pointer;
394
+ font-size: 14px;
395
+ line-height: 14px;
396
+ }
397
+
398
+ .chipRemove:hover {
399
+ background: rgba(0, 0, 0, 0.06);
400
+ }
401
+
402
+ .chipMore {
403
+ display: inline-flex;
404
+ align-items: center;
405
+ padding: 2px 8px;
406
+ border-radius: 999px;
407
+ background: rgba(0, 0, 0, 0.06);
408
+ color: var(--text-primary);
409
+ font-size: 12px;
410
+ line-height: 16px;
411
+ user-select: none;
412
+ white-space: nowrap;
413
+ }
@@ -18,5 +18,6 @@
18
18
  }
19
19
 
20
20
  .wrapper {
21
- width: 100%;
21
+ /* width: 100%; */
22
+ display: contents;
22
23
  }
@@ -180,7 +180,7 @@ export interface DropdownProps {
180
180
  /** Обязательное поле */
181
181
  required?: boolean;
182
182
  /** Значение */
183
- value?: string | number | TOptions | null;
183
+ value?: string | number | TOptions | null | TOptions[];
184
184
  /** Значение по умолчанию */
185
185
  defaultValue?: string | number | TOptions | null;
186
186
  /** Callback, который будет вызван при изменении значения */
@@ -229,6 +229,10 @@ export interface DropdownProps {
229
229
  noOptionsText?: string;
230
230
  /** Язык */
231
231
  lng?: string;
232
+ /** Множественный выбор */
233
+ multiple?: boolean;
234
+ /** Количество видимых значений при множественном выборе */
235
+ limitTags?: number;
232
236
  }
233
237
  /** @internal */
234
238
  export interface TypographyProps {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kamotive_ui",
3
- "version": "15.01.26",
3
+ "version": "21.01.26",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "files": [