@true-engineering/true-react-common-ui-kit 1.10.0 → 1.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/Select/Select.styles.d.ts +9 -10
- package/dist/components/Select/SelectList/SelectList.d.ts +2 -1
- package/dist/true-react-common-ui-kit.js +49 -24
- package/dist/true-react-common-ui-kit.js.map +1 -1
- package/dist/true-react-common-ui-kit.umd.cjs +49 -24
- package/dist/true-react-common-ui-kit.umd.cjs.map +1 -1
- package/package.json +1 -1
- package/src/components/Button/Button.tsx +97 -85
- package/src/components/Select/MultiSelect.stories.tsx +1 -0
- package/src/components/Select/Select.styles.ts +10 -12
- package/src/components/Select/Select.tsx +33 -14
- package/src/components/Select/SelectList/SelectList.tsx +4 -3
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
useMemo,
|
|
3
|
-
FC,
|
|
4
3
|
ReactElement,
|
|
5
4
|
ReactNode,
|
|
6
5
|
ButtonHTMLAttributes,
|
|
7
6
|
MouseEvent,
|
|
7
|
+
forwardRef,
|
|
8
8
|
} from 'react';
|
|
9
9
|
import clsx from 'clsx';
|
|
10
10
|
import merge from 'lodash-es/merge';
|
|
@@ -102,94 +102,106 @@ export interface IButtonProps extends ICommonProps {
|
|
|
102
102
|
onMouseDown?(event: MouseEvent<HTMLButtonElement>): void | Promise<void>;
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
-
export const Button
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
105
|
+
export const Button = forwardRef<HTMLButtonElement, IButtonProps>(
|
|
106
|
+
(
|
|
107
|
+
{
|
|
108
|
+
type = 'button',
|
|
109
|
+
children,
|
|
110
|
+
size = 'l',
|
|
111
|
+
view = 'primary',
|
|
112
|
+
isFullWidth = false,
|
|
113
|
+
isInline = false,
|
|
114
|
+
isDisabled = false,
|
|
115
|
+
isActive = false,
|
|
116
|
+
isLoading = false,
|
|
117
|
+
shouldSkipTabNavigation = false,
|
|
118
|
+
data,
|
|
119
|
+
testId,
|
|
120
|
+
tweakStyles,
|
|
121
|
+
icon,
|
|
122
|
+
iconPosition = 'left',
|
|
123
|
+
preloaderType = 'dots',
|
|
124
|
+
onClick,
|
|
125
|
+
onMouseDown,
|
|
126
|
+
},
|
|
127
|
+
ref,
|
|
128
|
+
) => {
|
|
129
|
+
const { classes, componentStyles } = useTheme(
|
|
130
|
+
'Button',
|
|
131
|
+
styles,
|
|
132
|
+
tweakStyles,
|
|
133
|
+
);
|
|
126
134
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
135
|
+
const tweakPreloaderStyles = useMemo(
|
|
136
|
+
() =>
|
|
137
|
+
merge(
|
|
138
|
+
{},
|
|
139
|
+
size === 's' || size === 'm' ? dotsPreloaderStyles : undefined,
|
|
140
|
+
componentStyles.tweakPreloader,
|
|
141
|
+
tweakStyles?.tweakPreloader,
|
|
142
|
+
) as ThemedPreloaderStyles,
|
|
143
|
+
[tweakStyles?.tweakPreloader, size],
|
|
144
|
+
);
|
|
137
145
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
146
|
+
const hasIcon = isNotEmpty(icon);
|
|
147
|
+
const hasChildren = isNotEmpty(children);
|
|
148
|
+
const hasNoAction = isDisabled || isLoading;
|
|
141
149
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
tabIndex={shouldSkipTabNavigation ? -1 : undefined}
|
|
154
|
-
disabled={hasNoAction}
|
|
155
|
-
onClick={!hasNoAction ? onClick : undefined}
|
|
156
|
-
onMouseDown={!hasNoAction ? onMouseDown : undefined}
|
|
157
|
-
{...addDataTestId(testId)}
|
|
158
|
-
{...addDataAttributes(data)}
|
|
159
|
-
>
|
|
160
|
-
<span
|
|
161
|
-
className={clsx(classes.content, {
|
|
162
|
-
[classes.iconFromRight]:
|
|
163
|
-
hasChildren && hasIcon && iconPosition === 'right',
|
|
164
|
-
[classes.iconFromLeft]:
|
|
165
|
-
hasChildren && hasIcon && iconPosition === 'left',
|
|
150
|
+
return (
|
|
151
|
+
<button
|
|
152
|
+
ref={ref}
|
|
153
|
+
type={type}
|
|
154
|
+
className={clsx(classes.root, classes[size], classes[view], {
|
|
155
|
+
[classes.disabled]: isDisabled,
|
|
156
|
+
[classes.fullWidth]: isFullWidth,
|
|
157
|
+
[classes.inline]: isInline,
|
|
158
|
+
[classes.active]: isActive,
|
|
159
|
+
[classes.loading]: isLoading,
|
|
160
|
+
[classes.onlyIcon]: hasIcon && !hasChildren,
|
|
166
161
|
})}
|
|
162
|
+
tabIndex={shouldSkipTabNavigation ? -1 : undefined}
|
|
163
|
+
disabled={hasNoAction}
|
|
164
|
+
onClick={!hasNoAction ? onClick : undefined}
|
|
165
|
+
onMouseDown={!hasNoAction ? onMouseDown : undefined}
|
|
166
|
+
{...addDataTestId(testId)}
|
|
167
|
+
{...addDataAttributes(data)}
|
|
167
168
|
>
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
169
|
+
<span
|
|
170
|
+
className={clsx(classes.content, {
|
|
171
|
+
[classes.iconFromRight]:
|
|
172
|
+
hasChildren && hasIcon && iconPosition === 'right',
|
|
173
|
+
[classes.iconFromLeft]:
|
|
174
|
+
hasChildren && hasIcon && iconPosition === 'left',
|
|
175
|
+
})}
|
|
176
|
+
>
|
|
177
|
+
{hasIcon && (
|
|
178
|
+
<span className={classes.icon}>
|
|
179
|
+
{typeof icon === 'string' ? (
|
|
180
|
+
<Icon type={icon as IIconType} />
|
|
181
|
+
) : (
|
|
182
|
+
icon
|
|
183
|
+
)}
|
|
184
|
+
</span>
|
|
185
|
+
)}
|
|
186
|
+
{hasChildren && (
|
|
187
|
+
<span
|
|
188
|
+
className={clsx(classes.children, hasIcon && classes.withIcon)}
|
|
189
|
+
>
|
|
190
|
+
{children}
|
|
191
|
+
</span>
|
|
192
|
+
)}
|
|
193
|
+
</span>
|
|
194
|
+
|
|
195
|
+
{isLoading && (
|
|
196
|
+
<span className={classes.loader}>
|
|
197
|
+
<ThemedPreloader
|
|
198
|
+
type={preloaderType}
|
|
199
|
+
useCurrentColor
|
|
200
|
+
tweakStyles={tweakPreloaderStyles}
|
|
201
|
+
/>
|
|
180
202
|
</span>
|
|
181
203
|
)}
|
|
182
|
-
</
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
<ThemedPreloader
|
|
187
|
-
type={preloaderType}
|
|
188
|
-
useCurrentColor
|
|
189
|
-
tweakStyles={tweakPreloaderStyles}
|
|
190
|
-
/>
|
|
191
|
-
</span>
|
|
192
|
-
)}
|
|
193
|
-
</button>
|
|
194
|
-
);
|
|
195
|
-
};
|
|
204
|
+
</button>
|
|
205
|
+
);
|
|
206
|
+
},
|
|
207
|
+
);
|
|
@@ -52,22 +52,20 @@ export const styles = {
|
|
|
52
52
|
},
|
|
53
53
|
},
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
paddingRight: 32,
|
|
58
|
-
},
|
|
59
|
-
|
|
60
|
-
withUnits: {
|
|
55
|
+
counter: {
|
|
56
|
+
'&:not(:last-child)': {
|
|
61
57
|
paddingRight: 8,
|
|
62
58
|
},
|
|
59
|
+
},
|
|
63
60
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
61
|
+
icon: {
|
|
62
|
+
width: 16,
|
|
63
|
+
height: 16,
|
|
64
|
+
},
|
|
68
65
|
|
|
69
|
-
|
|
70
|
-
|
|
66
|
+
tweakInput: {
|
|
67
|
+
input: {
|
|
68
|
+
paddingRight: 32,
|
|
71
69
|
},
|
|
72
70
|
|
|
73
71
|
disabled: {
|
|
@@ -44,6 +44,7 @@ import { SelectStyles, styles } from './Select.styles';
|
|
|
44
44
|
import { ISearchInputProps, SearchInput } from '../SearchInput';
|
|
45
45
|
import { IMultipleSelectValue } from './types';
|
|
46
46
|
import { ALL_OPTION_INDEX, DEFAULT_OPTION_INDEX } from './constants';
|
|
47
|
+
import { renderIcon } from '../../helpers/snippets';
|
|
47
48
|
|
|
48
49
|
export interface ISelectProps<Value>
|
|
49
50
|
extends Omit<IInputProps, 'value' | 'onChange' | 'onBlur' | 'type'> {
|
|
@@ -114,7 +115,7 @@ export function Select<Value>(
|
|
|
114
115
|
dropdownIcon = 'chevron-down',
|
|
115
116
|
shouldScrollToList = true,
|
|
116
117
|
searchInput,
|
|
117
|
-
|
|
118
|
+
iconType,
|
|
118
119
|
onChange,
|
|
119
120
|
onFocus,
|
|
120
121
|
onBlur,
|
|
@@ -167,6 +168,19 @@ export function Select<Value>(
|
|
|
167
168
|
return filter(options, searchValue);
|
|
168
169
|
}, [optionsFilter, options, convertValueToString, searchValue, optionsMode]);
|
|
169
170
|
|
|
171
|
+
const availableOptions = useMemo(
|
|
172
|
+
() => options.filter((o) => !isOptionDisabled(o)),
|
|
173
|
+
[options, isOptionDisabled],
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
const areAllOptionsSelected =
|
|
177
|
+
isMultiSelect && value?.length === availableOptions.length;
|
|
178
|
+
const shouldShowMultiSelectCounter =
|
|
179
|
+
isMultiSelect &&
|
|
180
|
+
isNotEmpty(value) &&
|
|
181
|
+
value.length > 1 &&
|
|
182
|
+
!areAllOptionsSelected;
|
|
183
|
+
|
|
170
184
|
const optionsIndexesForNavigation = useMemo(() => {
|
|
171
185
|
const result: number[] = [];
|
|
172
186
|
if (shouldShowDefaultOption && hasDefaultOption) {
|
|
@@ -190,9 +204,7 @@ export function Select<Value>(
|
|
|
190
204
|
: undefined;
|
|
191
205
|
// Для мультиселекта пытаемся показать "Все опции" если выбраны все опции
|
|
192
206
|
const showedStringValue =
|
|
193
|
-
|
|
194
|
-
value?.length === filteredOptions.length &&
|
|
195
|
-
isNotEmpty(allOptionsLabel)
|
|
207
|
+
areAllOptionsSelected && isNotEmpty(allOptionsLabel)
|
|
196
208
|
? allOptionsLabel
|
|
197
209
|
: stringValue;
|
|
198
210
|
|
|
@@ -292,7 +304,7 @@ export function Select<Value>(
|
|
|
292
304
|
return;
|
|
293
305
|
}
|
|
294
306
|
if (index === ALL_OPTION_INDEX && isSelected) {
|
|
295
|
-
handleOnChange(
|
|
307
|
+
handleOnChange(availableOptions as IMultipleSelectValue<Value>);
|
|
296
308
|
return;
|
|
297
309
|
}
|
|
298
310
|
const option = filteredOptions[index];
|
|
@@ -374,7 +386,7 @@ export function Select<Value>(
|
|
|
374
386
|
if (isMultiSelect) {
|
|
375
387
|
let isThisValueAlreadySelected: boolean;
|
|
376
388
|
if (indexToSelect === ALL_OPTION_INDEX) {
|
|
377
|
-
isThisValueAlreadySelected =
|
|
389
|
+
isThisValueAlreadySelected = areAllOptionsSelected;
|
|
378
390
|
} else {
|
|
379
391
|
// подумать над концептом реального фокуса на опциях, а не вот эти вот focusedCell
|
|
380
392
|
const valueIdToSelect = convertToId(filteredOptions[indexToSelect]);
|
|
@@ -456,6 +468,7 @@ export function Select<Value>(
|
|
|
456
468
|
{},
|
|
457
469
|
componentStyles.tweakInput,
|
|
458
470
|
{ ...(hasReadonlyInput && { input: { cursor: 'pointer' } }) },
|
|
471
|
+
{ ...(isMultiSelect && { inputIcon: { width: 'auto' } }) },
|
|
459
472
|
tweakStyles?.tweakInput,
|
|
460
473
|
) as Styles,
|
|
461
474
|
[tweakStyles?.tweakInput, hasReadonlyInput],
|
|
@@ -519,6 +532,7 @@ export function Select<Value>(
|
|
|
519
532
|
: undefined
|
|
520
533
|
}
|
|
521
534
|
allOptionsLabel={shouldShowAllOption ? allOptionsLabel : undefined}
|
|
535
|
+
areAllOptionsSelected={areAllOptionsSelected}
|
|
522
536
|
customListHeader={
|
|
523
537
|
hasSearchInputInList ? (
|
|
524
538
|
<SearchInput
|
|
@@ -554,6 +568,18 @@ export function Select<Value>(
|
|
|
554
568
|
</div>
|
|
555
569
|
);
|
|
556
570
|
|
|
571
|
+
const multiSelectCounterWithIcon =
|
|
572
|
+
shouldShowMultiSelectCounter || isNotEmpty(iconType) ? (
|
|
573
|
+
<>
|
|
574
|
+
{shouldShowMultiSelectCounter && (
|
|
575
|
+
<div className={classes.counter}>(+{value.length - 1})</div>
|
|
576
|
+
)}
|
|
577
|
+
{isNotEmpty(iconType) && (
|
|
578
|
+
<div className={classes.icon}>{renderIcon(iconType)}</div>
|
|
579
|
+
)}
|
|
580
|
+
</>
|
|
581
|
+
) : undefined;
|
|
582
|
+
|
|
557
583
|
return (
|
|
558
584
|
<div className={classes.root} onKeyDown={handleKeyDown}>
|
|
559
585
|
<div
|
|
@@ -577,14 +603,7 @@ export function Select<Value>(
|
|
|
577
603
|
isLoading={areOptionsLoading}
|
|
578
604
|
tweakStyles={tweakInputStyles}
|
|
579
605
|
testId={testId}
|
|
580
|
-
|
|
581
|
-
isMultiSelect &&
|
|
582
|
-
isNotEmpty(value) &&
|
|
583
|
-
value.length > 1 &&
|
|
584
|
-
value.length !== options.length
|
|
585
|
-
? `(+${value.length - 1})`
|
|
586
|
-
: units
|
|
587
|
-
}
|
|
606
|
+
iconType={isMultiSelect ? multiSelectCounterWithIcon : iconType}
|
|
588
607
|
{...inputProps}
|
|
589
608
|
/>
|
|
590
609
|
<div
|
|
@@ -20,6 +20,7 @@ export interface ISelectListProps<Value> extends ICommonProps {
|
|
|
20
20
|
defaultOptionLabel?: string;
|
|
21
21
|
testId?: string;
|
|
22
22
|
allOptionsLabel?: string;
|
|
23
|
+
areAllOptionsSelected?: boolean;
|
|
23
24
|
shouldScrollToList?: boolean;
|
|
24
25
|
customListHeader?: ReactNode;
|
|
25
26
|
onOptionSelect(index: number, event: MouseEvent<HTMLElement>): void;
|
|
@@ -41,6 +42,7 @@ export function SelectList<Value>({
|
|
|
41
42
|
tweakStyles,
|
|
42
43
|
testId,
|
|
43
44
|
shouldScrollToList = true,
|
|
45
|
+
areAllOptionsSelected,
|
|
44
46
|
customListHeader,
|
|
45
47
|
isOptionDisabled,
|
|
46
48
|
allOptionsLabel,
|
|
@@ -118,10 +120,9 @@ export function SelectList<Value>({
|
|
|
118
120
|
classes={classes}
|
|
119
121
|
index={ALL_OPTION_INDEX}
|
|
120
122
|
isSemiChecked={
|
|
121
|
-
selectedOptionsCount > 0 &&
|
|
122
|
-
selectedOptionsCount < options.length
|
|
123
|
+
selectedOptionsCount > 0 && !areAllOptionsSelected
|
|
123
124
|
}
|
|
124
|
-
isActive={
|
|
125
|
+
isActive={areAllOptionsSelected}
|
|
125
126
|
isFocused={focusedIndex === ALL_OPTION_INDEX}
|
|
126
127
|
onOptionSelect={onOptionSelect}
|
|
127
128
|
onToggleCheckbox={onToggleCheckbox}
|