kamotive_ui 1.2.6 → 1.2.7

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.
@@ -83,22 +83,77 @@ export const DropdownListItem = ({ item, getOptionLabel, size = 'md', selectedIt
83
83
  return (React.createElement(DropdownListItem, { key: (_a = child === null || child === void 0 ? void 0 : child.key) !== null && _a !== void 0 ? _a : childIndex, item: child, getOptionLabel: getOptionLabel, size: size, selectedItem: selectedItem, onChange: onChange, isActive: activeIndex === index, activeIndex: activeIndex, index: childIndex }));
84
84
  })))));
85
85
  };
86
- export const Dropdown = ({ id, label, placeholder, size = 'lg', options, getOptionLabel, value, defaultValue, variant = 'text', style, className, disabled = false, readOnly = false, isOpened = false, noOptionsText = 'Нет вариатов для выбора', isLeftLabel = false, error = false, helperText, onChange, onClose, clearable = true, required = false, isDivider = false, }) => {
87
- var _a;
86
+ export const Dropdown = ({ options, id, label, placeholder, required = false, value, defaultValue, onChange, 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 = 'Нет вариантов для выбора', }) => {
88
87
  const [isOpen, setIsOpen] = useState(isOpened);
89
88
  const [modifiedOptions, setModifiedOptions] = useState([]);
90
89
  const [selectedItem, setSelectedItem] = useState(null);
91
90
  const [errorInput, setErrorInput] = useState(false);
92
91
  const [errorInputHelperText, setErrorInputHelperText] = useState(helperText);
93
92
  const [activeIndex, setActiveIndex] = useState(-1);
93
+ const [searchValue, setSearchValue] = useState('');
94
+ const [filteredOptions, setFilteredOptions] = useState([]);
95
+ const inputRef = useRef(null);
94
96
  const containerRef = useRef(null);
95
97
  const [containerWidth, setContainerWidth] = useState(undefined);
96
- const handleToggle = (event) => {
97
- setIsOpen((prev) => !prev);
98
- if (isOpen) {
99
- onClose === null || onClose === void 0 ? void 0 : onClose(event);
98
+ const wrapperClassess = classNames(className, {
99
+ [styles['dropdown--container']]: !isLeftLabel,
100
+ [styles['dropdown--container-left']]: isLeftLabel,
101
+ [styles['dropdown--container-label']]: label && !isLeftLabel && !required,
102
+ [styles['dropdown--container-helperText']]: errorInput,
103
+ });
104
+ const buttonClassess = classNames(styles.button, styles[`button--${size}`], {
105
+ [styles['button-item--selected']]: (selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.value) && !disabled,
106
+ [styles['button--readOnly']]: readOnly,
107
+ [styles['button--disabled']]: disabled,
108
+ [styles['button--error']]: errorInput,
109
+ });
110
+ const dropdownClassess = classNames(styles.dropdown, className, {
111
+ [styles['dropdown--disabled']]: disabled,
112
+ });
113
+ const labelClasses = classNames(styles.label, styles[size], {
114
+ [styles['label--default']]: !isLeftLabel,
115
+ [styles['label--left']]: isLeftLabel,
116
+ [styles['label--required']]: required,
117
+ });
118
+ const selectedItemClassess = classNames({
119
+ [styles['item-selected']]: selectedItem,
120
+ [styles['item-placeholder']]: !selectedItem && ((placeholder !== null && placeholder !== void 0 ? placeholder : label) || (!placeholder && !label)),
121
+ [styles['button--icons--item-selected']]: variant === 'icons' && (selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.icon),
122
+ });
123
+ // обновляет значения searchValue и filteredOptions
124
+ const setAutocompleteValues = (value) => {
125
+ setSearchValue(value);
126
+ // фильтрация по введенному значению
127
+ if (modifiedOptions && modifiedOptions.length > 0) {
128
+ const filtered = modifiedOptions.filter((option) => {
129
+ if (!option || !option.value)
130
+ return false;
131
+ const optionValue = String(option.value).toLowerCase();
132
+ return optionValue.includes(value.toLowerCase());
133
+ });
134
+ setFilteredOptions(filtered);
135
+ setActiveIndex(filtered && filtered.length > 0 ? 0 : -1);
100
136
  }
101
137
  };
138
+ const handleToggle = useCallback((event) => {
139
+ event.preventDefault();
140
+ event.stopPropagation();
141
+ const newIsOpen = !isOpen;
142
+ setIsOpen(newIsOpen);
143
+ if (newIsOpen && enableAutocomplete) {
144
+ const value = (selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.value) ? selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.value.toString() : searchValue;
145
+ setSearchValue(value);
146
+ setFilteredOptions(modifiedOptions);
147
+ requestAnimationFrame(() => {
148
+ if (inputRef.current) {
149
+ inputRef.current.focus();
150
+ }
151
+ });
152
+ }
153
+ else if (!newIsOpen) {
154
+ onClose === null || onClose === void 0 ? void 0 : onClose(event);
155
+ }
156
+ }, [isOpen, enableAutocomplete, searchValue, selectedItem, modifiedOptions, onClose]);
102
157
  const onChangeHandler = (event, item) => {
103
158
  event.preventDefault();
104
159
  event.stopPropagation();
@@ -116,6 +171,12 @@ export const Dropdown = ({ id, label, placeholder, size = 'lg', options, getOpti
116
171
  setErrorInput(true);
117
172
  }
118
173
  };
174
+ const handleSearchChange = (event) => {
175
+ event.preventDefault();
176
+ event.stopPropagation();
177
+ const value = event.target.value;
178
+ setAutocompleteValues(value);
179
+ };
119
180
  //для выбора опции из списка с клавиатуры
120
181
  const handleKeyDown = (event) => {
121
182
  if (!isOpen) {
@@ -127,6 +188,14 @@ export const Dropdown = ({ id, label, placeholder, size = 'lg', options, getOpti
127
188
  }
128
189
  return;
129
190
  }
191
+ if (enableAutocomplete &&
192
+ event.target instanceof HTMLInputElement &&
193
+ (event.key !== 'ArrowDown' &&
194
+ event.key !== 'ArrowUp' &&
195
+ event.key !== 'Enter' &&
196
+ event.key !== 'Escape')) {
197
+ return;
198
+ }
130
199
  switch (event.key) {
131
200
  case 'ArrowDown':
132
201
  event.preventDefault();
@@ -161,7 +230,11 @@ export const Dropdown = ({ id, label, placeholder, size = 'lg', options, getOpti
161
230
  ? checkItem(defaultValue)
162
231
  : null;
163
232
  setSelectedItem(startValue !== null && startValue !== void 0 ? startValue : null);
164
- setIsOpen(false);
233
+ if (!enableAutocomplete) {
234
+ setIsOpen(false);
235
+ }
236
+ setSearchValue("");
237
+ setFilteredOptions(modifiedOptions);
165
238
  onChange === null || onChange === void 0 ? void 0 : onChange(event, startValue !== null && startValue !== void 0 ? startValue : null);
166
239
  onClose === null || onClose === void 0 ? void 0 : onClose(event);
167
240
  setActiveIndex(-1);
@@ -170,36 +243,37 @@ export const Dropdown = ({ id, label, placeholder, size = 'lg', options, getOpti
170
243
  setErrorInputHelperText(helperText !== null && helperText !== void 0 ? helperText : 'Поле обязательно для заполнения');
171
244
  }
172
245
  };
173
- const wrapperClassess = classNames(className, {
174
- [styles['dropdown--container']]: !isLeftLabel,
175
- [styles['dropdown--container-left']]: isLeftLabel,
176
- [styles['dropdown--container-label']]: label && !isLeftLabel && !required,
177
- [styles['dropdown--container-helperText']]: errorInput,
178
- });
179
- const buttonClassess = classNames(styles.button, styles[`button--${size}`], {
180
- [styles['button-item--selected']]: (selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.value) && !disabled,
181
- [styles['button--readOnly']]: readOnly,
182
- [styles['button--disabled']]: disabled,
183
- [styles['button--error']]: errorInput,
184
- });
185
- const dropdownClassess = classNames(styles.dropdown, className, {
186
- [styles['dropdown--disabled']]: disabled,
187
- });
188
- const labelClasses = classNames(styles.label, styles[size], {
189
- [styles['label--default']]: !isLeftLabel,
190
- [styles['label--left']]: isLeftLabel,
191
- [styles['label--required']]: required,
192
- });
193
- const selectedItemClassess = classNames({
194
- [styles['item-selected']]: selectedItem,
195
- [styles['item-placeholder']]: !selectedItem && ((placeholder !== null && placeholder !== void 0 ? placeholder : label) || (!placeholder && !label)),
196
- [styles['button--icons--item-selected']]: variant === 'icons' && (selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.icon),
197
- });
246
+ const getTextField = () => {
247
+ var _a;
248
+ return (React.createElement("div", { className: selectedItemClassess },
249
+ variant === 'icons' &&
250
+ (selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.icon) &&
251
+ React.cloneElement(selectedItem.icon, {
252
+ strokeWidth: size === 'lg' ? '0.5' : size === 'md' ? '0.3' : '0.0',
253
+ }),
254
+ isOpen && enableAutocomplete ? (React.createElement("input", { ref: inputRef, type: "text", value: searchValue, className: styles.inlineSearchInput, onChange: handleSearchChange, placeholder: (selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.value) ? selectedItem.value.toString() : 'Поиск...', onClick: (e) => {
255
+ e.stopPropagation();
256
+ e.preventDefault();
257
+ onClick === null || onClick === void 0 ? void 0 : onClick(e);
258
+ e.currentTarget.focus();
259
+ }, onMouseDown: (e) => {
260
+ e.stopPropagation();
261
+ }, onFocus: (e) => {
262
+ e.stopPropagation();
263
+ onFocus === null || onFocus === void 0 ? void 0 : onFocus(e);
264
+ }, onBlur: (e) => {
265
+ e.stopPropagation();
266
+ onBlur === null || onBlur === void 0 ? void 0 : onBlur(e);
267
+ }, onKeyDown: handleKeyDown, autoFocus: true })) : selectedItem ? (selectedItem.value) : (searchValue || ((_a = placeholder !== null && placeholder !== void 0 ? placeholder : label) !== null && _a !== void 0 ? _a : 'Выберите значение'))));
268
+ };
198
269
  const getDropdownMenu = () => {
199
- const menu = isOpen && (React.createElement("div", { className: dropdownClassess }, modifiedOptions && modifiedOptions.length > 0 ? (modifiedOptions.map((modifiedOption, index) => {
270
+ const optionsToRender = enableAutocomplete && searchValue
271
+ ? filteredOptions
272
+ : modifiedOptions;
273
+ const menu = isOpen && (React.createElement("div", { className: dropdownClassess }, optionsToRender && optionsToRender.length > 0 ? (optionsToRender.map((optionsToRender, index) => {
200
274
  var _a;
201
- return (React.createElement(DropdownListItem, { key: (_a = modifiedOption === null || modifiedOption === void 0 ? void 0 : modifiedOption.key) !== null && _a !== void 0 ? _a : index, item: modifiedOption, getOptionLabel: getOptionLabel, size: size, selectedItem: selectedItem, variant: variant, onChange: onChangeHandler, isActive: activeIndex === index, activeIndex: activeIndex, index: index }));
202
- })) : (React.createElement("div", { className: styles['no-options'] }, noOptionsText))));
275
+ return (React.createElement(DropdownListItem, { key: (_a = optionsToRender === null || optionsToRender === void 0 ? void 0 : optionsToRender.key) !== 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 }));
276
+ })) : (React.createElement("div", { className: `${styles['item-container']} ${styles['item-block']}`, style: { paddingLeft: "15px" } }, noOptionsText))));
203
277
  return isOpen ? menu : null;
204
278
  };
205
279
  useEffect(() => {
@@ -254,21 +328,18 @@ export const Dropdown = ({ id, label, placeholder, size = 'lg', options, getOpti
254
328
  else {
255
329
  setSelectedItem(null);
256
330
  }
257
- }, [value, defaultValue, checkItem]);
331
+ }, [value, defaultValue]);
258
332
  useEffect(() => {
259
333
  setErrorInput(error);
260
334
  }, [error]);
261
- return (React.createElement("div", { id: id, className: wrapperClassess, ref: containerRef, style: style ? style : { width: isLeftLabel && containerWidth ? `${containerWidth}px` : '100%' } },
335
+ useEffect(() => {
336
+ setFilteredOptions(modifiedOptions);
337
+ }, [modifiedOptions]);
338
+ return (React.createElement("div", { id: id, className: wrapperClassess, ref: containerRef, onClick: onClick, style: style ? style : { width: isLeftLabel && containerWidth ? `${containerWidth}px` : '100%' } },
262
339
  label && (React.createElement(Typography, { variant: "Caption", className: labelClasses }, label)),
263
340
  React.createElement("button", { className: buttonClassess, onClick: readOnly ? undefined : handleToggle, disabled: disabled, tabIndex: 0, onKeyDown: handleKeyDown },
264
- React.createElement("div", { className: selectedItemClassess },
265
- variant === 'icons' &&
266
- (selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.icon) &&
267
- React.cloneElement(selectedItem.icon, {
268
- strokeWidth: size === 'lg' ? '0.5' : size === 'md' ? '0.3' : '0.0',
269
- }),
270
- selectedItem ? selectedItem.value : ((_a = placeholder !== null && placeholder !== void 0 ? placeholder : label) !== null && _a !== void 0 ? _a : 'Выберите значение')),
271
- clearable && !readOnly && !disabled && selectedItem && (React.createElement("div", { className: styles.resetButton },
341
+ getTextField(),
342
+ clearable && !readOnly && !disabled && (selectedItem || enableAutocomplete && searchValue) && (React.createElement("div", { className: styles.resetButton },
272
343
  React.createElement(IconClose10, { strokeWidth: "0.2", htmlColor: "var(--text-light)", onClick: handleReset }))),
273
344
  React.createElement("div", { className: styles.dropdownIcon }, !isOpen ? (React.createElement(ChevronDown10, { strokeWidth: size === 'lg' ? '0.5' : '0.3' })) : (React.createElement(ChevronUp10, { strokeWidth: size === 'lg' ? '0.5' : '0.3' }))),
274
345
  getDropdownMenu()),
@@ -143,6 +143,19 @@
143
143
  overflow-x: hidden;
144
144
  }
145
145
 
146
+ .inlineSearchInput {
147
+ border: none;
148
+ outline: none;
149
+ background: transparent;
150
+ font-size: inherit;
151
+ font-family: inherit;
152
+ color: inherit;
153
+ padding: 0;
154
+ cursor: text;
155
+ pointer-events: auto;
156
+ text-align: left;
157
+ }
158
+
146
159
  .item--container {
147
160
  padding: 0.5em 0.5em;
148
161
  width: 87%;
@@ -195,6 +208,7 @@
195
208
 
196
209
  .label {
197
210
  transition: 0.3ms ease-out;
211
+ font-weight: 600;
198
212
  /* color: var(--text-grey); */
199
213
  }
200
214
  .label--default {
@@ -205,7 +219,7 @@
205
219
  overflow: hidden;
206
220
  text-overflow: ellipsis;
207
221
  max-width: calc(100% - 32px);
208
- color: var(--text-grey);
222
+ color: var(--text-dark);
209
223
  }
210
224
  .label--default.lg {
211
225
  font-size: 12px;
@@ -240,14 +254,27 @@
240
254
 
241
255
  .item-selected {
242
256
  color: var(--text-dark);
243
- white-space: nowrap;
257
+ width: 80%;
244
258
  overflow: hidden;
245
259
  text-overflow: ellipsis;
260
+ white-space: nowrap;
261
+ min-width: 0;
262
+ text-align: left;
263
+ position: relative;
264
+ }
265
+
266
+ .item-selected .inlineSearchInput {
267
+ text-align: left;
268
+ direction: ltr;
269
+ width: 100%;
270
+ overflow: visible;
246
271
  }
247
272
 
248
273
  .item-placeholder {
249
274
  color: var(--text-grey);
250
275
  transition: 0.3ms ease-out;
276
+ display: flex;
277
+ justify-content: flex-start;
251
278
  }
252
279
 
253
280
  .helperText.md {
@@ -103,7 +103,7 @@
103
103
 
104
104
  .label {
105
105
  line-height: 14px;
106
- font-weight: 400;
106
+ font-weight: 600;
107
107
  transition: 0.3ms ease-out;
108
108
  /* color: var(--text-grey); */
109
109
  }
@@ -115,7 +115,7 @@
115
115
  overflow: hidden;
116
116
  text-overflow: ellipsis;
117
117
  max-width: calc(100% - 32px);
118
- color: var(--text-grey);
118
+ color: var(--text-dark);
119
119
  }
120
120
  .label--default .lg {
121
121
  font-size: 12px;
@@ -3,7 +3,7 @@ import styles from './Tab.module.css';
3
3
  import classNames from 'classnames';
4
4
  ;
5
5
  import { Typography } from '../Typography/Typography';
6
- export const Tab = ({ value, onClick, label, selected, disabled = false }) => {
6
+ export const Tab = ({ value, onClick, onMouseEnter, label, selected, disabled = false }) => {
7
7
  const handleClick = (e) => {
8
8
  if (onClick && value && !disabled) {
9
9
  onClick(value);
@@ -12,6 +12,6 @@ export const Tab = ({ value, onClick, label, selected, disabled = false }) => {
12
12
  return (React.createElement("button", { role: "tab", "aria-selected": selected, "aria-disabled": disabled, value: value, className: classNames(styles.tab, {
13
13
  [styles['selected']]: selected,
14
14
  [styles['disabled']]: disabled,
15
- }), onClick: handleClick },
15
+ }), onClick: handleClick, onMouseEnter: onMouseEnter },
16
16
  React.createElement(Typography, { variant: selected ? 'Body2-Medium' : "Body2" }, label)));
17
17
  };
@@ -120,52 +120,60 @@ export type BaseOptions = {
120
120
  };
121
121
  export type TOptions<T = {}> = BaseOptions & T;
122
122
  export interface DropdownProps {
123
+ /** Массив элементов для выпадающего списка */
124
+ options: Array<string | number | TOptions>;
123
125
  /** Идентификатор */
124
126
  id?: string;
125
- /** Лейбл */
127
+ /** Лейбл */
126
128
  label?: string;
127
129
  /** Подсказик заполнения */
128
130
  placeholder?: string;
129
- /** Размер */
130
- size?: 'md' | 'lg';
131
- /** Массив элементов для выпадающего списка */
132
- options: Array<string | number | TOptions>;
133
- /** Функция для получения текста опции */
134
- getOptionLabel?: (option: TOptions) => keyof TOptions;
131
+ /** Обязательное поле */
132
+ required?: boolean;
135
133
  /** Значение */
136
134
  value?: string | number | TOptions | null;
137
135
  /** Значение по умолчанию */
138
136
  defaultValue?: string | number | TOptions | null;
137
+ /** Callback, который будет вызван при изменении значения */
138
+ onChange?: (event: any, value: string | number | TOptions | null) => void;
139
+ /** Функция для получения текста опции */
140
+ getOptionLabel?: (option: TOptions) => keyof TOptions;
139
141
  /** Вариaнты выпадающего списка(текст + иконка, текст)' */
140
142
  variant?: 'icons' | 'text';
143
+ /** Размер */
144
+ size?: 'md' | 'lg';
141
145
  /** Стили передаваемые напрямую */
142
146
  style?: CSSProperties;
143
- /**Дополнительный класс */
147
+ /** Дополнительный класс */
144
148
  className?: string;
149
+ /** Отображение левой метки */
150
+ isLeftLabel?: boolean;
151
+ /** Отображение разделителя */
152
+ isDivider?: boolean;
145
153
  /** Заблокированный */
146
154
  disabled?: boolean;
147
155
  /** Только для чтения */
148
156
  readOnly?: boolean;
149
157
  /** Открытый */
150
158
  isOpened?: boolean;
151
- /** Текст при отсутствии опций */
152
- noOptionsText?: string;
153
- /** Отображение левой метки */
154
- isLeftLabel?: boolean;
155
159
  /** Ошибка */
156
160
  error?: boolean;
157
161
  /** Текст ошибки */
158
162
  helperText?: string;
159
- /** Callback, который будет вызван при изменении значения */
160
- onChange?: (event: any, value: string | number | TOptions | null) => void;
163
+ /** Callback, который будет вызван при клике */
164
+ onClick?: (event: any) => void;
165
+ /** Callback при потере фокуса */
166
+ onBlur?: (event: any) => void;
167
+ /** Callback при получении фокуса */
168
+ onFocus?: (event: any) => void;
161
169
  /** Callback, который будет вызван при закрытии выпадающего списка */
162
170
  onClose?: (event: any) => void;
163
171
  /** Возможность сброса значения до первоначального */
164
172
  clearable?: boolean;
165
- /** Обязательное поле */
166
- required?: boolean;
167
- /** Отображение разделителя */
168
- isDivider?: boolean;
173
+ /** Включение автозаполнения */
174
+ enableAutocomplete?: boolean;
175
+ /** Текст при отсутствии опций */
176
+ noOptionsText?: string;
169
177
  }
170
178
  /** @internal */
171
179
  export interface TypographyProps {
@@ -237,6 +245,7 @@ export interface TabProps {
237
245
  value?: string;
238
246
  /** Обработчик клика */
239
247
  onClick?: (value: string) => void;
248
+ onMouseEnter?: (event: React.MouseEvent<HTMLButtonElement>) => void;
240
249
  /** Текст лейбла */
241
250
  label?: string;
242
251
  /** Размер */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kamotive_ui",
3
- "version": "1.2.6",
3
+ "version": "1.2.7",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "files": [