@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.
- package/CHANGELOG.md +1178 -0
- package/LICENSE +201 -0
- package/README.md +8 -0
- package/package.json +60 -0
- package/src/components/AdaptiveChips/AdaptiveChips.tsx +21 -0
- package/src/components/AdaptiveChips/index.ts +1 -0
- package/src/components/MobileChipChoice/components/ChipChoiceBase/ChipChoiceBase.tsx +148 -0
- package/src/components/MobileChipChoice/components/ChipChoiceBase/index.ts +1 -0
- package/src/components/MobileChipChoice/components/ChipChoiceBase/styles.module.scss +89 -0
- package/src/components/MobileChipChoice/components/MobileChipChoiceCustom.tsx +81 -0
- package/src/components/MobileChipChoice/components/MobileChipChoiceDate.tsx +135 -0
- package/src/components/MobileChipChoice/components/MobileChipChoiceDateRange.tsx +101 -0
- package/src/components/MobileChipChoice/components/MobileChipChoiceMultiple.tsx +218 -0
- package/src/components/MobileChipChoice/components/MobileChipChoiceSingle.tsx +176 -0
- package/src/components/MobileChipChoice/components/MobileChipChoiceTime.tsx +122 -0
- package/src/components/MobileChipChoice/components/index.ts +6 -0
- package/src/components/MobileChipChoice/components/styles.module.scss +36 -0
- package/src/components/MobileChipChoice/constants.ts +20 -0
- package/src/components/MobileChipChoice/hooks.tsx +127 -0
- package/src/components/MobileChipChoice/index.ts +38 -0
- package/src/components/MobileChipChoice/styles.module.scss +103 -0
- package/src/components/MobileChipChoice/types.ts +139 -0
- package/src/components/MobileChipChoice/utils/index.ts +3 -0
- package/src/components/MobileChipChoice/utils/options.tsx +61 -0
- package/src/components/MobileChipChoice/utils/typeGuards.ts +35 -0
- package/src/components/MobileChipChoice/utils/utils.ts +62 -0
- package/src/components/MobileChipChoiceRow/MobileChipChoiceRow.tsx +275 -0
- package/src/components/MobileChipChoiceRow/components/ForwardedChipChoice.tsx +10 -0
- package/src/components/MobileChipChoiceRow/components/constants.ts +12 -0
- package/src/components/MobileChipChoiceRow/components/index.ts +1 -0
- package/src/components/MobileChipChoiceRow/constants.ts +21 -0
- package/src/components/MobileChipChoiceRow/index.ts +2 -0
- package/src/components/MobileChipChoiceRow/styles.module.scss +32 -0
- package/src/components/MobileChipChoiceRow/types.ts +60 -0
- package/src/components/index.ts +3 -0
- package/src/constants.ts +50 -0
- package/src/helperComponents/ButtonClearValue/ButtonClearValue.tsx +40 -0
- package/src/helperComponents/ButtonClearValue/index.ts +1 -0
- package/src/helperComponents/ButtonClearValue/styles.module.scss +50 -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 +2 -0
- package/src/index.ts +1 -0
- package/src/styles.module.scss +113 -0
- 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,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];
|
package/src/constants.ts
ADDED
|
@@ -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';
|