@snack-uikit/fields 0.14.2 → 0.15.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/CHANGELOG.md +16 -0
- package/README.md +15 -14
- package/dist/components/FieldDecorator/Header.js +1 -1
- package/dist/components/FieldDecorator/styles.module.css +7 -1
- package/dist/components/FieldSelect/FieldSelect.d.ts +1 -7
- package/dist/components/FieldSelect/FieldSelect.js +9 -21
- package/dist/components/FieldSelect/FieldSelectMultiple.d.ts +17 -0
- package/dist/components/FieldSelect/FieldSelectMultiple.js +118 -0
- package/dist/components/FieldSelect/FieldSelectSingle.d.ts +9 -28
- package/dist/components/FieldSelect/FieldSelectSingle.js +69 -55
- package/dist/components/FieldSelect/hooks.d.ts +30 -0
- package/dist/components/FieldSelect/hooks.js +72 -0
- package/dist/components/FieldSelect/index.d.ts +2 -1
- package/dist/components/FieldSelect/index.js +1 -1
- package/dist/components/FieldSelect/styles.module.css +129 -27
- package/dist/components/FieldSelect/types.d.ts +42 -37
- package/dist/components/FieldSelect/utils.d.ts +19 -0
- package/dist/components/FieldSelect/utils.js +112 -0
- package/dist/helperComponents/FieldContainerPrivate/styles.module.css +30 -6
- package/package.json +5 -3
- package/src/components/FieldDecorator/Header.tsx +6 -1
- package/src/components/FieldDecorator/styles.module.scss +38 -30
- package/src/components/FieldSelect/FieldSelect.tsx +13 -21
- package/src/components/FieldSelect/FieldSelectMultiple.tsx +255 -0
- package/src/components/FieldSelect/FieldSelectSingle.tsx +159 -99
- package/src/components/FieldSelect/hooks.ts +125 -0
- package/src/components/FieldSelect/index.ts +12 -1
- package/src/components/FieldSelect/styles.module.scss +71 -31
- package/src/components/FieldSelect/types.ts +55 -40
- package/src/components/FieldSelect/utils.ts +163 -0
- package/src/helperComponents/FieldContainerPrivate/FieldContainerPrivate.tsx +2 -0
- package/src/helperComponents/FieldContainerPrivate/styles.module.scss +32 -11
- package/dist/components/FieldSelect/FieldSelectBase.d.ts +0 -31
- package/dist/components/FieldSelect/FieldSelectBase.js +0 -51
- package/dist/components/FieldSelect/FieldSelectMulti.d.ts +0 -37
- package/dist/components/FieldSelect/FieldSelectMulti.js +0 -89
- package/dist/components/FieldSelect/constants.d.ts +0 -7
- package/dist/components/FieldSelect/constants.js +0 -6
- package/dist/components/FieldSelect/helpers/getArrowIcon.d.ts +0 -8
- package/dist/components/FieldSelect/helpers/getArrowIcon.js +0 -8
- package/dist/components/FieldSelect/helpers/getDisplayedValue.d.ts +0 -10
- package/dist/components/FieldSelect/helpers/getDisplayedValue.js +0 -12
- package/dist/components/FieldSelect/helpers/index.d.ts +0 -2
- package/dist/components/FieldSelect/helpers/index.js +0 -2
- package/dist/components/FieldSelect/hooks/index.d.ts +0 -3
- package/dist/components/FieldSelect/hooks/index.js +0 -3
- package/dist/components/FieldSelect/hooks/useFilteredOptions.d.ts +0 -7
- package/dist/components/FieldSelect/hooks/useFilteredOptions.js +0 -6
- package/dist/components/FieldSelect/hooks/useList.d.ts +0 -37
- package/dist/components/FieldSelect/hooks/useList.js +0 -52
- package/dist/components/FieldSelect/hooks/useListNavigation.d.ts +0 -26
- package/dist/components/FieldSelect/hooks/useListNavigation.js +0 -48
- package/src/components/FieldSelect/FieldSelectBase.tsx +0 -222
- package/src/components/FieldSelect/FieldSelectMulti.tsx +0 -163
- package/src/components/FieldSelect/constants.ts +0 -9
- package/src/components/FieldSelect/helpers/getArrowIcon.ts +0 -9
- package/src/components/FieldSelect/helpers/getDisplayedValue.ts +0 -25
- package/src/components/FieldSelect/helpers/index.ts +0 -2
- package/src/components/FieldSelect/hooks/index.ts +0 -3
- package/src/components/FieldSelect/hooks/useFilteredOptions.ts +0 -23
- package/src/components/FieldSelect/hooks/useList.ts +0 -87
- package/src/components/FieldSelect/hooks/useListNavigation.ts +0 -81
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { SELECTION_MODE } from '../constants';
|
|
2
|
-
import { Option } from '../types';
|
|
3
|
-
export declare const getDisplayedValue: (props: {
|
|
4
|
-
selectionMode: typeof SELECTION_MODE.Single;
|
|
5
|
-
selected: Option;
|
|
6
|
-
} | {
|
|
7
|
-
selectionMode: typeof SELECTION_MODE.Multi;
|
|
8
|
-
selected: Option[];
|
|
9
|
-
getSelectedItemsText(amount: number): string;
|
|
10
|
-
}) => string;
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { SELECTION_MODE } from '../constants';
|
|
2
|
-
export const getDisplayedValue = (props) => {
|
|
3
|
-
if (props.selectionMode === SELECTION_MODE.Single) {
|
|
4
|
-
return props.selected.label;
|
|
5
|
-
}
|
|
6
|
-
const { selected, getSelectedItemsText } = props;
|
|
7
|
-
const selectedOptions = selected;
|
|
8
|
-
if (selectedOptions.length > 1) {
|
|
9
|
-
return getSelectedItemsText(selectedOptions.length);
|
|
10
|
-
}
|
|
11
|
-
return selectedOptions.reduce((res, cur, index, arr) => `${res}${cur.label}${index === arr.length - 1 ? '' : ','}`, '');
|
|
12
|
-
};
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import { useMemo } from 'react';
|
|
2
|
-
export function useFilteredOptions({ searchable, touched, inputValue, options, }) {
|
|
3
|
-
return useMemo(() => searchable && touched && inputValue
|
|
4
|
-
? options.filter(item => item.label.toLowerCase().includes(inputValue.toLowerCase().trim()))
|
|
5
|
-
: options, [inputValue, options, searchable, touched]);
|
|
6
|
-
}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
/// <reference types="react" />
|
|
2
|
-
import { FieldSelectProps } from '../FieldSelect';
|
|
3
|
-
import { Option } from '../types';
|
|
4
|
-
type UseListProps = Pick<FieldSelectProps, 'readonly' | 'disabled' | 'open' | 'onOpenChange' | 'options' | 'showCopyButton'> & {
|
|
5
|
-
searchable: NonNullable<FieldSelectProps['searchable']>;
|
|
6
|
-
showAdditionalButton: boolean;
|
|
7
|
-
showClearButton: boolean;
|
|
8
|
-
inputValue: string;
|
|
9
|
-
setInputValue(value: string): void;
|
|
10
|
-
isChecked(option: Option): boolean;
|
|
11
|
-
};
|
|
12
|
-
export declare function useList({ open, onOpenChange, readonly, disabled, searchable, showAdditionalButton, showCopyButton: showCopyButtonProp, showClearButton: showClearButtonProp, inputValue, setInputValue, options, isChecked, }: UseListProps): {
|
|
13
|
-
isOpen: boolean;
|
|
14
|
-
setIsOpen: (value: boolean) => void;
|
|
15
|
-
localRef: import("react").RefObject<HTMLInputElement>;
|
|
16
|
-
clearButtonRef: import("react").RefObject<HTMLButtonElement>;
|
|
17
|
-
showClearButton: boolean;
|
|
18
|
-
copyButtonRef: import("react").RefObject<HTMLButtonElement>;
|
|
19
|
-
showCopyButton: boolean;
|
|
20
|
-
extendedOptions: {
|
|
21
|
-
checked: boolean;
|
|
22
|
-
disabled?: boolean | undefined;
|
|
23
|
-
caption?: string | undefined;
|
|
24
|
-
icon?: import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>> | undefined;
|
|
25
|
-
description?: string | undefined;
|
|
26
|
-
tagLabel?: string | undefined;
|
|
27
|
-
avatar?: Omit<import("@snack-uikit/avatar").AvatarProps, "size"> | undefined;
|
|
28
|
-
value: string;
|
|
29
|
-
label: string;
|
|
30
|
-
}[];
|
|
31
|
-
onInputKeyDown: import("react").KeyboardEventHandler<HTMLInputElement>;
|
|
32
|
-
onButtonKeyDown: (event: import("react").KeyboardEvent<Element>) => void;
|
|
33
|
-
onInputValueChange: (value: string) => void;
|
|
34
|
-
firstDroplistItemRefCallback: (element: HTMLButtonElement | null) => void;
|
|
35
|
-
onDroplistFocusLeave: (direction: string) => void;
|
|
36
|
-
};
|
|
37
|
-
export {};
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import { useEffect, useRef } from 'react';
|
|
2
|
-
import { useUncontrolledProp } from 'uncontrollable';
|
|
3
|
-
import { useFilteredOptions } from './useFilteredOptions';
|
|
4
|
-
import { useListNavigation } from './useListNavigation';
|
|
5
|
-
export function useList({ open, onOpenChange, readonly, disabled, searchable, showAdditionalButton, showCopyButton: showCopyButtonProp, showClearButton: showClearButtonProp, inputValue, setInputValue, options, isChecked, }) {
|
|
6
|
-
const touched = useRef(false);
|
|
7
|
-
const localRef = useRef(null);
|
|
8
|
-
const clearButtonRef = useRef(null);
|
|
9
|
-
const copyButtonRef = useRef(null);
|
|
10
|
-
const [isOpen, setIsOpen] = useUncontrolledProp(open, false, onOpenChange);
|
|
11
|
-
const showDropList = isOpen && !readonly && !disabled;
|
|
12
|
-
const showClearButton = showClearButtonProp && !readonly && (showAdditionalButton || inputValue.length > 0);
|
|
13
|
-
const showCopyButton = Boolean(showAdditionalButton && showCopyButtonProp && readonly);
|
|
14
|
-
const { extendedOptions, onInputKeyDown, onButtonKeyDown, onDroplistFocusLeave, firstDroplistItemRefCallback } = useListNavigation({
|
|
15
|
-
inputRef: localRef,
|
|
16
|
-
options: useFilteredOptions({ searchable, options, touched: touched.current, inputValue }),
|
|
17
|
-
toggleListOpen: setIsOpen,
|
|
18
|
-
isChecked,
|
|
19
|
-
});
|
|
20
|
-
const handleOpenChange = (value) => {
|
|
21
|
-
if (!value) {
|
|
22
|
-
touched.current = false;
|
|
23
|
-
}
|
|
24
|
-
setIsOpen(value);
|
|
25
|
-
};
|
|
26
|
-
const handleInputValueChange = (value) => {
|
|
27
|
-
touched.current = true;
|
|
28
|
-
setIsOpen(true);
|
|
29
|
-
setInputValue(value);
|
|
30
|
-
};
|
|
31
|
-
useEffect(() => {
|
|
32
|
-
var _a;
|
|
33
|
-
if (open) {
|
|
34
|
-
(_a = localRef.current) === null || _a === void 0 ? void 0 : _a.focus();
|
|
35
|
-
}
|
|
36
|
-
}, [localRef, open]);
|
|
37
|
-
return {
|
|
38
|
-
isOpen: showDropList,
|
|
39
|
-
setIsOpen: handleOpenChange,
|
|
40
|
-
localRef,
|
|
41
|
-
clearButtonRef,
|
|
42
|
-
showClearButton,
|
|
43
|
-
copyButtonRef,
|
|
44
|
-
showCopyButton,
|
|
45
|
-
extendedOptions,
|
|
46
|
-
onInputKeyDown,
|
|
47
|
-
onButtonKeyDown,
|
|
48
|
-
onInputValueChange: handleInputValueChange,
|
|
49
|
-
firstDroplistItemRefCallback,
|
|
50
|
-
onDroplistFocusLeave,
|
|
51
|
-
};
|
|
52
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { KeyboardEvent, KeyboardEventHandler, RefObject } from 'react';
|
|
2
|
-
import { Option } from '../types';
|
|
3
|
-
type UseListNavigationProps = {
|
|
4
|
-
options: Option[];
|
|
5
|
-
toggleListOpen(value: boolean): void;
|
|
6
|
-
inputRef: RefObject<HTMLInputElement>;
|
|
7
|
-
isChecked(option: Option): boolean;
|
|
8
|
-
};
|
|
9
|
-
export declare function useListNavigation({ options, toggleListOpen, inputRef, isChecked }: UseListNavigationProps): {
|
|
10
|
-
onInputKeyDown: KeyboardEventHandler<HTMLInputElement>;
|
|
11
|
-
onButtonKeyDown: (event: KeyboardEvent) => void;
|
|
12
|
-
onDroplistFocusLeave: (direction: string) => void;
|
|
13
|
-
extendedOptions: {
|
|
14
|
-
checked: boolean;
|
|
15
|
-
disabled?: boolean | undefined;
|
|
16
|
-
caption?: string | undefined;
|
|
17
|
-
icon?: import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>> | undefined;
|
|
18
|
-
description?: string | undefined;
|
|
19
|
-
tagLabel?: string | undefined;
|
|
20
|
-
avatar?: Omit<import("@snack-uikit/avatar").AvatarProps, "size"> | undefined;
|
|
21
|
-
value: string;
|
|
22
|
-
label: string;
|
|
23
|
-
}[];
|
|
24
|
-
firstDroplistItemRefCallback: (element: HTMLButtonElement | null) => void;
|
|
25
|
-
};
|
|
26
|
-
export {};
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import { useCallback, useMemo, useRef } from 'react';
|
|
2
|
-
import { moveCursorToEnd, runAfterRerender } from '@snack-uikit/input-private';
|
|
3
|
-
export function useListNavigation({ options, toggleListOpen, inputRef, isChecked }) {
|
|
4
|
-
const setInputFocus = useCallback(() => {
|
|
5
|
-
runAfterRerender(() => {
|
|
6
|
-
var _a;
|
|
7
|
-
(_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus();
|
|
8
|
-
moveCursorToEnd(inputRef.current);
|
|
9
|
-
});
|
|
10
|
-
}, [inputRef]);
|
|
11
|
-
const firstDroplistItemRef = useRef(null);
|
|
12
|
-
const firstDroplistItemRefCallback = useCallback((element) => {
|
|
13
|
-
firstDroplistItemRef.current = element;
|
|
14
|
-
}, [firstDroplistItemRef]);
|
|
15
|
-
const setOptionFocus = useCallback(() => {
|
|
16
|
-
runAfterRerender(() => { var _a; return (_a = firstDroplistItemRef.current) === null || _a === void 0 ? void 0 : _a.focus(); });
|
|
17
|
-
}, [firstDroplistItemRef]);
|
|
18
|
-
const leaveElement = (event) => {
|
|
19
|
-
if (event.key === 'ArrowDown') {
|
|
20
|
-
event.preventDefault();
|
|
21
|
-
toggleListOpen(true);
|
|
22
|
-
setOptionFocus();
|
|
23
|
-
}
|
|
24
|
-
if (event.key === 'Tab') {
|
|
25
|
-
toggleListOpen(false);
|
|
26
|
-
}
|
|
27
|
-
};
|
|
28
|
-
const handleInputKeyDown = event => {
|
|
29
|
-
leaveElement(event);
|
|
30
|
-
if (event.key === 'Enter') {
|
|
31
|
-
toggleListOpen(true);
|
|
32
|
-
setOptionFocus();
|
|
33
|
-
}
|
|
34
|
-
};
|
|
35
|
-
const onDroplistFocusLeave = useCallback((direction) => {
|
|
36
|
-
if (['top', 'common'].includes(direction)) {
|
|
37
|
-
setInputFocus();
|
|
38
|
-
}
|
|
39
|
-
}, [setInputFocus]);
|
|
40
|
-
const extendedOptions = useMemo(() => options.map(option => (Object.assign(Object.assign({}, option), { checked: isChecked(option) }))), [isChecked, options]);
|
|
41
|
-
return {
|
|
42
|
-
onInputKeyDown: handleInputKeyDown,
|
|
43
|
-
onButtonKeyDown: leaveElement,
|
|
44
|
-
onDroplistFocusLeave,
|
|
45
|
-
extendedOptions,
|
|
46
|
-
firstDroplistItemRefCallback,
|
|
47
|
-
};
|
|
48
|
-
}
|
|
@@ -1,222 +0,0 @@
|
|
|
1
|
-
import mergeRefs from 'merge-refs';
|
|
2
|
-
import {
|
|
3
|
-
forwardRef,
|
|
4
|
-
KeyboardEvent,
|
|
5
|
-
KeyboardEventHandler,
|
|
6
|
-
MouseEventHandler,
|
|
7
|
-
RefCallback,
|
|
8
|
-
RefObject,
|
|
9
|
-
useMemo,
|
|
10
|
-
} from 'react';
|
|
11
|
-
|
|
12
|
-
import { Droplist } from '@snack-uikit/droplist';
|
|
13
|
-
import { InputPrivate, SIZE, useButtonNavigation, useClearButton } from '@snack-uikit/input-private';
|
|
14
|
-
import { extractSupportProps } from '@snack-uikit/utils';
|
|
15
|
-
|
|
16
|
-
import { CONTAINER_VARIANT, VALIDATION_STATE } from '../../constants';
|
|
17
|
-
import { FieldContainerPrivate } from '../../helperComponents';
|
|
18
|
-
import { useCopyButton } from '../../hooks';
|
|
19
|
-
import { useHandlers } from '../FieldDate/hooks/useHandlers';
|
|
20
|
-
import { FieldDecorator } from '../FieldDecorator';
|
|
21
|
-
import { SELECTION_MODE } from './constants';
|
|
22
|
-
import { getArrowIcon } from './helpers';
|
|
23
|
-
import styles from './styles.module.scss';
|
|
24
|
-
import { ExtendedOption, FieldSelectBaseProps, Option } from './types';
|
|
25
|
-
|
|
26
|
-
type BaseProps = Omit<FieldSelectBaseProps, 'open' | 'onOpenChange' | 'options'> &
|
|
27
|
-
Required<Pick<FieldSelectBaseProps, 'open' | 'onOpenChange'>> & {
|
|
28
|
-
localRef: RefObject<HTMLInputElement>;
|
|
29
|
-
options: ExtendedOption[];
|
|
30
|
-
onClear: MouseEventHandler<HTMLButtonElement>;
|
|
31
|
-
onChange(option: Option): () => void;
|
|
32
|
-
inputValue: string;
|
|
33
|
-
onInputValueChange(value: string): void;
|
|
34
|
-
displayedValue?: string;
|
|
35
|
-
valueToCopy: string;
|
|
36
|
-
onInputKeyDown: KeyboardEventHandler<HTMLInputElement>;
|
|
37
|
-
onButtonKeyDown: KeyboardEventHandler<HTMLButtonElement>;
|
|
38
|
-
showClearButton: boolean;
|
|
39
|
-
clearButtonRef: RefObject<HTMLButtonElement>;
|
|
40
|
-
copyButtonRef: RefObject<HTMLButtonElement>;
|
|
41
|
-
onClick?: MouseEventHandler<HTMLElement>;
|
|
42
|
-
onContainerPrivateMouseDown?: MouseEventHandler<HTMLElement>;
|
|
43
|
-
onDroplistFocusLeave(direction: string): void;
|
|
44
|
-
firstDroplistItemRefCallback: RefCallback<HTMLButtonElement>;
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
type Props =
|
|
48
|
-
| ({
|
|
49
|
-
selectionMode: typeof SELECTION_MODE.Single;
|
|
50
|
-
selected: Option;
|
|
51
|
-
} & BaseProps)
|
|
52
|
-
| ({
|
|
53
|
-
selectionMode: typeof SELECTION_MODE.Multi;
|
|
54
|
-
selected: Option[];
|
|
55
|
-
} & BaseProps);
|
|
56
|
-
|
|
57
|
-
export const FieldSelectBase = forwardRef<HTMLInputElement, Props>(
|
|
58
|
-
(
|
|
59
|
-
{
|
|
60
|
-
selectionMode,
|
|
61
|
-
onClear,
|
|
62
|
-
showClearButton,
|
|
63
|
-
clearButtonRef,
|
|
64
|
-
copyButtonRef,
|
|
65
|
-
onChange,
|
|
66
|
-
inputValue,
|
|
67
|
-
onInputValueChange,
|
|
68
|
-
displayedValue,
|
|
69
|
-
valueToCopy,
|
|
70
|
-
localRef,
|
|
71
|
-
onInputKeyDown: onInputKeyDownProp,
|
|
72
|
-
onButtonKeyDown: onButtonKeyDownProp,
|
|
73
|
-
id,
|
|
74
|
-
name,
|
|
75
|
-
placeholder,
|
|
76
|
-
options,
|
|
77
|
-
disabled = false,
|
|
78
|
-
readonly = false,
|
|
79
|
-
searchable = true,
|
|
80
|
-
showCopyButton = true,
|
|
81
|
-
open,
|
|
82
|
-
onOpenChange,
|
|
83
|
-
onFocus,
|
|
84
|
-
onBlur,
|
|
85
|
-
onContainerPrivateMouseDown,
|
|
86
|
-
className,
|
|
87
|
-
label,
|
|
88
|
-
labelTooltip,
|
|
89
|
-
labelTooltipPlacement,
|
|
90
|
-
required = false,
|
|
91
|
-
hint,
|
|
92
|
-
showHintIcon,
|
|
93
|
-
size = SIZE.S,
|
|
94
|
-
validationState = VALIDATION_STATE.Default,
|
|
95
|
-
prefixIcon,
|
|
96
|
-
locale,
|
|
97
|
-
noDataText = locale?.language === 'ru' ? 'Нет данных' : 'No data',
|
|
98
|
-
firstDroplistItemRefCallback,
|
|
99
|
-
onDroplistFocusLeave,
|
|
100
|
-
...rest
|
|
101
|
-
},
|
|
102
|
-
ref,
|
|
103
|
-
) => {
|
|
104
|
-
const { ArrowIcon, arrowIconSize } = getArrowIcon({ size, open });
|
|
105
|
-
const Item = selectionMode === SELECTION_MODE.Single ? Droplist.ItemSingle : Droplist.ItemMultiple;
|
|
106
|
-
|
|
107
|
-
const clearButtonSettings = useClearButton({ clearButtonRef, showClearButton, size, onClear });
|
|
108
|
-
const copyButtonSettings = useCopyButton({ copyButtonRef, showCopyButton, size, valueToCopy });
|
|
109
|
-
const {
|
|
110
|
-
onInputKeyDown: inputKeyDownNavigationHandler,
|
|
111
|
-
inputTabIndex,
|
|
112
|
-
setInitialTabIndices,
|
|
113
|
-
buttons,
|
|
114
|
-
} = useButtonNavigation({
|
|
115
|
-
inputRef: localRef,
|
|
116
|
-
buttons: useMemo(() => [clearButtonSettings, copyButtonSettings], [clearButtonSettings, copyButtonSettings]),
|
|
117
|
-
onButtonKeyDown: onButtonKeyDownProp,
|
|
118
|
-
readonly,
|
|
119
|
-
submitKeys: ['Enter', 'Space', 'Tab'],
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
const onInputKeyDownHandler = useHandlers<KeyboardEvent<HTMLInputElement>>([
|
|
123
|
-
inputKeyDownNavigationHandler,
|
|
124
|
-
onInputKeyDownProp,
|
|
125
|
-
]);
|
|
126
|
-
|
|
127
|
-
const onFocusLeaveHandler = useHandlers([
|
|
128
|
-
(direction: string) => {
|
|
129
|
-
if (direction === 'common') setInitialTabIndices();
|
|
130
|
-
},
|
|
131
|
-
onDroplistFocusLeave,
|
|
132
|
-
]);
|
|
133
|
-
|
|
134
|
-
return (
|
|
135
|
-
<FieldDecorator
|
|
136
|
-
className={className}
|
|
137
|
-
label={label}
|
|
138
|
-
labelTooltip={labelTooltip}
|
|
139
|
-
labelTooltipPlacement={labelTooltipPlacement}
|
|
140
|
-
labelFor={id}
|
|
141
|
-
required={required}
|
|
142
|
-
hint={hint}
|
|
143
|
-
disabled={disabled}
|
|
144
|
-
readonly={readonly}
|
|
145
|
-
showHintIcon={showHintIcon}
|
|
146
|
-
size={size}
|
|
147
|
-
validationState={validationState}
|
|
148
|
-
{...extractSupportProps(rest)}
|
|
149
|
-
>
|
|
150
|
-
<Droplist
|
|
151
|
-
trigger='click'
|
|
152
|
-
triggerClassName={styles.triggerClassName}
|
|
153
|
-
triggerClickByKeys={!searchable}
|
|
154
|
-
placement='bottom'
|
|
155
|
-
onFocusLeave={onFocusLeaveHandler}
|
|
156
|
-
firstElementRefCallback={firstDroplistItemRefCallback}
|
|
157
|
-
data-test-id='field-select__list'
|
|
158
|
-
{...(readonly || disabled ? { open: false } : { open, onOpenChange })}
|
|
159
|
-
triggerElement={
|
|
160
|
-
<FieldContainerPrivate
|
|
161
|
-
className={styles.container}
|
|
162
|
-
size={size}
|
|
163
|
-
validationState={validationState}
|
|
164
|
-
disabled={disabled}
|
|
165
|
-
readonly={readonly}
|
|
166
|
-
variant={CONTAINER_VARIANT.SingleLine}
|
|
167
|
-
focused={open}
|
|
168
|
-
selectable={!searchable}
|
|
169
|
-
inputRef={localRef}
|
|
170
|
-
prefix={prefixIcon}
|
|
171
|
-
onMouseDown={onContainerPrivateMouseDown}
|
|
172
|
-
postfix={
|
|
173
|
-
<>
|
|
174
|
-
{buttons}
|
|
175
|
-
<ArrowIcon size={arrowIconSize} className={styles.arrowIcon} />
|
|
176
|
-
</>
|
|
177
|
-
}
|
|
178
|
-
>
|
|
179
|
-
<InputPrivate
|
|
180
|
-
id={id}
|
|
181
|
-
name={name}
|
|
182
|
-
type='text'
|
|
183
|
-
placeholder={displayedValue ? undefined : placeholder}
|
|
184
|
-
ref={mergeRefs(ref, localRef)}
|
|
185
|
-
onFocus={onFocus}
|
|
186
|
-
onBlur={onBlur}
|
|
187
|
-
disabled={disabled}
|
|
188
|
-
data-size={size}
|
|
189
|
-
data-test-id='field-select__input'
|
|
190
|
-
onKeyDown={onInputKeyDownHandler}
|
|
191
|
-
onChange={searchable ? onInputValueChange : undefined}
|
|
192
|
-
readonly={!searchable || readonly}
|
|
193
|
-
value={inputValue}
|
|
194
|
-
tabIndex={inputTabIndex}
|
|
195
|
-
/>
|
|
196
|
-
|
|
197
|
-
{displayedValue && (
|
|
198
|
-
<span className={styles.displayValue} data-test-id='field-select__displayed-value'>
|
|
199
|
-
{displayedValue}
|
|
200
|
-
</span>
|
|
201
|
-
)}
|
|
202
|
-
</FieldContainerPrivate>
|
|
203
|
-
}
|
|
204
|
-
>
|
|
205
|
-
{options.length === 0 ? (
|
|
206
|
-
<Droplist.NoData size={size} label={noDataText} data-test-id='field-select__no-data' />
|
|
207
|
-
) : (
|
|
208
|
-
options.map(option => (
|
|
209
|
-
<Item
|
|
210
|
-
onChange={onChange(option)}
|
|
211
|
-
key={option.value}
|
|
212
|
-
data-test-id={'field-select__list-option-' + option.value}
|
|
213
|
-
{...option}
|
|
214
|
-
option={option.label}
|
|
215
|
-
/>
|
|
216
|
-
))
|
|
217
|
-
)}
|
|
218
|
-
</Droplist>
|
|
219
|
-
</FieldDecorator>
|
|
220
|
-
);
|
|
221
|
-
},
|
|
222
|
-
);
|
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
import { FocusEventHandler, forwardRef, MouseEventHandler, useCallback, useMemo, useRef, useState } from 'react';
|
|
2
|
-
import { useUncontrolledProp } from 'uncontrollable';
|
|
3
|
-
|
|
4
|
-
import { DEFAULT_LOCALE, SELECTION_MODE } from './constants';
|
|
5
|
-
import { FieldSelectBase } from './FieldSelectBase';
|
|
6
|
-
import { getDisplayedValue } from './helpers';
|
|
7
|
-
import { useList } from './hooks';
|
|
8
|
-
import { FieldSelectMultiProps, Option } from './types';
|
|
9
|
-
|
|
10
|
-
export const FieldSelectMulti = forwardRef<HTMLInputElement, FieldSelectMultiProps>(
|
|
11
|
-
(
|
|
12
|
-
{
|
|
13
|
-
value: valueProp,
|
|
14
|
-
onChange,
|
|
15
|
-
options,
|
|
16
|
-
disabled = false,
|
|
17
|
-
readonly = false,
|
|
18
|
-
searchable = true,
|
|
19
|
-
required = false,
|
|
20
|
-
open,
|
|
21
|
-
onOpenChange,
|
|
22
|
-
locale = DEFAULT_LOCALE,
|
|
23
|
-
getSelectedItemsText = number => (locale?.language === 'ru' ? `Выбрано: ${number}` : `Selected: ${number}`),
|
|
24
|
-
showCopyButton: showCopyButtonProp = true,
|
|
25
|
-
showClearButton: showClearButtonProp = true,
|
|
26
|
-
onFocus,
|
|
27
|
-
onBlur,
|
|
28
|
-
...rest
|
|
29
|
-
},
|
|
30
|
-
ref,
|
|
31
|
-
) => {
|
|
32
|
-
const selectionMode = SELECTION_MODE.Multi;
|
|
33
|
-
const [value, setValue] = useUncontrolledProp(valueProp, [], onChange);
|
|
34
|
-
const selected = useMemo(() => options.filter(option => value?.includes(option.value)), [options, value]);
|
|
35
|
-
const displayedValue = getDisplayedValue({ selectionMode, selected, getSelectedItemsText });
|
|
36
|
-
const valueToCopy = selected.map(op => op.label).join(', ');
|
|
37
|
-
const [inputValue, setInputValue] = useState('');
|
|
38
|
-
const showAdditionalButton = Boolean(value?.length && value.length > 0 && !disabled);
|
|
39
|
-
const isChecked = useCallback(
|
|
40
|
-
(option: Option) => Boolean(selected.find(op => op.value === option.value)),
|
|
41
|
-
[selected],
|
|
42
|
-
);
|
|
43
|
-
const [isFocused, setIsFocused] = useState(false);
|
|
44
|
-
const stayOpen = useRef(false);
|
|
45
|
-
const waitingForSearchStart = searchable && !isFocused;
|
|
46
|
-
const showDisplayValue = displayedValue && (!searchable || waitingForSearchStart);
|
|
47
|
-
|
|
48
|
-
const {
|
|
49
|
-
isOpen,
|
|
50
|
-
setIsOpen,
|
|
51
|
-
localRef,
|
|
52
|
-
extendedOptions,
|
|
53
|
-
onInputKeyDown,
|
|
54
|
-
onInputValueChange,
|
|
55
|
-
onButtonKeyDown,
|
|
56
|
-
clearButtonRef,
|
|
57
|
-
copyButtonRef,
|
|
58
|
-
showClearButton,
|
|
59
|
-
showCopyButton,
|
|
60
|
-
onDroplistFocusLeave,
|
|
61
|
-
firstDroplistItemRefCallback,
|
|
62
|
-
} = useList({
|
|
63
|
-
open,
|
|
64
|
-
onOpenChange,
|
|
65
|
-
disabled,
|
|
66
|
-
readonly,
|
|
67
|
-
inputValue,
|
|
68
|
-
setInputValue,
|
|
69
|
-
searchable,
|
|
70
|
-
options,
|
|
71
|
-
isChecked,
|
|
72
|
-
showAdditionalButton,
|
|
73
|
-
showCopyButton: showCopyButtonProp,
|
|
74
|
-
showClearButton: showClearButtonProp,
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
const handleOpenChange = (isOpen: boolean) => {
|
|
78
|
-
if (stayOpen.current) {
|
|
79
|
-
stayOpen.current = false;
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
setInputValue('');
|
|
84
|
-
setIsOpen(isOpen);
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
const handlePreventListClose: MouseEventHandler<HTMLElement> = event => {
|
|
88
|
-
event.preventDefault();
|
|
89
|
-
|
|
90
|
-
if (isOpen && waitingForSearchStart) {
|
|
91
|
-
stayOpen.current = true;
|
|
92
|
-
}
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
const handleClear = () => {
|
|
96
|
-
setValue([]);
|
|
97
|
-
setInputValue('');
|
|
98
|
-
stayOpen.current = false;
|
|
99
|
-
|
|
100
|
-
if (required) {
|
|
101
|
-
localRef.current?.focus();
|
|
102
|
-
setIsOpen(true);
|
|
103
|
-
} else {
|
|
104
|
-
localRef.current?.blur();
|
|
105
|
-
setIsOpen(false);
|
|
106
|
-
}
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
const handleChange = (option: Option) => () => {
|
|
110
|
-
setValue(
|
|
111
|
-
(selected.find(op => op.value === option.value)
|
|
112
|
-
? selected.filter(op => op.value !== option.value)
|
|
113
|
-
: [...selected, option]
|
|
114
|
-
).map(op => op.value),
|
|
115
|
-
);
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
const handleFocus: FocusEventHandler<HTMLInputElement> = event => {
|
|
119
|
-
setIsFocused(true);
|
|
120
|
-
onFocus?.(event);
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
const handleBlur: FocusEventHandler<HTMLInputElement> = event => {
|
|
124
|
-
setIsFocused(false);
|
|
125
|
-
onBlur?.(event);
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
return (
|
|
129
|
-
<FieldSelectBase
|
|
130
|
-
{...rest}
|
|
131
|
-
ref={ref}
|
|
132
|
-
localRef={localRef}
|
|
133
|
-
selectionMode={selectionMode}
|
|
134
|
-
options={extendedOptions}
|
|
135
|
-
selected={selected}
|
|
136
|
-
disabled={disabled}
|
|
137
|
-
readonly={readonly}
|
|
138
|
-
required={required}
|
|
139
|
-
searchable={searchable}
|
|
140
|
-
onChange={handleChange}
|
|
141
|
-
onClear={handleClear}
|
|
142
|
-
displayedValue={showDisplayValue ? displayedValue : ''}
|
|
143
|
-
valueToCopy={valueToCopy}
|
|
144
|
-
inputValue={inputValue}
|
|
145
|
-
onInputValueChange={onInputValueChange}
|
|
146
|
-
onInputKeyDown={onInputKeyDown}
|
|
147
|
-
clearButtonRef={clearButtonRef}
|
|
148
|
-
copyButtonRef={copyButtonRef}
|
|
149
|
-
onButtonKeyDown={onButtonKeyDown}
|
|
150
|
-
open={isOpen}
|
|
151
|
-
onOpenChange={handleOpenChange}
|
|
152
|
-
locale={locale}
|
|
153
|
-
showCopyButton={showCopyButton}
|
|
154
|
-
showClearButton={showClearButton}
|
|
155
|
-
onFocus={handleFocus}
|
|
156
|
-
onBlur={handleBlur}
|
|
157
|
-
onContainerPrivateMouseDown={handlePreventListClose}
|
|
158
|
-
onDroplistFocusLeave={onDroplistFocusLeave}
|
|
159
|
-
firstDroplistItemRefCallback={firstDroplistItemRefCallback}
|
|
160
|
-
/>
|
|
161
|
-
);
|
|
162
|
-
},
|
|
163
|
-
);
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { ChevronDownSVG, ChevronUpSVG } from '@snack-uikit/icons';
|
|
2
|
-
import { ICON_SIZE, SIZE, Size } from '@snack-uikit/input-private';
|
|
3
|
-
|
|
4
|
-
export function getArrowIcon({ size, open }: { size: Size; open: boolean }) {
|
|
5
|
-
return {
|
|
6
|
-
ArrowIcon: open ? ChevronUpSVG : ChevronDownSVG,
|
|
7
|
-
arrowIconSize: size === SIZE.S ? ICON_SIZE.Xs : ICON_SIZE.S,
|
|
8
|
-
};
|
|
9
|
-
}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { SELECTION_MODE } from '../constants';
|
|
2
|
-
import { Option } from '../types';
|
|
3
|
-
|
|
4
|
-
export const getDisplayedValue = (
|
|
5
|
-
props:
|
|
6
|
-
| { selectionMode: typeof SELECTION_MODE.Single; selected: Option }
|
|
7
|
-
| { selectionMode: typeof SELECTION_MODE.Multi; selected: Option[]; getSelectedItemsText(amount: number): string },
|
|
8
|
-
) => {
|
|
9
|
-
if (props.selectionMode === SELECTION_MODE.Single) {
|
|
10
|
-
return props.selected.label;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const { selected, getSelectedItemsText } = props;
|
|
14
|
-
|
|
15
|
-
const selectedOptions = selected;
|
|
16
|
-
|
|
17
|
-
if (selectedOptions.length > 1) {
|
|
18
|
-
return getSelectedItemsText(selectedOptions.length);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
return selectedOptions.reduce(
|
|
22
|
-
(res, cur, index, arr) => `${res}${cur.label}${index === arr.length - 1 ? '' : ','}`,
|
|
23
|
-
'',
|
|
24
|
-
);
|
|
25
|
-
};
|