@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,62 @@
1
+ import { ReactNode } from 'react';
2
+
3
+ import { ItemId } from '@cloud-ru/uikit-product-mobile-dropdown';
4
+
5
+ import { ContentRenderProps, FilterOption } from '../types';
6
+ import { isBaseOption } from './typeGuards';
7
+
8
+ export type FlattenOption<T extends ContentRenderProps = ContentRenderProps> = {
9
+ value: ItemId;
10
+ label: ItemId;
11
+ contentRenderProps?: T;
12
+ disabled?: boolean;
13
+ hidden?: boolean;
14
+ afterContent?: ReactNode;
15
+ beforeContent?: ReactNode;
16
+ };
17
+
18
+ type KindFlattenOptionsProps<T extends ContentRenderProps = ContentRenderProps> = {
19
+ options: FilterOption<T>[];
20
+ };
21
+
22
+ export function kindFlattenOptions<T extends ContentRenderProps = ContentRenderProps>({
23
+ options,
24
+ }: KindFlattenOptionsProps<T>): {
25
+ flattenOptions: Record<string, FlattenOption<T>>;
26
+ } {
27
+ const flattenOptions: Record<string, FlattenOption<T>> = {};
28
+
29
+ function flatten(option: FilterOption<T>) {
30
+ if (isBaseOption<T>(option)) {
31
+ const { value, label, contentRenderProps, disabled, afterContent, beforeContent, hidden } = option;
32
+
33
+ flattenOptions[value] = {
34
+ value,
35
+ label,
36
+ contentRenderProps,
37
+ disabled,
38
+ afterContent,
39
+ beforeContent,
40
+ hidden,
41
+ };
42
+
43
+ return;
44
+ }
45
+
46
+ const { options } = option;
47
+
48
+ for (let idx = 0; idx < options.length; idx++) {
49
+ flatten(options[idx]);
50
+ }
51
+
52
+ return;
53
+ }
54
+
55
+ for (let idx = 0; idx < options.length; idx++) {
56
+ flatten(options[idx]);
57
+ }
58
+
59
+ return {
60
+ flattenOptions,
61
+ };
62
+ }
@@ -0,0 +1,275 @@
1
+ import cn from 'classnames';
2
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
3
+ import { useUncontrolledProp } from 'uncontrollable';
4
+
5
+ import { CrossSVG, PlusSVG } from '@cloud-ru/uikit-product-icons';
6
+ import { useLocale } from '@cloud-ru/uikit-product-locale';
7
+ import { MobileDroplist, MobileDroplistProps } from '@cloud-ru/uikit-product-mobile-dropdown';
8
+ import { MobileTooltip } from '@cloud-ru/uikit-product-mobile-tooltip';
9
+ import { ButtonFunction } from '@snack-uikit/button';
10
+ import { Divider } from '@snack-uikit/divider';
11
+ import { extractSupportProps, WithSupportProps } from '@snack-uikit/utils';
12
+
13
+ import { CHIP_CHOICE_ROW_IDS } from '../../constants';
14
+ import { ForwardedChipChoice } from './components';
15
+ import { CHIP_CHOICE_ROW_SIZE, MAP_ROW_SIZE_TO_BUTTON_SIZE, MAP_ROW_SIZE_TO_CHOICE_SIZE } from './constants';
16
+ import styles from './styles.module.scss';
17
+ import { ChipChoiceProps, ChipChoiceRowSize, FilterValue, OmitBetter } from './types';
18
+
19
+ export type FiltersState = Record<string, unknown>;
20
+
21
+ export type MobileChipChoiceRowFilter = OmitBetter<ChipChoiceProps, 'onChange' | 'value' | 'size' | 'defaultValue'> & {
22
+ pinned?: boolean;
23
+ };
24
+
25
+ export type MobileChipChoiceRowProps<TState extends FiltersState> = WithSupportProps<{
26
+ /** Состояние фильтров */
27
+ value?: TState;
28
+ /** Начальное состояние фильтров */
29
+ defaultValue?: Partial<TState>;
30
+ /** Колбек изменения состояния фильтров */
31
+ onChange?(filters: TState): void;
32
+ /** Массив чипов */
33
+ filters: MobileChipChoiceRowFilter[];
34
+ /** Размер @default 's' */
35
+ size?: ChipChoiceRowSize;
36
+ /** CSS-класс */
37
+ className?: string;
38
+ /** Скрыть/показать кнопку очиски фильтров @default true */
39
+ showClearButton?: boolean;
40
+ /** Скрыть/показать кнопку добавления фильров @default true */
41
+ showAddButton?: boolean;
42
+ /** Состояние для видимых фильтров */
43
+ visibleFilters?: string[];
44
+ /** Коллбек на изменение видимых фильтров */
45
+ onVisibleFiltersChange?(value: string[]): void;
46
+ }>;
47
+
48
+ export function MobileChipChoiceRow<TState extends FiltersState>({
49
+ filters,
50
+ onChange,
51
+ showClearButton: showClearButtonProp = true,
52
+ showAddButton = true,
53
+ className,
54
+ value,
55
+ defaultValue: defaultValueProp,
56
+ size = CHIP_CHOICE_ROW_SIZE.S,
57
+ visibleFilters: visibleFiltersProp,
58
+ onVisibleFiltersChange,
59
+ ...rest
60
+ }: MobileChipChoiceRowProps<TState>) {
61
+ const { t } = useLocale('Chips');
62
+
63
+ const defaultValue = useMemo(() => (defaultValueProp ?? {}) as TState, [defaultValueProp]);
64
+
65
+ const [state, setState] = useUncontrolledProp<TState>(value, defaultValue, newState => {
66
+ const result = typeof newState === 'function' ? newState(state) : newState;
67
+ onChange?.(result);
68
+ });
69
+
70
+ const [addListValue, setAddListValue] = useUncontrolledProp<string[]>(
71
+ visibleFiltersProp,
72
+ Object.keys(state),
73
+ newState => {
74
+ const result = typeof newState === 'function' ? newState(addListValue) : newState;
75
+ onVisibleFiltersChange?.(result);
76
+ },
77
+ );
78
+
79
+ const [openedChip, setOpenedChip] = useState<string>('');
80
+
81
+ const [addListOpen, setAddListOpen] = useState(false);
82
+
83
+ const handleChange = (fieldId: string, value: FilterValue) => {
84
+ setState((state: TState) => ({
85
+ ...state,
86
+ [fieldId]: value,
87
+ }));
88
+ };
89
+
90
+ const handleChipOpen = useCallback(
91
+ (filterId: string) => (isOpen: boolean) => {
92
+ setOpenedChip(isOpen ? filterId : '');
93
+ },
94
+ [],
95
+ );
96
+
97
+ const handleFiltersClear = () => {
98
+ const defaultState = filters.reduce((res, filter) => {
99
+ if (filter.pinned) {
100
+ return { ...res, [filter.id]: defaultValue[filter.id] } as TState;
101
+ }
102
+
103
+ return res;
104
+ }, {} as TState);
105
+
106
+ setState(defaultState);
107
+ setAddListValue([]);
108
+ };
109
+
110
+ const { pinnedFilters, nonPinnedFilters } = useMemo(
111
+ () =>
112
+ filters.reduce(
113
+ (res, filter) => {
114
+ if (filter.pinned) {
115
+ res.pinnedFilters.push(filter);
116
+ } else {
117
+ res.nonPinnedFilters.push(filter);
118
+ }
119
+
120
+ return res;
121
+ },
122
+ { pinnedFilters: [] as MobileChipChoiceRowFilter[], nonPinnedFilters: [] as MobileChipChoiceRowFilter[] },
123
+ ),
124
+ [filters],
125
+ );
126
+
127
+ const visibleFilters = useMemo(
128
+ () =>
129
+ addListValue.reduce((res, filterId) => {
130
+ const filter = nonPinnedFilters.find(filter => filter.id === filterId);
131
+
132
+ if (filter) {
133
+ res.push(filter);
134
+ }
135
+
136
+ return res;
137
+ }, [] as MobileChipChoiceRowFilter[]),
138
+ [addListValue, nonPinnedFilters],
139
+ );
140
+
141
+ const hasAnyFilter = useMemo(
142
+ () => visibleFilters.length > 0 || pinnedFilters.some(filter => state[filter.id] !== defaultValue[filter.id]),
143
+ [defaultValue, pinnedFilters, state, visibleFilters.length],
144
+ );
145
+
146
+ const handleClearPinnedFilter = (filterId: string) => {
147
+ const defaultFilterValue = defaultValue[filterId];
148
+
149
+ if (state[filterId] === defaultFilterValue) {
150
+ return;
151
+ }
152
+
153
+ return () => setState((prevState: TState) => ({ ...prevState, [filterId]: defaultFilterValue }));
154
+ };
155
+
156
+ const handleRemoveVisibleFilter = (filterId: string) => () => {
157
+ setAddListValue((prev?: string[]) => prev?.filter(item => filterId !== item));
158
+ setState((prevState: TState) => ({ ...prevState, [filterId]: undefined }));
159
+ };
160
+
161
+ const addSelectorOptions = useMemo(
162
+ () =>
163
+ nonPinnedFilters.reduce(
164
+ (res, filter, index) => {
165
+ if (addListValue.includes(filter.id)) {
166
+ return res;
167
+ }
168
+
169
+ res.push({
170
+ id: filter.id,
171
+ content: { option: filter.label ?? filter.id },
172
+ onClick: () => {
173
+ setAddListValue(function (prevValue?: string[]) {
174
+ return [...(prevValue ?? []), filter.id];
175
+ });
176
+ setAddListOpen(false);
177
+ },
178
+ 'data-test-id': `${CHIP_CHOICE_ROW_IDS.addButtonOption}-${filter['data-test-id'] ?? index}`,
179
+ });
180
+
181
+ return res;
182
+ },
183
+ [] as MobileDroplistProps['items'],
184
+ ),
185
+ [addListValue, nonPinnedFilters, setAddListValue],
186
+ );
187
+
188
+ const canAddChips = addSelectorOptions.length > 0;
189
+
190
+ const addListPrevValue = useRef(addListValue);
191
+
192
+ useEffect(() => {
193
+ const prevValue = addListPrevValue.current;
194
+
195
+ if (addListValue.length > prevValue.length) {
196
+ const newItem = addListValue.find(item => !prevValue.includes(item));
197
+
198
+ if (newItem) {
199
+ handleChipOpen(newItem)(true);
200
+ }
201
+ }
202
+
203
+ addListPrevValue.current = addListValue;
204
+ }, [addListValue, handleChipOpen]);
205
+
206
+ const showClearButton = showClearButtonProp && hasAnyFilter;
207
+ const showPinnedFiltersDivider = showAddButton || showClearButton || visibleFilters.length > 0;
208
+
209
+ return (
210
+ <div className={cn(styles.chipChoiceRow, className)} {...extractSupportProps(rest)}>
211
+ {pinnedFilters.length > 0 && (
212
+ <div className={styles.pinnedItems}>
213
+ {pinnedFilters.map(filter => (
214
+ <ForwardedChipChoice
215
+ key={filter.id}
216
+ {...filter}
217
+ value={state[filter.id] as never}
218
+ size={MAP_ROW_SIZE_TO_CHOICE_SIZE[size]}
219
+ onChange={(value: FilterValue) => handleChange(filter.id, value)}
220
+ onClearButtonClick={handleClearPinnedFilter(filter.id)}
221
+ />
222
+ ))}
223
+
224
+ {showPinnedFiltersDivider && <Divider orientation='vertical' className={styles.divider} />}
225
+ </div>
226
+ )}
227
+
228
+ {visibleFilters.map(filter => (
229
+ <ForwardedChipChoice
230
+ key={filter.id}
231
+ {...filter}
232
+ value={state[filter.id] as never}
233
+ size={MAP_ROW_SIZE_TO_CHOICE_SIZE[size]}
234
+ onChange={(value: FilterValue) => handleChange(filter.id, value)}
235
+ onClearButtonClick={handleRemoveVisibleFilter(filter.id)}
236
+ open={openedChip === filter.id}
237
+ onOpenChange={handleChipOpen(filter.id)}
238
+ />
239
+ ))}
240
+
241
+ <div className={styles.controlWrapper}>
242
+ {showAddButton && (
243
+ <MobileTooltip
244
+ tip={t('addButtonDisabledTip')}
245
+ open={canAddChips ? false : undefined}
246
+ placement='bottom'
247
+ data-test-id={CHIP_CHOICE_ROW_IDS.addButtonTooltip}
248
+ >
249
+ <MobileDroplist open={canAddChips && addListOpen} onOpenChange={setAddListOpen} items={addSelectorOptions}>
250
+ <ButtonFunction
251
+ disabled={!canAddChips}
252
+ label={t('add')}
253
+ icon={<PlusSVG />}
254
+ iconPosition='before'
255
+ size={MAP_ROW_SIZE_TO_BUTTON_SIZE[size]}
256
+ data-test-id={CHIP_CHOICE_ROW_IDS.addButton}
257
+ />
258
+ </MobileDroplist>
259
+ </MobileTooltip>
260
+ )}
261
+
262
+ {showClearButton && (
263
+ <ButtonFunction
264
+ onClick={handleFiltersClear}
265
+ label={t('clear')}
266
+ icon={<CrossSVG />}
267
+ iconPosition='before'
268
+ size={MAP_ROW_SIZE_TO_BUTTON_SIZE[size]}
269
+ data-test-id={CHIP_CHOICE_ROW_IDS.clearButton}
270
+ />
271
+ )}
272
+ </div>
273
+ </div>
274
+ );
275
+ }
@@ -0,0 +1,10 @@
1
+ import { JSXElementConstructor } from 'react';
2
+
3
+ import { ChipChoiceProps } from '../types';
4
+ import { MAP_CHIP_TYPE_TO_COMPONENT } from './constants';
5
+
6
+ export function ForwardedChipChoice(props: ChipChoiceProps) {
7
+ const Component = MAP_CHIP_TYPE_TO_COMPONENT[props.type] as unknown as JSXElementConstructor<ChipChoiceProps>;
8
+
9
+ return <Component {...props} />;
10
+ }
@@ -0,0 +1,12 @@
1
+ import { MobileChipChoice } from '../../MobileChipChoice';
2
+ import { CHIP_CHOICE_TYPE } from '../../MobileChipChoice/constants';
3
+
4
+ export const MAP_CHIP_TYPE_TO_COMPONENT = {
5
+ [CHIP_CHOICE_TYPE.Single]: MobileChipChoice.Single,
6
+ [CHIP_CHOICE_TYPE.Multiple]: MobileChipChoice.Multiple,
7
+ [CHIP_CHOICE_TYPE.Date]: MobileChipChoice.Date,
8
+ [CHIP_CHOICE_TYPE.DateTime]: MobileChipChoice.Date,
9
+ [CHIP_CHOICE_TYPE.DateRange]: MobileChipChoice.DateRange,
10
+ [CHIP_CHOICE_TYPE.Time]: MobileChipChoice.Time,
11
+ [CHIP_CHOICE_TYPE.Custom]: MobileChipChoice.Custom,
12
+ };
@@ -0,0 +1 @@
1
+ export * from './ForwardedChipChoice';
@@ -0,0 +1,21 @@
1
+ import { ButtonFunctionProps } from '@snack-uikit/button';
2
+
3
+ import { SIZE } from '../../constants';
4
+
5
+ export const CHIP_CHOICE_ROW_SIZE = {
6
+ Xs: 'xs',
7
+ S: 's',
8
+ M: 'm',
9
+ } as const;
10
+
11
+ export const MAP_ROW_SIZE_TO_CHOICE_SIZE = {
12
+ [CHIP_CHOICE_ROW_SIZE.Xs]: SIZE.Xs,
13
+ [CHIP_CHOICE_ROW_SIZE.S]: SIZE.S,
14
+ [CHIP_CHOICE_ROW_SIZE.M]: SIZE.M,
15
+ };
16
+
17
+ export const MAP_ROW_SIZE_TO_BUTTON_SIZE: Record<string, ButtonFunctionProps['size']> = {
18
+ [CHIP_CHOICE_ROW_SIZE.Xs]: 'xs',
19
+ [CHIP_CHOICE_ROW_SIZE.S]: 's',
20
+ [CHIP_CHOICE_ROW_SIZE.M]: 'm',
21
+ };
@@ -0,0 +1,2 @@
1
+ export * from './MobileChipChoiceRow';
2
+ export { hasFilterBeenApplied } from '@snack-uikit/chips';
@@ -0,0 +1,32 @@
1
+ @use '@sbercloud/figma-tokens-cloud-platform/build/scss/components/styles-tokens-chips-chipChoice' as chips;
2
+
3
+ .chipChoiceRow {
4
+ display: flex;
5
+ flex-wrap: wrap;
6
+
7
+ @include chips.composite-var(chips.$chip-choice-row-container);
8
+ }
9
+
10
+ .pinnedItems {
11
+ @include chips.composite-var(chips.$chip-choice-row-pinned);
12
+
13
+ display: flex;
14
+ flex-wrap: wrap;
15
+ min-width: 0;
16
+ }
17
+
18
+ .controlWrapper {
19
+ @include chips.composite-var(chips.$chip-choice-row-control-wrapper);
20
+
21
+ display: flex;
22
+ flex-wrap: nowrap;
23
+ }
24
+
25
+ .divider {
26
+ align-self: stretch;
27
+ height: auto;
28
+ }
29
+
30
+ .addButtonWrapper {
31
+ display: inline-flex;
32
+ }
@@ -0,0 +1,60 @@
1
+ import { ValueOf } from '@snack-uikit/utils';
2
+
3
+ import {
4
+ MobileChipChoiceCustomProps,
5
+ MobileChipChoiceDateProps,
6
+ MobileChipChoiceDateRangeProps,
7
+ MobileChipChoiceTimeProps,
8
+ } from '../MobileChipChoice/components';
9
+ import { CHIP_CHOICE_TYPE } from '../MobileChipChoice/constants';
10
+ import { MobileChipChoiceMultipleProps, MobileChipChoiceSingleProps } from '../MobileChipChoice/types';
11
+ import { CHIP_CHOICE_ROW_SIZE } from './constants';
12
+
13
+ export type ChipChoiceRowSize = ValueOf<typeof CHIP_CHOICE_ROW_SIZE>;
14
+
15
+ type ChipChoiceMultipleType = {
16
+ type: typeof CHIP_CHOICE_TYPE.Multiple;
17
+ } & MobileChipChoiceMultipleProps;
18
+
19
+ type ChipChoiceSingleType = {
20
+ type: typeof CHIP_CHOICE_TYPE.Single;
21
+ } & MobileChipChoiceSingleProps;
22
+
23
+ type ChipChoiceDateType = {
24
+ type: typeof CHIP_CHOICE_TYPE.Date;
25
+ } & MobileChipChoiceDateProps;
26
+
27
+ type ChipChoiceDateTimeType = {
28
+ type: typeof CHIP_CHOICE_TYPE.DateTime;
29
+ } & Omit<MobileChipChoiceDateProps, 'mode'> & { mode: 'date-time'; showSeconds?: boolean };
30
+
31
+ type ChipChoiceDateRangeType = {
32
+ type: typeof CHIP_CHOICE_TYPE.DateRange;
33
+ } & MobileChipChoiceDateRangeProps;
34
+
35
+ type ChipChoiceTimeType = {
36
+ type: typeof CHIP_CHOICE_TYPE.Time;
37
+ } & MobileChipChoiceTimeProps;
38
+
39
+ type ChipChoiceCustomType = {
40
+ type: typeof CHIP_CHOICE_TYPE.Custom;
41
+ } & MobileChipChoiceCustomProps;
42
+
43
+ export type ChipChoiceProps = {
44
+ id: string;
45
+ } & (
46
+ | ChipChoiceMultipleType
47
+ | ChipChoiceSingleType
48
+ | ChipChoiceDateType
49
+ | ChipChoiceDateTimeType
50
+ | ChipChoiceDateRangeType
51
+ | ChipChoiceTimeType
52
+ | ChipChoiceCustomType
53
+ );
54
+
55
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
56
+ export type OmitBetter<T, K extends keyof any> = T extends any ? Pick<T, Exclude<keyof T, K>> : never;
57
+
58
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
59
+ // @ts-ignore
60
+ export type FilterValue = Parameters<ChipChoiceProps['onChange']>[number];
@@ -0,0 +1,3 @@
1
+ export * from './AdaptiveChips';
2
+ export * from './MobileChipChoice';
3
+ export * from './MobileChipChoiceRow';
@@ -0,0 +1,50 @@
1
+ export const SIZE = {
2
+ Xs: 'xs',
3
+ S: 's',
4
+ M: 'm',
5
+ L: 'l',
6
+ } as const;
7
+
8
+ export const BUTTON_SIZE = {
9
+ Xxs: 'xxs',
10
+ Xs: 'xs',
11
+ } as const;
12
+
13
+ export const VARIANT = {
14
+ LabelOnly: 'label-only',
15
+ IconBefore: 'icon-before',
16
+ } as const;
17
+
18
+ export const CHIP_ASSIST_TEST_IDS = {
19
+ icon: 'chip-assist__icon',
20
+ spinner: 'chip-assist__spinner',
21
+ label: 'chip-assist__label',
22
+ };
23
+
24
+ export const CHIP_CHOICE_TEST_IDS = {
25
+ icon: 'chip-choice__icon',
26
+ label: 'chip-choice__label',
27
+ spinner: 'chip-choice__spinner',
28
+ value: 'chip-choice__value',
29
+ clearButton: 'chip-choice__clear-button',
30
+ droplist: 'chip-choice__droplist',
31
+ footer: 'chip-choice__footer',
32
+ cancelButton: 'chip-choice__cancel-button',
33
+ approveButton: 'chip-choice__approve-button',
34
+ };
35
+
36
+ export const CHIP_CHOICE_ROW_IDS = {
37
+ clearButton: 'chip-choice-row__clear-button',
38
+ addButton: 'chip-choice-row__add-button',
39
+ addButtonTooltip: 'chip-choice-row__add-button-tooltip',
40
+ addButtonOption: 'chip-choice-row__add-button-option',
41
+ };
42
+
43
+ export const CHIP_TOGGLE_TEST_IDS = {
44
+ input: 'chip-toggle__input',
45
+ icon: 'chip-toggle__icon',
46
+ spinner: 'chip-toggle__spinner',
47
+ label: 'chip-toggle__label',
48
+ };
49
+
50
+ export const DEFAULT_EMPTY_VALUE = '—';
@@ -0,0 +1,40 @@
1
+ import { forwardRef, KeyboardEventHandler, MouseEventHandler } from 'react';
2
+
3
+ import { CrossSVG } from '@cloud-ru/uikit-product-icons';
4
+
5
+ import { BUTTON_SIZE } from '../../constants';
6
+ import { ButtonSize } from '../../types';
7
+ import styles from './styles.module.scss';
8
+
9
+ type ButtonClearValueProps = {
10
+ size: ButtonSize;
11
+ onClick: MouseEventHandler<HTMLButtonElement>;
12
+ onKeyDown?: KeyboardEventHandler<HTMLButtonElement>;
13
+ tabIndex?: number;
14
+ 'data-test-id'?: string;
15
+ };
16
+
17
+ export const ButtonClearValue = forwardRef<HTMLButtonElement, ButtonClearValueProps>(
18
+ ({ size, onClick, tabIndex = -1, onKeyDown, 'data-test-id': dataTestId }, ref) => {
19
+ const handleClick: MouseEventHandler<HTMLButtonElement> = event => {
20
+ event.stopPropagation();
21
+ onClick(event);
22
+ };
23
+
24
+ return (
25
+ <button
26
+ className={styles.buttonClearValue}
27
+ data-size={size}
28
+ onClick={handleClick}
29
+ data-test-id={dataTestId}
30
+ type='button'
31
+ ref={ref}
32
+ onKeyDown={onKeyDown}
33
+ tabIndex={tabIndex}
34
+ >
35
+ {size === BUTTON_SIZE.Xxs && <CrossSVG size={16} />}
36
+ {size === BUTTON_SIZE.Xs && <CrossSVG />}
37
+ </button>
38
+ );
39
+ },
40
+ );
@@ -0,0 +1 @@
1
+ export * from './ButtonClearValue';
@@ -0,0 +1,50 @@
1
+ @use "@sbercloud/figma-tokens-cloud-platform/build/scss/components/styles-tokens-element";
2
+ @use "@sbercloud/figma-tokens-cloud-platform/build/scss/components/styles-tokens-chips-chipChoice";
3
+
4
+ $sizes: 'xxs', 'xs';
5
+ $icon-sizes: (
6
+ 'xxs': styles-tokens-element.$icon-xs,
7
+ 'xs': styles-tokens-element.$icon-s
8
+ );
9
+
10
+ .buttonClearValue {
11
+ display: flex;
12
+ align-items: center;
13
+ justify-content: center;
14
+
15
+ margin: 0;
16
+ padding: 0;
17
+
18
+ color: styles-tokens-chips-chipChoice.$sys-neutral-text-light;
19
+
20
+ background-color: transparent;
21
+ border: none;
22
+
23
+ @each $size in $sizes {
24
+ &[data-size='#{$size}'] {
25
+ @include styles-tokens-chips-chipChoice.composite-var(styles-tokens-chips-chipChoice.$chip-choice, 'button-clear-value', $size);
26
+
27
+ svg {
28
+ width: styles-tokens-chips-chipChoice.simple-var($icon-sizes, $size) !important; /* stylelint-disable-line declaration-no-important */
29
+ height: styles-tokens-chips-chipChoice.simple-var($icon-sizes, $size) !important; /* stylelint-disable-line declaration-no-important */
30
+ }
31
+ }
32
+ }
33
+
34
+ &:hover {
35
+ cursor: pointer;
36
+ color: styles-tokens-chips-chipChoice.$sys-neutral-text-support;
37
+ }
38
+
39
+ &:focus-visible {
40
+ @include styles-tokens-chips-chipChoice.outline-var(styles-tokens-element.$container-focused-s);
41
+
42
+ color: styles-tokens-chips-chipChoice.$sys-neutral-text-support;
43
+ outline-color: styles-tokens-chips-chipChoice.$sys-available-complementary;
44
+ outline-offset: styles-tokens-chips-chipChoice.$spacing-state-focus-offset;
45
+ }
46
+
47
+ &:active {
48
+ color: styles-tokens-chips-chipChoice.$sys-neutral-text-main;
49
+ }
50
+ }
@@ -0,0 +1,37 @@
1
+ import cn from 'classnames';
2
+
3
+ import { extractSupportProps, WithSupportProps } from '@snack-uikit/utils';
4
+
5
+ import styles from './styles.module.scss';
6
+
7
+ export type ItemContentProps = WithSupportProps<{
8
+ option: string | number;
9
+ caption?: string;
10
+ description?: string;
11
+ disabled?: boolean;
12
+ className?: string;
13
+ }>;
14
+
15
+ export function ItemContent({ caption, description, option, className, disabled, ...rest }: ItemContentProps) {
16
+ return (
17
+ <div
18
+ className={cn(styles.content, className)}
19
+ {...extractSupportProps(rest)}
20
+ data-size='l'
21
+ data-disabled={disabled || undefined}
22
+ >
23
+ <div className={styles.headline}>
24
+ <div className={styles.label} data-test-id='list__base-item-option'>
25
+ {option}
26
+ </div>
27
+ {caption && <span className={styles.caption}>{caption}</span>}
28
+ </div>
29
+
30
+ {description && (
31
+ <div className={styles.description} data-test-id='list__base-item-description'>
32
+ {description}
33
+ </div>
34
+ )}
35
+ </div>
36
+ );
37
+ }
@@ -0,0 +1 @@
1
+ export * from './ItemContent';