@cloud-ru/uikit-product-mobile-chips 0.8.36

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.
Files changed (46) hide show
  1. package/CHANGELOG.md +1178 -0
  2. package/LICENSE +201 -0
  3. package/README.md +8 -0
  4. package/package.json +60 -0
  5. package/src/components/AdaptiveChips/AdaptiveChips.tsx +21 -0
  6. package/src/components/AdaptiveChips/index.ts +1 -0
  7. package/src/components/MobileChipChoice/components/ChipChoiceBase/ChipChoiceBase.tsx +148 -0
  8. package/src/components/MobileChipChoice/components/ChipChoiceBase/index.ts +1 -0
  9. package/src/components/MobileChipChoice/components/ChipChoiceBase/styles.module.scss +89 -0
  10. package/src/components/MobileChipChoice/components/MobileChipChoiceCustom.tsx +81 -0
  11. package/src/components/MobileChipChoice/components/MobileChipChoiceDate.tsx +135 -0
  12. package/src/components/MobileChipChoice/components/MobileChipChoiceDateRange.tsx +101 -0
  13. package/src/components/MobileChipChoice/components/MobileChipChoiceMultiple.tsx +218 -0
  14. package/src/components/MobileChipChoice/components/MobileChipChoiceSingle.tsx +176 -0
  15. package/src/components/MobileChipChoice/components/MobileChipChoiceTime.tsx +122 -0
  16. package/src/components/MobileChipChoice/components/index.ts +6 -0
  17. package/src/components/MobileChipChoice/components/styles.module.scss +36 -0
  18. package/src/components/MobileChipChoice/constants.ts +20 -0
  19. package/src/components/MobileChipChoice/hooks.tsx +127 -0
  20. package/src/components/MobileChipChoice/index.ts +38 -0
  21. package/src/components/MobileChipChoice/styles.module.scss +103 -0
  22. package/src/components/MobileChipChoice/types.ts +139 -0
  23. package/src/components/MobileChipChoice/utils/index.ts +3 -0
  24. package/src/components/MobileChipChoice/utils/options.tsx +61 -0
  25. package/src/components/MobileChipChoice/utils/typeGuards.ts +35 -0
  26. package/src/components/MobileChipChoice/utils/utils.ts +62 -0
  27. package/src/components/MobileChipChoiceRow/MobileChipChoiceRow.tsx +275 -0
  28. package/src/components/MobileChipChoiceRow/components/ForwardedChipChoice.tsx +10 -0
  29. package/src/components/MobileChipChoiceRow/components/constants.ts +12 -0
  30. package/src/components/MobileChipChoiceRow/components/index.ts +1 -0
  31. package/src/components/MobileChipChoiceRow/constants.ts +21 -0
  32. package/src/components/MobileChipChoiceRow/index.ts +2 -0
  33. package/src/components/MobileChipChoiceRow/styles.module.scss +32 -0
  34. package/src/components/MobileChipChoiceRow/types.ts +60 -0
  35. package/src/components/index.ts +3 -0
  36. package/src/constants.ts +50 -0
  37. package/src/helperComponents/ButtonClearValue/ButtonClearValue.tsx +40 -0
  38. package/src/helperComponents/ButtonClearValue/index.ts +1 -0
  39. package/src/helperComponents/ButtonClearValue/styles.module.scss +50 -0
  40. package/src/helperComponents/ItemContent/ItemContent.tsx +37 -0
  41. package/src/helperComponents/ItemContent/index.ts +1 -0
  42. package/src/helperComponents/ItemContent/styles.module.scss +80 -0
  43. package/src/helperComponents/index.ts +2 -0
  44. package/src/index.ts +1 -0
  45. package/src/styles.module.scss +113 -0
  46. package/src/types.ts +26 -0
@@ -0,0 +1,122 @@
1
+ import { ReactNode, useCallback, useMemo, useRef } from 'react';
2
+ import { useUncontrolledProp } from 'uncontrollable';
3
+
4
+ import { useLocale } from '@cloud-ru/uikit-product-locale';
5
+ import { MobileDropdown } from '@cloud-ru/uikit-product-mobile-dropdown';
6
+ import { TimePicker, TimePickerProps } from '@snack-uikit/calendar';
7
+ import { useValueControl } from '@snack-uikit/utils';
8
+
9
+ import { CHIP_CHOICE_TEST_IDS, SIZE } from '../../../constants';
10
+ import { DEFAULT_LOCALE } from '../constants';
11
+ import { useHandleOnKeyDown } from '../hooks';
12
+ import { ChipChoiceCommonProps } from '../types';
13
+ import { ChipChoiceBase } from './ChipChoiceBase';
14
+ import styles from './styles.module.scss';
15
+
16
+ const getStringTimeValue = (
17
+ time: TimePickerProps['value'],
18
+ { showSeconds, locale }: Pick<TimePickerProps, 'showSeconds'> & { locale: Intl.Locale },
19
+ ) => {
20
+ if (!time) {
21
+ return '';
22
+ }
23
+
24
+ const date = new Date();
25
+ date.setHours(time.hours ?? 0);
26
+ date.setMinutes(time.minutes ?? 0);
27
+ date.setSeconds(time.seconds ?? 0);
28
+
29
+ return date.toLocaleTimeString(locale, {
30
+ hour: 'numeric',
31
+ minute: 'numeric',
32
+ second: showSeconds ? 'numeric' : undefined,
33
+ });
34
+ };
35
+
36
+ type TimeValue = TimePickerProps['value'];
37
+
38
+ export type MobileChipChoiceTimeProps = Omit<ChipChoiceCommonProps, 'widthStrategy'> &
39
+ Pick<TimePickerProps, 'value' | 'defaultValue' | 'showSeconds'> & {
40
+ /** Колбек смены значения */
41
+ onChange?(value: TimeValue): void;
42
+ /** Колбек формирующий строковое представление выбранного значения. Принимает выбранное значение */
43
+ valueRender?(value?: TimeValue): ReactNode;
44
+ };
45
+
46
+ export function MobileChipChoiceTime({
47
+ size = SIZE.S,
48
+ value,
49
+ defaultValue,
50
+ onChange,
51
+ valueRender,
52
+ showSeconds = true,
53
+ onClearButtonClick,
54
+ open: openProp,
55
+ onOpenChange,
56
+ ...rest
57
+ }: MobileChipChoiceTimeProps) {
58
+ const [selectedValue, setSelectedValue] = useValueControl<TimeValue>({ value, defaultValue, onChange });
59
+
60
+ const localRef = useRef<HTMLDivElement>(null);
61
+
62
+ const [open, setOpen] = useUncontrolledProp(openProp, false, onOpenChange);
63
+ const handleOnKeyDown = useHandleOnKeyDown({ setOpen });
64
+
65
+ const closeDroplist = useCallback(() => {
66
+ setOpen(false);
67
+ setTimeout(() => localRef.current?.focus(), 0);
68
+ }, [setOpen]);
69
+
70
+ const { t } = useLocale('Chips');
71
+
72
+ const valueToRender = useMemo(() => {
73
+ if (valueRender) {
74
+ return valueRender(selectedValue);
75
+ }
76
+
77
+ if (!selectedValue) return t('allLabel');
78
+
79
+ return getStringTimeValue(selectedValue, { showSeconds, locale: DEFAULT_LOCALE });
80
+ }, [selectedValue, showSeconds, t, valueRender]);
81
+
82
+ const handleChangeValue = useCallback(
83
+ (value: TimeValue) => {
84
+ setSelectedValue(value);
85
+ closeDroplist();
86
+ },
87
+ [closeDroplist, setSelectedValue],
88
+ );
89
+
90
+ const navigationStartRef = useRef<HTMLButtonElement>(null);
91
+ const focusNavigationStartItem = () => setTimeout(() => navigationStartRef.current?.focus(), 0);
92
+
93
+ return (
94
+ <MobileDropdown
95
+ content={
96
+ <TimePicker
97
+ className={styles.timeWrapper}
98
+ value={selectedValue}
99
+ size='l'
100
+ fitToContainer={false}
101
+ onChangeValue={handleChangeValue}
102
+ navigationStartRef={navigationStartRef}
103
+ onFocusLeave={closeDroplist}
104
+ showSeconds={showSeconds}
105
+ />
106
+ }
107
+ open={open}
108
+ onOpenChange={setOpen}
109
+ data-test-id={CHIP_CHOICE_TEST_IDS.droplist}
110
+ >
111
+ <ChipChoiceBase
112
+ {...rest}
113
+ ref={localRef}
114
+ onClearButtonClick={onClearButtonClick}
115
+ value={selectedValue}
116
+ valueToRender={valueToRender}
117
+ size={size}
118
+ onKeyDown={handleOnKeyDown(focusNavigationStartItem)}
119
+ />
120
+ </MobileDropdown>
121
+ );
122
+ }
@@ -0,0 +1,6 @@
1
+ export * from './MobileChipChoiceCustom';
2
+ export * from './MobileChipChoiceSingle';
3
+ export * from './MobileChipChoiceMultiple';
4
+ export * from './MobileChipChoiceDate';
5
+ export * from './MobileChipChoiceDateRange';
6
+ export * from './MobileChipChoiceTime';
@@ -0,0 +1,36 @@
1
+ @use '@sbercloud/figma-tokens-cloud-platform/build/scss/components/styles-tokens-fields';
2
+
3
+ .footer {
4
+ width: 100%;
5
+ }
6
+
7
+ .footerTopLine {
8
+ display: flex;
9
+ align-items: center;
10
+ justify-content: space-between;
11
+ gap: 4px;
12
+ }
13
+
14
+ .counter {
15
+ color: styles-tokens-fields.$sys-neutral-text-light;
16
+
17
+ @include styles-tokens-fields.composite-var(styles-tokens-fields.$sans-body-m);
18
+ }
19
+
20
+ .bodyNoPadding {
21
+ /* stylelint-disable-next-line declaration-no-important */
22
+ padding: 0 !important;
23
+ }
24
+
25
+ .dateWrapper {
26
+ height: 384px;
27
+ }
28
+
29
+ .dateTimeWrapper {
30
+ height: 458px;
31
+ padding-bottom: 8px;
32
+ }
33
+
34
+ .timeWrapper {
35
+ min-width: 100%;
36
+ }
@@ -0,0 +1,20 @@
1
+ import { BUTTON_SIZE, SIZE } from '../../constants';
2
+
3
+ export const BUTTON_CLEAR_VALUE_SIZE_MAP = {
4
+ [SIZE.Xs]: BUTTON_SIZE.Xxs,
5
+ [SIZE.S]: BUTTON_SIZE.Xs,
6
+ [SIZE.M]: BUTTON_SIZE.Xs,
7
+ [SIZE.L]: BUTTON_SIZE.Xs,
8
+ };
9
+
10
+ export const CHIP_CHOICE_TYPE = {
11
+ Multiple: 'multiple',
12
+ Date: 'date',
13
+ DateTime: 'date-time',
14
+ DateRange: 'date-range',
15
+ Single: 'single',
16
+ Custom: 'custom',
17
+ Time: 'time',
18
+ } as const;
19
+
20
+ export const DEFAULT_LOCALE = new Intl.Locale('ru-RU');
@@ -0,0 +1,127 @@
1
+ import FuzzySearch from 'fuzzy-search';
2
+ import { KeyboardEvent, KeyboardEventHandler, useCallback, useMemo } from 'react';
3
+
4
+ import { useLocale } from '@cloud-ru/uikit-product-locale';
5
+ import { ItemId, MobileDroplistProps } from '@cloud-ru/uikit-product-mobile-dropdown';
6
+ import { ButtonFilled, ButtonFunction } from '@snack-uikit/button';
7
+
8
+ import { CHIP_CHOICE_TEST_IDS } from '../../constants';
9
+ import styles from './styles.module.scss';
10
+ import { AccordionOption, BaseOption, ContentRenderProps, FilterOption, NestListOption } from './types';
11
+
12
+ type UseHandleOnKeyDownProps = {
13
+ setOpen(open: boolean): void;
14
+ };
15
+
16
+ export function useHandleOnKeyDown({ setOpen }: UseHandleOnKeyDownProps) {
17
+ return useCallback(
18
+ (onKeyDown?: KeyboardEventHandler<HTMLElement>) => (e: KeyboardEvent<HTMLDivElement>) => {
19
+ if (e.code === 'Space') {
20
+ e.stopPropagation();
21
+ } else {
22
+ onKeyDown?.(e);
23
+ }
24
+
25
+ if (['ArrowDown'].includes(e.key)) {
26
+ e.preventDefault();
27
+ setOpen(true);
28
+ }
29
+
30
+ if (['ArrowUp'].includes(e.key)) {
31
+ e.preventDefault();
32
+ setOpen(false);
33
+ }
34
+
35
+ if (e.key === 'Tab') {
36
+ setOpen(false);
37
+ }
38
+ },
39
+ [setOpen],
40
+ );
41
+ }
42
+
43
+ const DEFAULT_MIN_SEARCH_INPUT_LENGTH = 2;
44
+
45
+ /**
46
+ * Нечеткий поиск среди айтемов по полям 'content.option', 'content.caption', 'content.description', 'label'
47
+ */
48
+ export function useOptionSearch<T extends ContentRenderProps = ContentRenderProps>({
49
+ options,
50
+ flatMapOptions,
51
+ minSearchInputLength,
52
+ disableFuzzySearch,
53
+ }: {
54
+ options: FilterOption<T>[];
55
+ flatMapOptions: (BaseOption<T> | AccordionOption<T> | NestListOption<T>)[];
56
+ minSearchInputLength?: number;
57
+ disableFuzzySearch?: boolean;
58
+ }) {
59
+ return useCallback(
60
+ (search: string) => {
61
+ if (search.length < (minSearchInputLength ?? DEFAULT_MIN_SEARCH_INPUT_LENGTH)) return options;
62
+
63
+ if (disableFuzzySearch) {
64
+ return options.filter(option => {
65
+ const fieldsForSearch = [option.label];
66
+
67
+ if ('contentRenderProps' in option) {
68
+ fieldsForSearch.push(option?.contentRenderProps?.description);
69
+ fieldsForSearch.push(option?.contentRenderProps?.caption);
70
+ }
71
+
72
+ return fieldsForSearch
73
+ .filter((v): v is ItemId => Boolean(v))
74
+ .some(value => value.toString().includes(search));
75
+ });
76
+ }
77
+
78
+ return new FuzzySearch(
79
+ flatMapOptions,
80
+ ['label', 'contentRenderProps.description', 'contentRenderProps.caption'],
81
+ {},
82
+ ).search(search);
83
+ },
84
+ [disableFuzzySearch, flatMapOptions, minSearchInputLength, options],
85
+ );
86
+ }
87
+
88
+ type UseAutoApplyProps = {
89
+ autoApply: boolean;
90
+ onApprove(): void;
91
+ onCancel(): void;
92
+ };
93
+
94
+ export function useAutoApplyFooter({
95
+ autoApply,
96
+ onApprove,
97
+ onCancel,
98
+ }: UseAutoApplyProps): MobileDroplistProps['footer'] {
99
+ const { t } = useLocale('Chips');
100
+
101
+ return useMemo(() => {
102
+ if (autoApply) {
103
+ return null;
104
+ }
105
+
106
+ return (
107
+ <div className={styles.autoApplyFooter} data-test-id={CHIP_CHOICE_TEST_IDS.footer}>
108
+ <ButtonFunction
109
+ fullWidth
110
+ size='l'
111
+ appearance='neutral'
112
+ label={t('cancel')}
113
+ onClick={onCancel}
114
+ data-test-id={CHIP_CHOICE_TEST_IDS.cancelButton}
115
+ />
116
+ <ButtonFilled
117
+ size='l'
118
+ fullWidth
119
+ appearance='primary'
120
+ label={t('apply')}
121
+ onClick={onApprove}
122
+ data-test-id={CHIP_CHOICE_TEST_IDS.approveButton}
123
+ />
124
+ </div>
125
+ );
126
+ }, [t, autoApply, onApprove, onCancel]);
127
+ }
@@ -0,0 +1,38 @@
1
+ import {
2
+ CustomContentRenderProps,
3
+ MobileChipChoiceCustom,
4
+ MobileChipChoiceCustomProps,
5
+ MobileChipChoiceDate,
6
+ MobileChipChoiceDateProps,
7
+ MobileChipChoiceDateRange,
8
+ MobileChipChoiceDateRangeProps,
9
+ MobileChipChoiceMultiple,
10
+ MobileChipChoiceSingle,
11
+ MobileChipChoiceTime,
12
+ MobileChipChoiceTimeProps,
13
+ } from './components';
14
+
15
+ export type {
16
+ FilterOption,
17
+ MobileChipChoiceMultipleProps,
18
+ MobileChipChoiceSingleProps,
19
+ ContentRenderProps,
20
+ } from './types';
21
+ export type {
22
+ MobileChipChoiceCustomProps,
23
+ MobileChipChoiceDateProps,
24
+ MobileChipChoiceDateRangeProps,
25
+ MobileChipChoiceTimeProps,
26
+ CustomContentRenderProps,
27
+ };
28
+
29
+ export namespace MobileChipChoice {
30
+ export const Custom = MobileChipChoiceCustom;
31
+ export const Single = MobileChipChoiceSingle;
32
+ export const Multiple = MobileChipChoiceMultiple;
33
+ export const Date = MobileChipChoiceDate;
34
+ export const DateRange = MobileChipChoiceDateRange;
35
+ export const Time = MobileChipChoiceTime;
36
+ }
37
+
38
+ export { isAccordionOption, isBaseOption, isGroupOption, isGroupSelectOption, isNextListOption } from './utils';
@@ -0,0 +1,103 @@
1
+ @use '@sbercloud/figma-tokens-cloud-platform/build/scss/components/styles-tokens-chips-chipChoice';
2
+ @use '@sbercloud/figma-tokens-cloud-platform/build/scss/components/styles-tokens-element';
3
+ @use '../../styles.module';
4
+
5
+ $sizes: 'xs', 's', 'm', 'l';
6
+
7
+ $labelTypography: (
8
+ 'xs': styles-tokens-element.$light-label-s,
9
+ 's': styles-tokens-element.$light-label-m,
10
+ 'm': styles-tokens-element.$sans-label-l,
11
+ 'l': styles-tokens-element.$light-label-l,
12
+ );
13
+
14
+ $valueTypography: (
15
+ 'xs': styles-tokens-element.$sans-label-s,
16
+ 's': styles-tokens-element.$sans-label-m,
17
+ 'm': styles-tokens-element.$sans-label-l,
18
+ 'l': styles-tokens-element.$sans-label-l,
19
+ );
20
+
21
+ .label,
22
+ .value {
23
+ display: inline-flex;
24
+ align-items: center;
25
+ }
26
+
27
+ .choiceChip {
28
+ background-color: styles-tokens-element.$sys-neutral-background1-level;
29
+ border-color: styles-tokens-element.$sys-neutral-decor-default;
30
+
31
+ @include styles.chip-defaults;
32
+ @include styles.chip-anatomy-styles(styles-tokens-chips-chipChoice.$chip-choice, $sizes, $labelTypography);
33
+
34
+ .label,
35
+ .value {
36
+ color: styles-tokens-element.$sys-neutral-text-support;
37
+ }
38
+
39
+ .icon {
40
+ color: styles-tokens-element.$sys-neutral-text-light;
41
+ }
42
+
43
+ @each $size in $sizes {
44
+ &[data-size='#{$size}'] {
45
+ .value {
46
+ @include styles-tokens-element.composite-var($valueTypography, $size);
47
+ }
48
+ }
49
+ }
50
+
51
+ &:hover,
52
+ &:active,
53
+ &:focus-visible {
54
+ background-color: styles-tokens-element.$sys-neutral-background2-level;
55
+ border-color: styles-tokens-element.$sys-neutral-decor-hovered;
56
+
57
+ .label {
58
+ color: styles-tokens-element.$sys-neutral-text-support;
59
+ }
60
+
61
+ .value {
62
+ color: styles-tokens-element.$sys-neutral-text-main;
63
+ }
64
+ }
65
+
66
+ &:focus-visible {
67
+ @include styles.chip-outline;
68
+ }
69
+
70
+ &[data-disabled] {
71
+ cursor: not-allowed;
72
+ background-color: styles-tokens-element.$sys-neutral-background;
73
+ border-color: styles-tokens-element.$sys-neutral-decor-disabled;
74
+
75
+ .label,
76
+ .value {
77
+ color: styles-tokens-element.$sys-neutral-text-light;
78
+ }
79
+ }
80
+
81
+ &[data-loading] {
82
+ background-color: styles-tokens-element.$sys-neutral-background;
83
+ border-color: styles-tokens-element.$sys-neutral-decor-activated;
84
+
85
+ @include styles.chip-loading-state(styles-tokens-chips-chipChoice.$chip-choice, true, null, null, true);
86
+
87
+ .label,
88
+ .value {
89
+ color: styles-tokens-element.$sys-neutral-text-support;
90
+ }
91
+ }
92
+ }
93
+
94
+ .triggerClassName {
95
+ --offset: #{styles-tokens-element.$space-drop-list-drop-offset};
96
+ }
97
+
98
+ .autoApplyFooter {
99
+ display: flex;
100
+ align-items: center;
101
+ justify-content: space-between;
102
+ gap: 8px;
103
+ }
@@ -0,0 +1,139 @@
1
+ import { MouseEventHandler, ReactNode } from 'react';
2
+
3
+ import {
4
+ BaseItemProps,
5
+ DroplistProps,
6
+ GroupItemProps,
7
+ GroupSelectItemProps,
8
+ ItemContentProps,
9
+ ItemId,
10
+ NextListItemProps,
11
+ SelectionMultipleState,
12
+ SelectionSingleState,
13
+ } from '@cloud-ru/uikit-product-mobile-dropdown';
14
+ import { TruncateStringProps } from '@snack-uikit/truncate-string';
15
+ import { WithSupportProps } from '@snack-uikit/utils';
16
+
17
+ import { BaseChipProps, Size } from '../../types';
18
+
19
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
20
+ export type AnyType = any;
21
+
22
+ export type ContentRenderProps = Omit<ItemContentProps, 'option' | 'disabled'>;
23
+
24
+ export type FilterOption<T extends ContentRenderProps = ContentRenderProps> =
25
+ // eslint-disable-next-line no-use-before-define
26
+ | BaseOption<T>
27
+ // eslint-disable-next-line no-use-before-define
28
+ | AccordionOption<T>
29
+ // eslint-disable-next-line no-use-before-define
30
+ | GroupOption<T>
31
+ // eslint-disable-next-line no-use-before-define
32
+ | GroupSelectOption<T>
33
+ // eslint-disable-next-line no-use-before-define
34
+ | NestListOption<T>;
35
+
36
+ export type BaseOption<T extends ContentRenderProps = ContentRenderProps> = Omit<BaseItemProps, 'content' | 'id'> & {
37
+ value: ItemId;
38
+ label: ItemId;
39
+ contentRenderProps?: T;
40
+ };
41
+
42
+ export type AccordionOption<T extends ContentRenderProps = ContentRenderProps> = Omit<
43
+ BaseOption<T>,
44
+ 'switch' | 'inactive' | 'value'
45
+ > & {
46
+ id?: ItemId;
47
+ type: 'collapse';
48
+ options: FilterOption<T>[];
49
+ };
50
+
51
+ export type GroupOption<T extends ContentRenderProps = ContentRenderProps> = Omit<GroupItemProps, 'items'> & {
52
+ options: FilterOption<T>[];
53
+ };
54
+
55
+ export type GroupSelectOption<T extends ContentRenderProps = ContentRenderProps> = Omit<
56
+ GroupSelectItemProps,
57
+ 'items'
58
+ > & {
59
+ options: FilterOption<T>[];
60
+ };
61
+
62
+ export type NestListOption<T extends ContentRenderProps = ContentRenderProps> = Omit<
63
+ NextListItemProps,
64
+ 'items' | 'content'
65
+ > & {
66
+ label: ItemId;
67
+ contentRenderProps?: T;
68
+ options: FilterOption<T>[];
69
+ };
70
+
71
+ export type ChipChoiceCommonProps = WithSupportProps<
72
+ Partial<BaseChipProps> & {
73
+ /** Размер */
74
+ size?: Size;
75
+ /** Колбек обработки клика */
76
+ onClick?: MouseEventHandler<HTMLButtonElement | HTMLDivElement>;
77
+ /** Колбек для клика по кнопке очистки */
78
+ onClearButtonClick?: MouseEventHandler<HTMLButtonElement>;
79
+ /** Управляет состоянием показан/не показан. */
80
+ open?: boolean;
81
+ /** Колбек отображения компонента. Срабатывает при изменении состояния open. */
82
+ onOpenChange?(isOpen: boolean): void;
83
+ /** Вариант обрезания значения */
84
+ truncateVariant?: TruncateStringProps['variant'];
85
+ }
86
+ >;
87
+
88
+ export type ChipChoiceSelectCommonProps<T extends ContentRenderProps = ContentRenderProps> = ChipChoiceCommonProps & {
89
+ options: FilterOption<T>[];
90
+
91
+ contentRender?(option: { label: ItemId; value?: ItemId; contentRenderProps?: T }): ReactNode;
92
+ filterFn?(option: { label: ItemId; value?: ItemId; contentRenderProps?: T }): boolean;
93
+
94
+ searchable?: boolean;
95
+ /**
96
+ * Отключает Fuzzy Search. Иногда в дроплисте могут быть различные айдишники - нам важно искать их без Fuzzy Search
97
+ * @default false
98
+ */
99
+ disableFuzzySearch?: boolean;
100
+
101
+ /** Флаг, отвечающий за применение выбранного значения по умолчанию */
102
+ autoApply?: boolean;
103
+ /** Колбек основной кнопки */
104
+ onApprove?(): void;
105
+ /** Колбек кнопки отмены */
106
+ onCancel?(): void;
107
+ } & Pick<
108
+ DroplistProps,
109
+ | 'selection'
110
+ | 'scrollRef'
111
+ | 'scrollContainerRef'
112
+ | 'noDataState'
113
+ | 'footer'
114
+ | 'footerActiveElementsRefs'
115
+ | 'dataError'
116
+ | 'errorDataState'
117
+ | 'dataFiltered'
118
+ | 'noResultsState'
119
+ | 'loading'
120
+ | 'virtualized'
121
+ >;
122
+
123
+ export type MobileChipChoiceSingleProps<T extends ContentRenderProps = ContentRenderProps> =
124
+ ChipChoiceSelectCommonProps<T> &
125
+ Omit<SelectionSingleState, 'mode'> & {
126
+ /** Массив опций */
127
+ options: FilterOption<T>[];
128
+ /** Колбек формирующий отображение выбранного значения. Принимает выбранное значение. По умолчанию для отображения используется FilterOption.label */
129
+ valueRender?(option?: BaseOption<T>): ReactNode;
130
+ };
131
+
132
+ export type MobileChipChoiceMultipleProps<T extends ContentRenderProps = ContentRenderProps> =
133
+ ChipChoiceSelectCommonProps<T> &
134
+ Omit<SelectionMultipleState, 'mode'> & {
135
+ /** Массив опций */
136
+ options: FilterOption<T>[];
137
+ /** Колбек формирующий отображение выбранного значения. Принимает выбранное значение. По умолчанию для отображения используется FilterOption.label */
138
+ valueRender?(option?: BaseOption<T>[]): ReactNode;
139
+ };
@@ -0,0 +1,3 @@
1
+ export * from './typeGuards';
2
+
3
+ export * from './utils';
@@ -0,0 +1,61 @@
1
+ import { ReactNode } from 'react';
2
+
3
+ import { ItemId, MobileDroplistProps } from '@cloud-ru/uikit-product-mobile-dropdown';
4
+
5
+ import { ItemContent } from '../../../helperComponents';
6
+ import { BaseOption, ContentRenderProps, FilterOption } from '../types';
7
+ import { isAccordionOption, isGroupOption, isGroupSelectOption, isNextListOption } from './typeGuards';
8
+
9
+ export function transformOptionsToItems<T extends ContentRenderProps = ContentRenderProps>(
10
+ options: FilterOption<T>[],
11
+ contentRender?: (option: { label: ItemId; value?: ItemId; contentRenderProps?: T; disabled?: boolean }) => ReactNode,
12
+ ): MobileDroplistProps['items'] {
13
+ return options.map(option => {
14
+ if (isAccordionOption<T>(option) || isNextListOption<T>(option)) {
15
+ const { label, options, id, contentRenderProps, disabled, ...rest } = option;
16
+
17
+ return {
18
+ ...rest,
19
+ disabled,
20
+ id,
21
+ content: contentRender ? (
22
+ contentRender({ label, contentRenderProps, disabled })
23
+ ) : (
24
+ <ItemContent option={label} {...contentRenderProps} disabled={disabled} />
25
+ ),
26
+ items: transformOptionsToItems<T>(options),
27
+ };
28
+ }
29
+
30
+ if (isGroupSelectOption<T>(option)) {
31
+ const { options, ...rest } = option;
32
+
33
+ return {
34
+ ...rest,
35
+ items: transformOptionsToItems<T>(options),
36
+ };
37
+ }
38
+
39
+ if (isGroupOption(option)) {
40
+ const { options, ...rest } = option;
41
+
42
+ return {
43
+ ...rest,
44
+ items: transformOptionsToItems(options),
45
+ };
46
+ }
47
+
48
+ const { label, value, contentRenderProps, disabled, ...rest } = option as BaseOption<T>;
49
+
50
+ return {
51
+ ...rest,
52
+ disabled,
53
+ id: value,
54
+ content: contentRender ? (
55
+ contentRender({ label, contentRenderProps, disabled, value })
56
+ ) : (
57
+ <ItemContent option={label} {...contentRenderProps} disabled={disabled} />
58
+ ),
59
+ };
60
+ });
61
+ }
@@ -0,0 +1,35 @@
1
+ import {
2
+ AccordionOption,
3
+ AnyType,
4
+ BaseOption,
5
+ ContentRenderProps,
6
+ GroupOption,
7
+ GroupSelectOption,
8
+ NestListOption,
9
+ } from '../types';
10
+
11
+ export function isBaseOption<T extends ContentRenderProps = ContentRenderProps>(
12
+ option: AnyType,
13
+ ): option is BaseOption<T> {
14
+ return !('options' in option);
15
+ }
16
+ export function isAccordionOption<T extends ContentRenderProps = ContentRenderProps>(
17
+ option: AnyType,
18
+ ): option is AccordionOption<T> {
19
+ return option && 'options' in option && option['type'] === 'collapse';
20
+ }
21
+ export function isNextListOption<T extends ContentRenderProps = ContentRenderProps>(
22
+ option: AnyType,
23
+ ): option is NestListOption<T> {
24
+ return option && 'options' in option && option['type'] === 'next-list';
25
+ }
26
+ export function isGroupOption<T extends ContentRenderProps = ContentRenderProps>(
27
+ option: AnyType,
28
+ ): option is GroupOption<T> {
29
+ return option && 'options' in option && option['type'] === 'group';
30
+ }
31
+ export function isGroupSelectOption<T extends ContentRenderProps = ContentRenderProps>(
32
+ option: AnyType,
33
+ ): option is GroupSelectOption<T> {
34
+ return option && 'options' in option && option['type'] === 'group-select';
35
+ }