@true-engineering/true-react-common-ui-kit 1.3.1 → 1.4.0
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/Input/Input.d.ts +3 -3
- package/dist/components/ScrollIntoViewIfNeeded/ScrollIntoViewIfNeeded.d.ts +1 -1
- package/dist/components/Select/Select.d.ts +8 -3
- package/dist/components/Select/Select.styles.d.ts +10 -0
- package/dist/components/Select/SelectList/SelectList.d.ts +4 -3
- package/dist/components/Select/SelectList/SelectList.styles.d.ts +9 -0
- package/dist/helpers/snippets.d.ts +3 -0
- package/dist/helpers/utils.d.ts +1 -0
- package/dist/hooks/use-dropdown.d.ts +1 -1
- package/dist/true-react-common-ui-kit.js +340 -291
- package/dist/true-react-common-ui-kit.js.map +1 -1
- package/dist/true-react-common-ui-kit.umd.cjs +340 -291
- package/dist/true-react-common-ui-kit.umd.cjs.map +1 -1
- package/package.json +1 -1
- package/src/components/FiltersPane/FiltersPane.stories.tsx +1 -0
- package/src/components/Input/Input.tsx +5 -3
- package/src/components/Select/Select.stories.tsx +16 -0
- package/src/components/Select/Select.styles.ts +11 -0
- package/src/components/Select/Select.tsx +86 -37
- package/src/components/Select/SelectList/SelectList.styles.ts +11 -0
- package/src/components/Select/SelectList/SelectList.tsx +14 -5
- package/src/helpers/snippets.tsx +5 -0
- package/src/helpers/utils.ts +14 -0
- package/src/hooks/use-dropdown.ts +1 -1
package/package.json
CHANGED
|
@@ -9,17 +9,19 @@ import {
|
|
|
9
9
|
KeyboardEvent,
|
|
10
10
|
ClipboardEvent,
|
|
11
11
|
InputHTMLAttributes,
|
|
12
|
+
ReactElement,
|
|
12
13
|
} from 'react';
|
|
13
14
|
import clsx from 'clsx';
|
|
14
15
|
import InputMask, { Props as ReactInputMaskProps } from 'react-input-mask';
|
|
15
16
|
|
|
16
|
-
import { Icon,
|
|
17
|
+
import { Icon, IIconType } from '../Icon';
|
|
17
18
|
import { ThemedPreloader } from '../ThemedPreloader';
|
|
18
19
|
import { addDataAttributes, addDataTestId, isNotEmpty } from '../../helpers';
|
|
19
20
|
import { ICommonProps } from '../../types';
|
|
20
21
|
import { useTheme, useTweakStyles } from '../../hooks';
|
|
21
22
|
|
|
22
23
|
import { InputStyles, styles } from './Input.styles';
|
|
24
|
+
import { renderIcon } from '../../helpers/snippets';
|
|
23
25
|
|
|
24
26
|
export const DEFAULT_SIZE = 6;
|
|
25
27
|
|
|
@@ -70,7 +72,7 @@ export interface IInputProps extends ICommonProps {
|
|
|
70
72
|
* @default 6
|
|
71
73
|
*/
|
|
72
74
|
defaultSize?: number;
|
|
73
|
-
iconType?:
|
|
75
|
+
iconType?: IIconType | ReactElement;
|
|
74
76
|
units?: string;
|
|
75
77
|
name?: string;
|
|
76
78
|
mask?: ReactInputMaskProps['mask'];
|
|
@@ -286,7 +288,7 @@ export const Input = forwardRef<HTMLInputElement, IInputProps>(
|
|
|
286
288
|
})}
|
|
287
289
|
onClick={!isDisabled ? onIconClick : undefined}
|
|
288
290
|
>
|
|
289
|
-
|
|
291
|
+
{renderIcon(iconType)}
|
|
290
292
|
</div>
|
|
291
293
|
)}
|
|
292
294
|
</div>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ReactNode, useEffect, useState } from 'react';
|
|
2
2
|
import { Select, ISelectProps } from './Select';
|
|
3
3
|
import { ComponentMeta, ComponentStory } from '@storybook/react';
|
|
4
|
+
import { isNotEmpty } from '../../helpers';
|
|
4
5
|
|
|
5
6
|
interface ObjectValue {
|
|
6
7
|
name: string;
|
|
@@ -79,6 +80,7 @@ interface ISelectWithCustomProps<T> extends ISelectProps<T> {
|
|
|
79
80
|
shouldRenderInBody?: boolean;
|
|
80
81
|
shouldHideOnScroll?: boolean;
|
|
81
82
|
shouldUseCustomIsDisabledFunction?: boolean;
|
|
83
|
+
shouldRenderSearchInputInList?: boolean;
|
|
82
84
|
canBeFlipped?: boolean;
|
|
83
85
|
scrollParent?: 'document' | 'auto';
|
|
84
86
|
}
|
|
@@ -91,17 +93,21 @@ function SelectWithCustomProps<T>({
|
|
|
91
93
|
shouldRenderInBody,
|
|
92
94
|
shouldHideOnScroll,
|
|
93
95
|
shouldUseCustomIsDisabledFunction,
|
|
96
|
+
shouldRenderSearchInputInList,
|
|
94
97
|
canBeFlipped,
|
|
95
98
|
scrollParent,
|
|
99
|
+
noMatchesLabel,
|
|
96
100
|
...rest
|
|
97
101
|
}: ISelectWithCustomProps<T>) {
|
|
98
102
|
const [stringValue, setStringValue] = useState<string>();
|
|
99
103
|
const stringHandler = (newValue?: string) => {
|
|
104
|
+
console.log('change');
|
|
100
105
|
setStringValue(newValue);
|
|
101
106
|
};
|
|
102
107
|
|
|
103
108
|
const [objectValue, setObjectValue] = useState<ObjectValue>();
|
|
104
109
|
const objectHandler = (newValue?: ObjectValue) => {
|
|
110
|
+
console.log('change');
|
|
105
111
|
setObjectValue(newValue);
|
|
106
112
|
};
|
|
107
113
|
|
|
@@ -133,6 +139,10 @@ function SelectWithCustomProps<T>({
|
|
|
133
139
|
console.log('isOpen');
|
|
134
140
|
};
|
|
135
141
|
|
|
142
|
+
const handleBlur = () => {
|
|
143
|
+
console.log('blur');
|
|
144
|
+
};
|
|
145
|
+
|
|
136
146
|
useEffect(() => {
|
|
137
147
|
const api = async () => {
|
|
138
148
|
setDynamicOptions(await getOptions());
|
|
@@ -172,9 +182,14 @@ function SelectWithCustomProps<T>({
|
|
|
172
182
|
<Select
|
|
173
183
|
{...rest}
|
|
174
184
|
{...(props as unknown as ISelectProps<any>)}
|
|
185
|
+
{...(shouldRenderSearchInputInList && {
|
|
186
|
+
searchInput: { shouldRenderInList: true },
|
|
187
|
+
})}
|
|
188
|
+
noMatchesLabel={isNotEmpty(noMatchesLabel) ? noMatchesLabel : undefined}
|
|
175
189
|
optionsMode={optionsMode}
|
|
176
190
|
onType={async () => setDynamicOptions(await getOptions())}
|
|
177
191
|
onOpen={handleOpen}
|
|
192
|
+
onBlur={handleBlur}
|
|
178
193
|
dropdownOptions={{
|
|
179
194
|
shouldUsePopper,
|
|
180
195
|
shouldRenderInBody,
|
|
@@ -236,6 +251,7 @@ Default.args = {
|
|
|
236
251
|
shouldRenderInBody: false,
|
|
237
252
|
shouldHideOnScroll: false,
|
|
238
253
|
shouldUseCustomIsDisabledFunction: false,
|
|
254
|
+
shouldRenderSearchInputInList: false,
|
|
239
255
|
shouldScrollToList: true,
|
|
240
256
|
canBeFlipped: false,
|
|
241
257
|
scrollParent: 'document',
|
|
@@ -69,6 +69,17 @@ export const styles = {
|
|
|
69
69
|
},
|
|
70
70
|
|
|
71
71
|
tweakSelectList: {},
|
|
72
|
+
|
|
73
|
+
tweakSearchInput: {
|
|
74
|
+
tweakInput: {
|
|
75
|
+
inputWrapper: {
|
|
76
|
+
height: 48,
|
|
77
|
+
borderRadius: 0,
|
|
78
|
+
border: 'none',
|
|
79
|
+
backgroundColor: 'transparent',
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
},
|
|
72
83
|
};
|
|
73
84
|
|
|
74
85
|
export type SelectStyles = ComponentStyles<typeof styles>;
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
useMemo,
|
|
9
9
|
useRef,
|
|
10
10
|
useState,
|
|
11
|
+
SyntheticEvent,
|
|
11
12
|
} from 'react';
|
|
12
13
|
import { Styles } from 'jss';
|
|
13
14
|
import clsx from 'clsx';
|
|
@@ -22,9 +23,10 @@ import {
|
|
|
22
23
|
useTheme,
|
|
23
24
|
useOnClickOutsideWithRef,
|
|
24
25
|
useDropdown,
|
|
26
|
+
useTweakStyles,
|
|
25
27
|
} from '../../hooks';
|
|
26
28
|
import { IDropdownWithPopperOptions } from '../../types';
|
|
27
|
-
import { isNotEmpty } from '../../helpers';
|
|
29
|
+
import { getTestId, hasExactParent, isNotEmpty } from '../../helpers';
|
|
28
30
|
import {
|
|
29
31
|
defaultConvertFunction,
|
|
30
32
|
defaultCompareFunction,
|
|
@@ -32,9 +34,10 @@ import {
|
|
|
32
34
|
defaultIsOptionDisabled,
|
|
33
35
|
} from './helpers';
|
|
34
36
|
import { SelectStyles, styles } from './Select.styles';
|
|
37
|
+
import { ISearchInputProps, SearchInput } from '../SearchInput';
|
|
35
38
|
|
|
36
39
|
export interface ISelectProps<Value>
|
|
37
|
-
extends Omit<IInputProps, 'value' | 'onChange' | 'type'> {
|
|
40
|
+
extends Omit<IInputProps, 'value' | 'onChange' | 'onBlur' | 'type'> {
|
|
38
41
|
tweakStyles?: SelectStyles;
|
|
39
42
|
defaultOptionLabel?: string;
|
|
40
43
|
noMatchesLabel?: string;
|
|
@@ -47,8 +50,13 @@ export interface ISelectProps<Value>
|
|
|
47
50
|
options: Value[];
|
|
48
51
|
value: Value | undefined;
|
|
49
52
|
shouldScrollToList?: boolean;
|
|
53
|
+
searchInput?: { shouldRenderInList: true } & Pick<
|
|
54
|
+
ISearchInputProps,
|
|
55
|
+
'placeholder'
|
|
56
|
+
>;
|
|
50
57
|
isOptionDisabled?(option: Value): boolean;
|
|
51
58
|
onChange(value: Value | undefined): void; // подумать как возвращать индекс
|
|
59
|
+
onBlur?(event: Event | SyntheticEvent): void;
|
|
52
60
|
onType?(value: string): Promise<void>;
|
|
53
61
|
optionsFilter?(options: Value[], query: string): Value[];
|
|
54
62
|
onOpen?(): void;
|
|
@@ -75,6 +83,7 @@ export function Select<Value>({
|
|
|
75
83
|
minSymbolsCountToOpenList = 0,
|
|
76
84
|
dropdownIcon = 'chevron-down',
|
|
77
85
|
shouldScrollToList = true,
|
|
86
|
+
searchInput,
|
|
78
87
|
onChange,
|
|
79
88
|
onFocus,
|
|
80
89
|
onBlur,
|
|
@@ -105,6 +114,12 @@ export function Select<Value>({
|
|
|
105
114
|
const list = useRef<HTMLDivElement>(null);
|
|
106
115
|
const input = useRef<HTMLInputElement>(null); // TODO ref снаружи?
|
|
107
116
|
|
|
117
|
+
const shouldRenderSearchInputInList =
|
|
118
|
+
searchInput?.shouldRenderInList === true;
|
|
119
|
+
|
|
120
|
+
const hasSearchInputInList =
|
|
121
|
+
optionsMode !== 'normal' && shouldRenderSearchInputInList;
|
|
122
|
+
|
|
108
123
|
const stringValue = isNotEmpty(value)
|
|
109
124
|
? convertValueToString(value)
|
|
110
125
|
: undefined;
|
|
@@ -132,23 +147,21 @@ export function Select<Value>({
|
|
|
132
147
|
);
|
|
133
148
|
}, [filteredOptions, value, convertValueToString]);
|
|
134
149
|
|
|
135
|
-
const handleListClose = () => {
|
|
150
|
+
const handleListClose = (event: Event | SyntheticEvent) => {
|
|
136
151
|
setIsListOpen(false);
|
|
137
152
|
setSearchValue('');
|
|
138
153
|
setShouldShowDefaultOption(true);
|
|
154
|
+
onBlur?.(event);
|
|
139
155
|
};
|
|
140
156
|
|
|
141
|
-
const handleListOpen =
|
|
142
|
-
if (isListOpen) {
|
|
143
|
-
|
|
157
|
+
const handleListOpen = () => {
|
|
158
|
+
if (!isListOpen) {
|
|
159
|
+
setIsListOpen(true);
|
|
144
160
|
}
|
|
145
|
-
setIsListOpen(true);
|
|
146
161
|
};
|
|
147
162
|
|
|
148
163
|
const handleFocus = (event: FocusEvent<HTMLInputElement>) => {
|
|
149
|
-
|
|
150
|
-
onFocus(event);
|
|
151
|
-
}
|
|
164
|
+
onFocus?.(event);
|
|
152
165
|
handleListOpen();
|
|
153
166
|
};
|
|
154
167
|
|
|
@@ -157,10 +170,19 @@ export function Select<Value>({
|
|
|
157
170
|
};
|
|
158
171
|
|
|
159
172
|
const handleBlur = (event: FocusEvent<HTMLInputElement>) => {
|
|
160
|
-
if (
|
|
161
|
-
|
|
173
|
+
if (!isNotEmpty(event.relatedTarget) || !isNotEmpty(list.current)) {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const isActionInsideList = hasExactParent(
|
|
178
|
+
event.relatedTarget,
|
|
179
|
+
list.current,
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
// Ниче не делаем если клик был внутри селекта
|
|
183
|
+
if (!isActionInsideList) {
|
|
184
|
+
handleListClose(event);
|
|
162
185
|
}
|
|
163
|
-
handleListClose();
|
|
164
186
|
};
|
|
165
187
|
|
|
166
188
|
const handleOnChange = useCallback(
|
|
@@ -174,10 +196,10 @@ export function Select<Value>({
|
|
|
174
196
|
[value, onChange],
|
|
175
197
|
);
|
|
176
198
|
|
|
177
|
-
const
|
|
178
|
-
(index: number) => {
|
|
199
|
+
const handleOptionSelect = useCallback(
|
|
200
|
+
(index: number, event: MouseEvent<HTMLElement> | KeyboardEvent) => {
|
|
179
201
|
handleOnChange(index === -1 ? undefined : filteredOptions[index]);
|
|
180
|
-
handleListClose();
|
|
202
|
+
handleListClose(event);
|
|
181
203
|
input.current?.blur();
|
|
182
204
|
},
|
|
183
205
|
[handleOnChange, filteredOptions],
|
|
@@ -216,7 +238,7 @@ export function Select<Value>({
|
|
|
216
238
|
setShouldShowDefaultOption(v === '');
|
|
217
239
|
}
|
|
218
240
|
|
|
219
|
-
if (v === '') {
|
|
241
|
+
if (v === '' && !hasSearchInputInList) {
|
|
220
242
|
handleOnChange(undefined);
|
|
221
243
|
}
|
|
222
244
|
|
|
@@ -241,7 +263,7 @@ export function Select<Value>({
|
|
|
241
263
|
indexToClick = 0;
|
|
242
264
|
}
|
|
243
265
|
|
|
244
|
-
|
|
266
|
+
handleOptionSelect(indexToClick, event);
|
|
245
267
|
break;
|
|
246
268
|
}
|
|
247
269
|
|
|
@@ -307,24 +329,31 @@ export function Select<Value>({
|
|
|
307
329
|
}
|
|
308
330
|
};
|
|
309
331
|
|
|
310
|
-
useOnClickOutsideWithRef(
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
},
|
|
315
|
-
inputWrapper,
|
|
316
|
-
);
|
|
332
|
+
useOnClickOutsideWithRef(list, handleListClose, inputWrapper);
|
|
333
|
+
|
|
334
|
+
const hasEnoughSymbolsToSearch =
|
|
335
|
+
searchValue.trim().length >= minSymbolsCountToOpenList;
|
|
317
336
|
|
|
318
337
|
const isOpen =
|
|
338
|
+
// Пользователь пытается открыть лист
|
|
319
339
|
isListOpen &&
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
340
|
+
// Нам есть что показать:
|
|
341
|
+
// Есть опции
|
|
342
|
+
(filteredOptions.length > 0 ||
|
|
343
|
+
// Дефолтная опция
|
|
344
|
+
(defaultOptionLabel !== undefined && !hasEnoughSymbolsToSearch) ||
|
|
345
|
+
// Текст "Загрузка..."
|
|
346
|
+
inputProps.isLoading ||
|
|
347
|
+
// Текст "Совпадений не найдено"
|
|
348
|
+
noMatchesLabel !== undefined ||
|
|
349
|
+
// У нас есть инпут с поиском внутри листа
|
|
350
|
+
hasSearchInputInList) &&
|
|
351
|
+
// Последняя проверка на случай, если мы че то ищем в опциях
|
|
352
|
+
(optionsMode === 'normal' || hasEnoughSymbolsToSearch);
|
|
325
353
|
|
|
326
354
|
const { isReadonly = true } = inputProps;
|
|
327
|
-
const shouldUsePointerCursor =
|
|
355
|
+
const shouldUsePointerCursor =
|
|
356
|
+
(optionsMode === 'normal' || shouldRenderSearchInputInList) && isReadonly;
|
|
328
357
|
|
|
329
358
|
const tweakInputStyles = useMemo(
|
|
330
359
|
() =>
|
|
@@ -341,6 +370,12 @@ export function Select<Value>({
|
|
|
341
370
|
[tweakStyles?.tweakInput, shouldUsePointerCursor],
|
|
342
371
|
);
|
|
343
372
|
|
|
373
|
+
const tweakSearchInputStyles = useTweakStyles(
|
|
374
|
+
componentStyles,
|
|
375
|
+
tweakStyles,
|
|
376
|
+
'tweakSearchInput',
|
|
377
|
+
);
|
|
378
|
+
|
|
344
379
|
// Эти значения ставятся в false по дефолту также в useDropdown
|
|
345
380
|
const {
|
|
346
381
|
shouldUsePopper = false,
|
|
@@ -373,9 +408,8 @@ export function Select<Value>({
|
|
|
373
408
|
[classes.listWrapperInBody]: shouldRenderInBody,
|
|
374
409
|
})}
|
|
375
410
|
ref={list}
|
|
376
|
-
// чтобы предотвратить onBlur на инпуте
|
|
377
|
-
onMouseDown={(event) => event.preventDefault()}
|
|
378
411
|
style={popperData?.styles.popper as Styles}
|
|
412
|
+
onBlur={handleBlur} // обработка для Tab из списка
|
|
379
413
|
{...popperData?.attributes.popper}
|
|
380
414
|
>
|
|
381
415
|
{isOpen && (
|
|
@@ -386,13 +420,24 @@ export function Select<Value>({
|
|
|
386
420
|
? defaultOptionLabel
|
|
387
421
|
: undefined
|
|
388
422
|
}
|
|
423
|
+
customListHeader={
|
|
424
|
+
hasSearchInputInList ? (
|
|
425
|
+
<SearchInput
|
|
426
|
+
value={searchValue}
|
|
427
|
+
onChange={handleInputChange}
|
|
428
|
+
tweakStyles={tweakSearchInputStyles}
|
|
429
|
+
placeholder="Поиск"
|
|
430
|
+
{...searchInput}
|
|
431
|
+
/>
|
|
432
|
+
) : undefined
|
|
433
|
+
}
|
|
389
434
|
noMatchesLabel={noMatchesLabel}
|
|
390
435
|
focusedIndex={focusedListCellIndex}
|
|
391
436
|
activeValue={value}
|
|
392
437
|
isLoading={inputProps.isLoading}
|
|
393
438
|
loadingLabel={loadingLabel}
|
|
394
439
|
tweakStyles={tweakStyles?.tweakSelectList as Styles}
|
|
395
|
-
testId={testId
|
|
440
|
+
testId={getTestId(testId, 'list')}
|
|
396
441
|
// скролл не работает с включеным поппером
|
|
397
442
|
shouldScrollToList={
|
|
398
443
|
shouldScrollToList && !shouldUsePopper && !shouldHideOnScroll
|
|
@@ -401,7 +446,7 @@ export function Select<Value>({
|
|
|
401
446
|
convertValueToString={convertValueToString}
|
|
402
447
|
convertValueToReactNode={convertValueToReactNode}
|
|
403
448
|
convertValueToId={convertValueToId}
|
|
404
|
-
onOptionClick={
|
|
449
|
+
onOptionClick={handleOptionSelect}
|
|
405
450
|
/>
|
|
406
451
|
)}
|
|
407
452
|
</div>
|
|
@@ -415,10 +460,14 @@ export function Select<Value>({
|
|
|
415
460
|
ref={inputWrapper}
|
|
416
461
|
>
|
|
417
462
|
<Input
|
|
418
|
-
value={
|
|
463
|
+
value={
|
|
464
|
+
searchValue !== '' && !shouldRenderSearchInputInList
|
|
465
|
+
? searchValue
|
|
466
|
+
: stringValue
|
|
467
|
+
}
|
|
419
468
|
onChange={handleInputChange}
|
|
420
469
|
isActive={isListOpen}
|
|
421
|
-
isReadonly={optionsMode === 'normal'}
|
|
470
|
+
isReadonly={optionsMode === 'normal' || shouldRenderSearchInputInList}
|
|
422
471
|
onFocus={handleFocus}
|
|
423
472
|
onBlur={handleBlur}
|
|
424
473
|
isDisabled={isDisabled}
|
|
@@ -12,6 +12,17 @@ export const styles = {
|
|
|
12
12
|
boxSizing: 'border-box',
|
|
13
13
|
padding: [CONTAINER_PADDING, 0],
|
|
14
14
|
fontSize: 16,
|
|
15
|
+
overflow: 'hidden',
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
withListHeader: {
|
|
19
|
+
paddingTop: 0,
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
listHeader: {
|
|
23
|
+
'& + $list': {
|
|
24
|
+
borderTop: [1, 'solid', colors.BORDER_LIGHT],
|
|
25
|
+
},
|
|
15
26
|
},
|
|
16
27
|
|
|
17
28
|
list: {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ReactNode, useMemo } from 'react';
|
|
1
|
+
import { ReactNode, useMemo, MouseEvent } from 'react';
|
|
2
2
|
import clsx from 'clsx';
|
|
3
3
|
import { ScrollIntoViewIfNeeded } from '../../ScrollIntoViewIfNeeded';
|
|
4
4
|
import { useTheme } from '../../../hooks';
|
|
@@ -17,7 +17,8 @@ export interface ISelectListProps<Value> extends ICommonProps {
|
|
|
17
17
|
defaultOptionLabel?: string;
|
|
18
18
|
testId?: string;
|
|
19
19
|
shouldScrollToList?: boolean;
|
|
20
|
-
|
|
20
|
+
customListHeader?: ReactNode;
|
|
21
|
+
onOptionClick(index: number, event: MouseEvent<HTMLElement>): void;
|
|
21
22
|
isOptionDisabled(value: Value): boolean;
|
|
22
23
|
convertValueToString(value: Value): string | undefined;
|
|
23
24
|
convertValueToReactNode?(value: Value, isDisabled: boolean): ReactNode;
|
|
@@ -37,6 +38,7 @@ export function SelectList<Value>({
|
|
|
37
38
|
tweakStyles,
|
|
38
39
|
testId,
|
|
39
40
|
shouldScrollToList = true,
|
|
41
|
+
customListHeader,
|
|
40
42
|
isOptionDisabled,
|
|
41
43
|
onOptionClick,
|
|
42
44
|
convertValueToString,
|
|
@@ -66,8 +68,13 @@ export function SelectList<Value>({
|
|
|
66
68
|
return (
|
|
67
69
|
<ScrollIntoViewIfNeeded
|
|
68
70
|
active={shouldScrollToList}
|
|
69
|
-
className={classes.root
|
|
71
|
+
className={clsx(classes.root, {
|
|
72
|
+
[classes.withListHeader]: isNotEmpty(customListHeader),
|
|
73
|
+
})}
|
|
70
74
|
>
|
|
75
|
+
{isNotEmpty(customListHeader) && (
|
|
76
|
+
<div className={classes.listHeader}>{customListHeader}</div>
|
|
77
|
+
)}
|
|
71
78
|
<div className={classes.list} data-testid={testId}>
|
|
72
79
|
{isLoading ? (
|
|
73
80
|
<div className={clsx(classes.cell, classes.loading)}>
|
|
@@ -84,7 +91,7 @@ export function SelectList<Value>({
|
|
|
84
91
|
classes.defaultCell,
|
|
85
92
|
focusedIndex === DEFAULT_OPTION_INDEX && classes.focused,
|
|
86
93
|
)}
|
|
87
|
-
onClick={() => onOptionClick(DEFAULT_OPTION_INDEX)}
|
|
94
|
+
onClick={(event) => onOptionClick(DEFAULT_OPTION_INDEX, event)}
|
|
88
95
|
>
|
|
89
96
|
{defaultOptionLabel}
|
|
90
97
|
</ScrollIntoViewIfNeeded>
|
|
@@ -111,7 +118,9 @@ export function SelectList<Value>({
|
|
|
111
118
|
active: isActive,
|
|
112
119
|
focused: isFocused,
|
|
113
120
|
})}
|
|
114
|
-
onClick={
|
|
121
|
+
onClick={
|
|
122
|
+
!isDisabled ? (event) => onOptionClick(i, event) : undefined
|
|
123
|
+
}
|
|
115
124
|
>
|
|
116
125
|
{opt}
|
|
117
126
|
</ScrollIntoViewIfNeeded>
|
package/src/helpers/utils.ts
CHANGED
|
@@ -11,6 +11,20 @@ export const transformToKebab = (string: string): string => {
|
|
|
11
11
|
return result;
|
|
12
12
|
};
|
|
13
13
|
|
|
14
|
+
export const hasExactParent = (element: Element, parent: Element): boolean => {
|
|
15
|
+
if (element === parent) {
|
|
16
|
+
return true; // Found the exact parent
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const parentNode = getParentNode(element);
|
|
20
|
+
|
|
21
|
+
if (parentNode === element) {
|
|
22
|
+
return false; // Reached the top-level HTML element or Shadow DOM host
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return hasExactParent(parentNode, parent);
|
|
26
|
+
};
|
|
27
|
+
|
|
14
28
|
export const getParentNode = (element: Element | ShadowRoot): Element =>
|
|
15
29
|
element.nodeName === 'HTML'
|
|
16
30
|
? (element as Element)
|
|
@@ -13,11 +13,11 @@ export const useDropdown = ({
|
|
|
13
13
|
dependenciesForPositionUpdating = [],
|
|
14
14
|
}: {
|
|
15
15
|
isOpen: boolean;
|
|
16
|
-
onDropdownClose: () => void;
|
|
17
16
|
referenceElement: VirtualElement | null | undefined;
|
|
18
17
|
dropdownElement: HTMLElement | null | undefined;
|
|
19
18
|
options?: IDropdownWithPopperOptions;
|
|
20
19
|
dependenciesForPositionUpdating?: DependencyList;
|
|
20
|
+
onDropdownClose(event: Event): void;
|
|
21
21
|
}): ReturnType<typeof usePopper> | undefined => {
|
|
22
22
|
const {
|
|
23
23
|
shouldUsePopper = false,
|