@cloud-ru/uikit-product-mobile-fields 0.11.24
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 +1300 -0
- package/LICENSE +201 -0
- package/README.md +8 -0
- package/package.json +66 -0
- package/src/components/AdaptiveField/AdaptiveField.tsx +88 -0
- package/src/components/AdaptiveField/index.ts +1 -0
- package/src/components/MobileFieldDate/MobileFieldDate.tsx +375 -0
- package/src/components/MobileFieldDate/constants.ts +33 -0
- package/src/components/MobileFieldDate/index.ts +2 -0
- package/src/components/MobileFieldDate/styles.module.scss +75 -0
- package/src/components/MobileFieldDate/types.ts +6 -0
- package/src/components/MobileFieldDate/utils.ts +49 -0
- package/src/components/MobileFieldSelect/MobileFieldSelect.tsx +18 -0
- package/src/components/MobileFieldSelect/MobileFieldSelectMultiple.tsx +331 -0
- package/src/components/MobileFieldSelect/MobileFieldSelectSingle.tsx +300 -0
- package/src/components/MobileFieldSelect/hooks.tsx +195 -0
- package/src/components/MobileFieldSelect/index.ts +13 -0
- package/src/components/MobileFieldSelect/legacy/components/Items/hooks.tsx +53 -0
- package/src/components/MobileFieldSelect/legacy/components/index.ts +1 -0
- package/src/components/MobileFieldSelect/legacy/hooks.ts +38 -0
- package/src/components/MobileFieldSelect/legacy/index.ts +3 -0
- package/src/components/MobileFieldSelect/legacy/utils.ts +176 -0
- package/src/components/MobileFieldSelect/styles.module.scss +176 -0
- package/src/components/MobileFieldSelect/types.ts +156 -0
- package/src/components/MobileFieldSelect/utils/extractFieldDecoratorProps.ts +35 -0
- package/src/components/MobileFieldSelect/utils/extractListProps.ts +30 -0
- package/src/components/MobileFieldSelect/utils/getArrowIcon.ts +15 -0
- package/src/components/MobileFieldSelect/utils/index.ts +6 -0
- package/src/components/MobileFieldSelect/utils/options.tsx +88 -0
- package/src/components/MobileFieldSelect/utils/typeGuards.ts +38 -0
- package/src/components/MobileFieldSelect/utils/updateItems.ts +121 -0
- package/src/components/index.ts +3 -0
- package/src/constants/allFields.ts +11 -0
- package/src/constants/dateFields.ts +127 -0
- package/src/constants/index.ts +2 -0
- package/src/helperComponents/ButtonCopyValue/ButtonCopyValue.tsx +79 -0
- package/src/helperComponents/ButtonCopyValue/helpers.tsx +19 -0
- package/src/helperComponents/ButtonCopyValue/index.ts +1 -0
- package/src/helperComponents/ButtonCopyValue/styles.module.scss +5 -0
- package/src/helperComponents/FieldContainerPrivate/FieldContainerPrivate.tsx +79 -0
- package/src/helperComponents/FieldContainerPrivate/index.ts +1 -0
- package/src/helperComponents/FieldContainerPrivate/styles.module.scss +131 -0
- package/src/helperComponents/ItemContent/ItemContent.tsx +37 -0
- package/src/helperComponents/ItemContent/index.ts +1 -0
- package/src/helperComponents/ItemContent/styles.module.scss +80 -0
- package/src/helperComponents/index.ts +3 -0
- package/src/hooks/dateHandlers/index.ts +3 -0
- package/src/hooks/dateHandlers/useDateField.ts +275 -0
- package/src/hooks/dateHandlers/useDateFieldHelpersForMode.ts +145 -0
- package/src/hooks/dateHandlers/useFocusHandlers.ts +46 -0
- package/src/hooks/dateHandlers/useHandlers.ts +15 -0
- package/src/hooks/index.ts +5 -0
- package/src/hooks/styles.module.scss +17 -0
- package/src/hooks/useCopyButton.tsx +47 -0
- package/src/hooks/usePostfix.tsx +21 -0
- package/src/hooks/usePrefix.tsx +21 -0
- package/src/hooks/useValueControl.ts +15 -0
- package/src/index.ts +3 -0
- package/src/styles.module.scss +55 -0
- package/src/types/allFields.ts +9 -0
- package/src/types/dateFields.ts +14 -0
- package/src/types/index.ts +2 -0
- package/src/utils/adaptiveField.tsx +19 -0
- package/src/utils/dateFields.ts +75 -0
- package/src/utils/getValidationState.ts +6 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { ReactElement, ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
import { FieldDecoratorProps } from '@snack-uikit/fields';
|
|
4
|
+
import { InputPrivateProps } from '@snack-uikit/input-private';
|
|
5
|
+
import {
|
|
6
|
+
AccordionItemProps,
|
|
7
|
+
BaseItemProps,
|
|
8
|
+
DroplistProps,
|
|
9
|
+
GroupItemProps,
|
|
10
|
+
ItemContentProps,
|
|
11
|
+
NextListItemProps,
|
|
12
|
+
SelectionMultipleState,
|
|
13
|
+
SelectionSingleState,
|
|
14
|
+
} from '@snack-uikit/list';
|
|
15
|
+
import { TagProps } from '@snack-uikit/tag';
|
|
16
|
+
import { WithSupportProps } from '@snack-uikit/utils';
|
|
17
|
+
|
|
18
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
19
|
+
export type AnyType = any;
|
|
20
|
+
|
|
21
|
+
export type OptionProps =
|
|
22
|
+
// eslint-disable-next-line no-use-before-define
|
|
23
|
+
| BaseOptionProps
|
|
24
|
+
// eslint-disable-next-line no-use-before-define
|
|
25
|
+
| AccordionOptionProps
|
|
26
|
+
// eslint-disable-next-line no-use-before-define
|
|
27
|
+
| GroupOptionProps
|
|
28
|
+
// eslint-disable-next-line no-use-before-define
|
|
29
|
+
| NestListOptionProps;
|
|
30
|
+
|
|
31
|
+
// eslint-disable-next-line no-use-before-define
|
|
32
|
+
export type OptionWithoutGroup = BaseOptionProps | AccordionOptionProps | NestListOptionProps;
|
|
33
|
+
|
|
34
|
+
export type BaseOptionProps = Pick<BaseItemProps, 'beforeContent' | 'afterContent' | 'disabled'> &
|
|
35
|
+
Pick<ItemContentProps, 'option' | 'caption' | 'description'> & { value: string | number } & Pick<
|
|
36
|
+
TagProps,
|
|
37
|
+
'appearance'
|
|
38
|
+
>;
|
|
39
|
+
|
|
40
|
+
export type AccordionOptionProps = Pick<AccordionItemProps, 'type'> & BaseOptionProps & { options: OptionProps[] };
|
|
41
|
+
|
|
42
|
+
export type GroupOptionProps = Omit<GroupItemProps, 'items' | 'id'> & {
|
|
43
|
+
options: OptionProps[];
|
|
44
|
+
};
|
|
45
|
+
export type NestListOptionProps = Pick<NextListItemProps, 'type' | 'onSublistOpenChanged' | 'id'> &
|
|
46
|
+
BaseOptionProps & { options: OptionProps[] };
|
|
47
|
+
|
|
48
|
+
export type InputProps = Pick<
|
|
49
|
+
InputPrivateProps,
|
|
50
|
+
'id' | 'name' | 'placeholder' | 'disabled' | 'readonly' | 'onFocus' | 'onBlur' | 'onKeyDown' | 'autoFocus'
|
|
51
|
+
>;
|
|
52
|
+
|
|
53
|
+
export type WrapperProps = Pick<
|
|
54
|
+
FieldDecoratorProps,
|
|
55
|
+
| 'className'
|
|
56
|
+
| 'label'
|
|
57
|
+
| 'labelTooltip'
|
|
58
|
+
| 'required'
|
|
59
|
+
| 'caption'
|
|
60
|
+
| 'hint'
|
|
61
|
+
| 'showHintIcon'
|
|
62
|
+
| 'size'
|
|
63
|
+
| 'validationState'
|
|
64
|
+
| 'labelTooltipPlacement'
|
|
65
|
+
| 'error'
|
|
66
|
+
>;
|
|
67
|
+
|
|
68
|
+
export type ItemWithId = (BaseItemProps | AccordionItemProps | NextListItemProps) & {
|
|
69
|
+
placeholder?: boolean;
|
|
70
|
+
appearance?: TagProps['appearance'];
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export type SelectedOptionFormatter = (item?: ItemWithId) => string;
|
|
74
|
+
|
|
75
|
+
export type SearchState = {
|
|
76
|
+
value?: string;
|
|
77
|
+
defaultValue?: string;
|
|
78
|
+
onChange?(value: string): void;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export type FieldSelectPrivateProps = InputProps &
|
|
82
|
+
WrapperProps & {
|
|
83
|
+
options: OptionProps[];
|
|
84
|
+
loading?: boolean;
|
|
85
|
+
|
|
86
|
+
/** Произвольный префикс для поля */
|
|
87
|
+
prefix?: ReactNode;
|
|
88
|
+
/** Произвольный постфикс для поля */
|
|
89
|
+
postfix?: ReactNode;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
type FiledSelectCommonProps = WithSupportProps<{
|
|
93
|
+
options: OptionProps[];
|
|
94
|
+
|
|
95
|
+
pinTop?: OptionProps[];
|
|
96
|
+
pinBottom?: OptionProps[];
|
|
97
|
+
|
|
98
|
+
searchable?: boolean;
|
|
99
|
+
/** Отображение кнопки Копировать для поля (актуально только для `readonly = true`) */
|
|
100
|
+
showCopyButton?: boolean;
|
|
101
|
+
/**
|
|
102
|
+
* Отображение кнопки очистки поля
|
|
103
|
+
* @default true
|
|
104
|
+
*/
|
|
105
|
+
showClearButton?: boolean;
|
|
106
|
+
/**
|
|
107
|
+
* Является ли поле доступным только для чтения
|
|
108
|
+
* @default false
|
|
109
|
+
*/
|
|
110
|
+
readonly?: boolean;
|
|
111
|
+
|
|
112
|
+
/** Иконка-префикс для поля */
|
|
113
|
+
prefixIcon?: ReactElement;
|
|
114
|
+
|
|
115
|
+
footer?: DroplistProps['footer'];
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Включить виртуализацию на компоненты списка. Рекомендуется если у вас от 1к элементов списка
|
|
119
|
+
*/
|
|
120
|
+
virtualized?: boolean;
|
|
121
|
+
|
|
122
|
+
widthStrategy?: DroplistProps['widthStrategy'];
|
|
123
|
+
|
|
124
|
+
search?: SearchState;
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Включить нечеткий поиск
|
|
128
|
+
* @default true
|
|
129
|
+
*/
|
|
130
|
+
enableFuzzySearch?: boolean;
|
|
131
|
+
|
|
132
|
+
autocomplete?: boolean;
|
|
133
|
+
|
|
134
|
+
addOptionByEnter?: boolean;
|
|
135
|
+
|
|
136
|
+
open?: boolean;
|
|
137
|
+
|
|
138
|
+
onOpenChange?(open: boolean): void;
|
|
139
|
+
|
|
140
|
+
selectedOptionFormatter?: SelectedOptionFormatter;
|
|
141
|
+
}> &
|
|
142
|
+
Pick<DroplistProps, 'dataError' | 'noDataState' | 'noResultsState' | 'errorDataState' | 'dataFiltered'>;
|
|
143
|
+
|
|
144
|
+
export type MobileFieldSelectSingleProps = FieldSelectPrivateProps &
|
|
145
|
+
Omit<SelectionSingleState, 'mode'> &
|
|
146
|
+
WrapperProps &
|
|
147
|
+
FiledSelectCommonProps;
|
|
148
|
+
|
|
149
|
+
export type MobileFieldSelectMultipleProps = FieldSelectPrivateProps & {
|
|
150
|
+
removeByBackspace?: boolean;
|
|
151
|
+
} & Omit<SelectionMultipleState, 'mode'> &
|
|
152
|
+
Omit<FiledSelectCommonProps, 'showCopyButton'>;
|
|
153
|
+
|
|
154
|
+
export type MobileFieldSelectProps =
|
|
155
|
+
| (MobileFieldSelectSingleProps & { selection?: 'single' })
|
|
156
|
+
| (MobileFieldSelectMultipleProps & { selection: 'multiple' });
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { FieldDecoratorProps } from '@snack-uikit/fields';
|
|
2
|
+
|
|
3
|
+
export function extractFieldDecoratorProps({
|
|
4
|
+
error,
|
|
5
|
+
required,
|
|
6
|
+
readonly,
|
|
7
|
+
label,
|
|
8
|
+
labelTooltip,
|
|
9
|
+
labelTooltipPlacement,
|
|
10
|
+
labelFor,
|
|
11
|
+
caption,
|
|
12
|
+
hint,
|
|
13
|
+
disabled,
|
|
14
|
+
showHintIcon,
|
|
15
|
+
size,
|
|
16
|
+
validationState,
|
|
17
|
+
className,
|
|
18
|
+
}: Partial<FieldDecoratorProps>): Partial<FieldDecoratorProps> {
|
|
19
|
+
return {
|
|
20
|
+
error,
|
|
21
|
+
required,
|
|
22
|
+
readonly,
|
|
23
|
+
label,
|
|
24
|
+
labelTooltip,
|
|
25
|
+
labelTooltipPlacement,
|
|
26
|
+
labelFor,
|
|
27
|
+
caption,
|
|
28
|
+
hint,
|
|
29
|
+
disabled,
|
|
30
|
+
showHintIcon,
|
|
31
|
+
size,
|
|
32
|
+
validationState,
|
|
33
|
+
className,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { DroplistProps } from '@snack-uikit/list';
|
|
2
|
+
|
|
3
|
+
import { MobileFieldSelectProps } from '../types';
|
|
4
|
+
|
|
5
|
+
export function extractListProps({
|
|
6
|
+
dataError,
|
|
7
|
+
noDataState,
|
|
8
|
+
noResultsState,
|
|
9
|
+
errorDataState,
|
|
10
|
+
dataFiltered,
|
|
11
|
+
loading,
|
|
12
|
+
footer,
|
|
13
|
+
widthStrategy,
|
|
14
|
+
}: Partial<MobileFieldSelectProps>): Partial<DroplistProps> {
|
|
15
|
+
return {
|
|
16
|
+
dataError,
|
|
17
|
+
noDataState,
|
|
18
|
+
noResultsState,
|
|
19
|
+
errorDataState,
|
|
20
|
+
dataFiltered,
|
|
21
|
+
loading,
|
|
22
|
+
footer,
|
|
23
|
+
widthStrategy,
|
|
24
|
+
trigger: 'clickAndFocusVisible',
|
|
25
|
+
placement: 'bottom',
|
|
26
|
+
'data-test-id': 'field-select__list',
|
|
27
|
+
scroll: true,
|
|
28
|
+
marker: true,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { JSXElementConstructor } from 'react';
|
|
2
|
+
|
|
3
|
+
import { ChevronDownSVG, ChevronUpSVG } from '@cloud-ru/uikit-product-icons';
|
|
4
|
+
import { ICON_SIZE, SIZE, Size } from '@snack-uikit/input-private';
|
|
5
|
+
import { ValueOf } from '@snack-uikit/utils';
|
|
6
|
+
|
|
7
|
+
export function getArrowIcon({ size, open }: { size: Size; open: boolean }): {
|
|
8
|
+
ArrowIcon: JSXElementConstructor<{ className?: string; size: ValueOf<typeof ICON_SIZE> }>;
|
|
9
|
+
arrowIconSize: ValueOf<typeof ICON_SIZE>;
|
|
10
|
+
} {
|
|
11
|
+
return {
|
|
12
|
+
ArrowIcon: open ? ChevronUpSVG : ChevronDownSVG,
|
|
13
|
+
arrowIconSize: size === SIZE.S ? ICON_SIZE.Xs : ICON_SIZE.S,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { ItemProps, SelectionSingleValueType } from '@snack-uikit/list';
|
|
2
|
+
import { TagProps } from '@snack-uikit/tag';
|
|
3
|
+
|
|
4
|
+
import { flattenItems } from '../legacy';
|
|
5
|
+
import { BaseOptionProps, ItemWithId, OptionProps } from '../types';
|
|
6
|
+
import { isAccordionOptionProps, isGroupOptionProps, isNextListOptionProps } from './typeGuards';
|
|
7
|
+
|
|
8
|
+
export function transformOptionsToItems(
|
|
9
|
+
options: OptionProps[],
|
|
10
|
+
): (ItemProps & { appearance?: TagProps['appearance'] })[] {
|
|
11
|
+
return options.map(option => {
|
|
12
|
+
if (isAccordionOptionProps(option) || isNextListOptionProps(option)) {
|
|
13
|
+
const { description, option: contentOption, caption, options, value, ...rest } = option;
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
'data-test-id': 'field-select__list-option-' + option.value,
|
|
17
|
+
...rest,
|
|
18
|
+
id: value,
|
|
19
|
+
content: { option: contentOption, caption, description },
|
|
20
|
+
items: transformOptionsToItems(options),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (isGroupOptionProps(option)) {
|
|
25
|
+
const { options, ...rest } = option;
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
...rest,
|
|
29
|
+
items: transformOptionsToItems(options),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const { description, option: contentOption, caption, value, appearance, ...rest } = option as BaseOptionProps;
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
'data-test-id': 'field-select__list-option-' + option.value,
|
|
37
|
+
...rest,
|
|
38
|
+
id: value,
|
|
39
|
+
appearance: appearance,
|
|
40
|
+
content: { description, option: contentOption, caption: caption },
|
|
41
|
+
};
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function findSelectedOption(
|
|
46
|
+
items: ItemProps[],
|
|
47
|
+
value: SelectionSingleValueType,
|
|
48
|
+
): [ItemWithId | undefined, ItemWithId | undefined] {
|
|
49
|
+
const flatten: ItemWithId[] = flattenItems(items);
|
|
50
|
+
|
|
51
|
+
if (!value) {
|
|
52
|
+
return [undefined, undefined];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const foundItem = flatten.find(item => String(item.id) === String(value));
|
|
56
|
+
const placeholderItem = { id: value, content: { option: String(value) } };
|
|
57
|
+
|
|
58
|
+
return [foundItem, !foundItem ? placeholderItem : undefined];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function findSelectedOptions(
|
|
62
|
+
items: ItemProps[],
|
|
63
|
+
value: SelectionSingleValueType[] | undefined,
|
|
64
|
+
): [ItemWithId[] | undefined, ItemWithId[] | undefined] {
|
|
65
|
+
const flatten: ItemWithId[] | undefined = flattenItems(items);
|
|
66
|
+
|
|
67
|
+
if (!value || !value?.length) {
|
|
68
|
+
return [undefined, undefined];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
let foundItems: ItemWithId[] | undefined;
|
|
72
|
+
let placeholderItems: ItemWithId[] | undefined;
|
|
73
|
+
|
|
74
|
+
value.forEach(value => {
|
|
75
|
+
if (flatten) {
|
|
76
|
+
const [found, placeholder] = findSelectedOption(flatten, value);
|
|
77
|
+
if (found || foundItems) {
|
|
78
|
+
foundItems = (foundItems ?? []).concat(found ?? []);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (placeholder || placeholderItems) {
|
|
82
|
+
placeholderItems = (placeholderItems ?? []).concat(placeholder ?? []);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
return [foundItems, placeholderItems];
|
|
88
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AccordionOptionProps,
|
|
3
|
+
BaseOptionProps,
|
|
4
|
+
GroupOptionProps,
|
|
5
|
+
MobileFieldSelectMultipleProps,
|
|
6
|
+
MobileFieldSelectSingleProps,
|
|
7
|
+
NestListOptionProps,
|
|
8
|
+
} from '../types';
|
|
9
|
+
|
|
10
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
11
|
+
export function isBaseOptionProps(option: any): option is BaseOptionProps {
|
|
12
|
+
return !('options' in option);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
16
|
+
export function isAccordionOptionProps(option: any): option is AccordionOptionProps {
|
|
17
|
+
return 'options' in option && option['type'] === 'collapse';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
21
|
+
export function isNextListOptionProps(option: any): option is NestListOptionProps {
|
|
22
|
+
return 'options' in option && option['type'] === 'next-list';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
26
|
+
export function isGroupOptionProps(option: any): option is GroupOptionProps {
|
|
27
|
+
return 'options' in option && option['type'] === 'group';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
31
|
+
export function isFieldSelectMultipleProps(props: any): props is MobileFieldSelectMultipleProps {
|
|
32
|
+
return 'selection' in props && props['selection'] === 'multiple';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
36
|
+
export function isFieldSelectSingleProps(props: any): props is MobileFieldSelectSingleProps {
|
|
37
|
+
return ('selection' in props && props['selection'] === 'single') || props['selection'] === undefined;
|
|
38
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { ItemId, ItemProps, SelectionSingleValueType } from '@snack-uikit/list';
|
|
2
|
+
|
|
3
|
+
import { flattenItems } from '../legacy';
|
|
4
|
+
import { ItemWithId, OptionProps } from '../types';
|
|
5
|
+
import { transformOptionsToItems } from './options';
|
|
6
|
+
|
|
7
|
+
export function createPlaceholderItem(value: SelectionSingleValueType) {
|
|
8
|
+
return { id: value, content: { option: String(value) }, placeholder: true };
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function updateItems({
|
|
12
|
+
options,
|
|
13
|
+
value,
|
|
14
|
+
selectedItem,
|
|
15
|
+
}: {
|
|
16
|
+
options: OptionProps[];
|
|
17
|
+
value?: ItemId;
|
|
18
|
+
selectedItem?: ItemWithId;
|
|
19
|
+
currentItems?: ItemProps[];
|
|
20
|
+
}): {
|
|
21
|
+
selectedItem?: ItemWithId;
|
|
22
|
+
items: ItemProps[];
|
|
23
|
+
} {
|
|
24
|
+
const originalItems = transformOptionsToItems(options);
|
|
25
|
+
|
|
26
|
+
if (value === undefined) {
|
|
27
|
+
return {
|
|
28
|
+
selectedItem: undefined,
|
|
29
|
+
items: originalItems,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let newItems: ItemProps[] = originalItems;
|
|
34
|
+
let newSelectedItem = selectedItem;
|
|
35
|
+
|
|
36
|
+
const foundItem = flattenItems(originalItems).find(item => item.id === value);
|
|
37
|
+
|
|
38
|
+
if (!foundItem) {
|
|
39
|
+
if (selectedItem && selectedItem?.id === value) {
|
|
40
|
+
newItems = [selectedItem, ...newItems];
|
|
41
|
+
} else {
|
|
42
|
+
newSelectedItem = createPlaceholderItem(value);
|
|
43
|
+
newItems = [newSelectedItem, ...newItems];
|
|
44
|
+
}
|
|
45
|
+
} else {
|
|
46
|
+
newSelectedItem = foundItem;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
selectedItem: newSelectedItem,
|
|
51
|
+
items: newItems,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function updateMultipleItems({
|
|
56
|
+
options,
|
|
57
|
+
value,
|
|
58
|
+
selectedItems,
|
|
59
|
+
}: {
|
|
60
|
+
options: OptionProps[];
|
|
61
|
+
value?: SelectionSingleValueType[];
|
|
62
|
+
selectedItems?: ItemWithId[];
|
|
63
|
+
currentItems?: ItemProps[];
|
|
64
|
+
}): {
|
|
65
|
+
selectedItems?: ItemWithId[];
|
|
66
|
+
items: ItemProps[];
|
|
67
|
+
} {
|
|
68
|
+
const originalItems = transformOptionsToItems(options);
|
|
69
|
+
|
|
70
|
+
if (!value || !value.length) {
|
|
71
|
+
return {
|
|
72
|
+
selectedItems: undefined,
|
|
73
|
+
items: originalItems,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const foundedValue: (number | string)[] = [];
|
|
78
|
+
|
|
79
|
+
let newItems: ItemProps[] = originalItems;
|
|
80
|
+
let newSelectedItems = selectedItems;
|
|
81
|
+
|
|
82
|
+
const flattenOriginalItems = flattenItems(originalItems);
|
|
83
|
+
|
|
84
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
85
|
+
const foundItems: ItemWithId[] = flattenOriginalItems.filter((item: any) => {
|
|
86
|
+
if (value.includes(item.id) && !foundedValue.includes(item.id)) {
|
|
87
|
+
foundedValue.push(item.id);
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
const nonFoundValues: SelectionSingleValueType[] = value.filter(
|
|
92
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
93
|
+
value => !flattenOriginalItems.find((item: any) => item.id === value),
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
if (nonFoundValues.length) {
|
|
97
|
+
const nonFoundItems = nonFoundValues.map(
|
|
98
|
+
value => selectedItems?.find(selectedItem => selectedItem.id === value) || createPlaceholderItem(value),
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
newSelectedItems = [...foundItems, ...nonFoundItems];
|
|
102
|
+
newItems = [...nonFoundItems, ...newItems];
|
|
103
|
+
} else {
|
|
104
|
+
newSelectedItems = foundItems;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
selectedItems: newSelectedItems.sort((a, b) => {
|
|
109
|
+
if (b.disabled && !a.disabled) {
|
|
110
|
+
return 1;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (a.disabled && !b.disabled) {
|
|
114
|
+
return -1;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return 0;
|
|
118
|
+
}),
|
|
119
|
+
items: newItems,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export const VALIDATION_STATE = {
|
|
2
|
+
Default: 'default',
|
|
3
|
+
Error: 'error',
|
|
4
|
+
Warning: 'warning',
|
|
5
|
+
Success: 'success',
|
|
6
|
+
} as const;
|
|
7
|
+
|
|
8
|
+
export const CONTAINER_VARIANT = {
|
|
9
|
+
SingleLine: 'single-line-container',
|
|
10
|
+
MultiLine: 'multi-line-container',
|
|
11
|
+
} as const;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { Mode, NoSecondsMode, Slot, TimeMode } from '../types';
|
|
2
|
+
|
|
3
|
+
export enum SlotKey {
|
|
4
|
+
Day = 'D',
|
|
5
|
+
Month = 'M',
|
|
6
|
+
Year = 'Y',
|
|
7
|
+
Hours = 'h',
|
|
8
|
+
Minutes = 'm',
|
|
9
|
+
Seconds = 's',
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const MODES = {
|
|
13
|
+
Date: 'date',
|
|
14
|
+
DateTime: 'date-time',
|
|
15
|
+
} as const;
|
|
16
|
+
|
|
17
|
+
export const TIME_MODES = {
|
|
18
|
+
FullTime: 'full-time',
|
|
19
|
+
NoSeconds: 'no-seconds',
|
|
20
|
+
} as const;
|
|
21
|
+
|
|
22
|
+
export const NO_SECONDS_MODE = 'date-time-no-sec';
|
|
23
|
+
|
|
24
|
+
export const MASK: Record<Mode | TimeMode | NoSecondsMode, Record<string, string>> = {
|
|
25
|
+
[MODES.Date]: {
|
|
26
|
+
'ru-RU': 'ДД.ММ.ГГГГ',
|
|
27
|
+
'en-US': 'DD.MM.YYYY',
|
|
28
|
+
},
|
|
29
|
+
[MODES.DateTime]: {
|
|
30
|
+
'ru-RU': 'ДД.ММ.ГГГГ, чч:мм:сс',
|
|
31
|
+
'en-US': 'DD.MM.YYYY, hh:mm:ss',
|
|
32
|
+
},
|
|
33
|
+
[NO_SECONDS_MODE]: {
|
|
34
|
+
'ru-RU': 'ДД.ММ.ГГГГ, чч:мм',
|
|
35
|
+
'en-US': 'DD.MM.YYYY, hh:mm',
|
|
36
|
+
},
|
|
37
|
+
[TIME_MODES.FullTime]: {
|
|
38
|
+
'ru-RU': 'чч:мм:сс',
|
|
39
|
+
'en-US': 'hh:mm:ss',
|
|
40
|
+
},
|
|
41
|
+
[TIME_MODES.NoSeconds]: {
|
|
42
|
+
'ru-RU': 'чч:мм',
|
|
43
|
+
'en-US': 'hh:mm',
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export const DEFAULT_LOCALE = new Intl.Locale('ru-RU');
|
|
48
|
+
|
|
49
|
+
const DATE_SLOTS = {
|
|
50
|
+
[SlotKey.Day]: { start: 0, end: 2, max: 31, min: 1 },
|
|
51
|
+
[SlotKey.Month]: { start: 3, end: 5, max: 12, min: 1 },
|
|
52
|
+
[SlotKey.Year]: { start: 6, end: 10, max: 2100, min: 1900 },
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const TIME_SLOTS = (shift: number, showSeconds: boolean) => ({
|
|
56
|
+
[SlotKey.Hours]: { start: shift, end: shift + 2, max: 23, min: 0 },
|
|
57
|
+
[SlotKey.Minutes]: { start: shift + 3, end: shift + 5, max: 59, min: 0 },
|
|
58
|
+
...(showSeconds ? { [SlotKey.Seconds]: { start: shift + 6, end: shift + 8, max: 59, min: 0 } } : {}),
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
export const SLOTS: Record<Mode | TimeMode | NoSecondsMode, Record<SlotKey | string, Slot>> = {
|
|
62
|
+
[MODES.Date]: DATE_SLOTS,
|
|
63
|
+
[MODES.DateTime]: { ...DATE_SLOTS, ...TIME_SLOTS(12, true) },
|
|
64
|
+
[NO_SECONDS_MODE]: { ...DATE_SLOTS, ...TIME_SLOTS(12, false) },
|
|
65
|
+
[TIME_MODES.FullTime]: TIME_SLOTS(0, true),
|
|
66
|
+
[TIME_MODES.NoSeconds]: TIME_SLOTS(0, false),
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export type FocusSlot = SlotKey | 'auto';
|
|
70
|
+
|
|
71
|
+
export const SLOT_ORDER: Record<Mode | TimeMode | NoSecondsMode, SlotKey[]> = {
|
|
72
|
+
[MODES.Date]: [SlotKey.Day, SlotKey.Month, SlotKey.Year],
|
|
73
|
+
[MODES.DateTime]: [SlotKey.Day, SlotKey.Month, SlotKey.Year, SlotKey.Hours, SlotKey.Minutes, SlotKey.Seconds],
|
|
74
|
+
[NO_SECONDS_MODE]: [SlotKey.Day, SlotKey.Month, SlotKey.Year, SlotKey.Hours, SlotKey.Minutes],
|
|
75
|
+
[TIME_MODES.FullTime]: [SlotKey.Hours, SlotKey.Minutes, SlotKey.Seconds],
|
|
76
|
+
[TIME_MODES.NoSeconds]: [SlotKey.Hours, SlotKey.Minutes],
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const RU_DATE_SLOTS_PLACEHOLDER = {
|
|
80
|
+
[SlotKey.Day]: 'ДД',
|
|
81
|
+
[SlotKey.Month]: 'ММ',
|
|
82
|
+
[SlotKey.Year]: 'ГГГГ',
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const RU_TIME_SLOTS_PLACEHOLDER = {
|
|
86
|
+
[SlotKey.Hours]: 'чч',
|
|
87
|
+
[SlotKey.Minutes]: 'мм',
|
|
88
|
+
[SlotKey.Seconds]: 'сс',
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const EN_DATE_SLOTS_PLACEHOLDER = {
|
|
92
|
+
[SlotKey.Day]: 'DD',
|
|
93
|
+
[SlotKey.Month]: 'MM',
|
|
94
|
+
[SlotKey.Year]: 'YYYY',
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const EN_TIME_SLOTS_PLACEHOLDER = {
|
|
98
|
+
[SlotKey.Hours]: 'hh',
|
|
99
|
+
[SlotKey.Minutes]: 'mm',
|
|
100
|
+
[SlotKey.Seconds]: 'ss',
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
export const SLOTS_PLACEHOLDER: Record<
|
|
104
|
+
Mode | TimeMode | NoSecondsMode,
|
|
105
|
+
Record<string, Partial<Record<SlotKey, string>>>
|
|
106
|
+
> = {
|
|
107
|
+
[MODES.Date]: {
|
|
108
|
+
'ru-RU': RU_DATE_SLOTS_PLACEHOLDER,
|
|
109
|
+
'en-US': EN_DATE_SLOTS_PLACEHOLDER,
|
|
110
|
+
},
|
|
111
|
+
[MODES.DateTime]: {
|
|
112
|
+
'ru-RU': { ...RU_DATE_SLOTS_PLACEHOLDER, ...RU_TIME_SLOTS_PLACEHOLDER },
|
|
113
|
+
'en-US': { ...EN_DATE_SLOTS_PLACEHOLDER, ...EN_TIME_SLOTS_PLACEHOLDER },
|
|
114
|
+
},
|
|
115
|
+
[NO_SECONDS_MODE]: {
|
|
116
|
+
'ru-RU': { ...RU_DATE_SLOTS_PLACEHOLDER, ...RU_TIME_SLOTS_PLACEHOLDER, [SlotKey.Seconds]: undefined },
|
|
117
|
+
'en-US': { ...EN_DATE_SLOTS_PLACEHOLDER, ...EN_TIME_SLOTS_PLACEHOLDER, [SlotKey.Seconds]: undefined },
|
|
118
|
+
},
|
|
119
|
+
[TIME_MODES.FullTime]: {
|
|
120
|
+
'ru-RU': RU_TIME_SLOTS_PLACEHOLDER,
|
|
121
|
+
'en-US': EN_TIME_SLOTS_PLACEHOLDER,
|
|
122
|
+
},
|
|
123
|
+
[TIME_MODES.NoSeconds]: {
|
|
124
|
+
'ru-RU': { ...RU_TIME_SLOTS_PLACEHOLDER, [SlotKey.Seconds]: undefined },
|
|
125
|
+
'en-US': { ...EN_TIME_SLOTS_PLACEHOLDER, [SlotKey.Seconds]: undefined },
|
|
126
|
+
},
|
|
127
|
+
};
|