@true-engineering/true-react-common-ui-kit 3.14.2 → 3.15.1
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/README.md +13 -0
- package/dist/components/TextArea/TextArea.d.ts +10 -10
- package/dist/components/TextArea/TextArea.stories.d.ts +3 -2
- package/dist/components/TextArea/TextArea.styles.d.ts +1 -1
- package/dist/components/TextArea/index.d.ts +1 -0
- package/dist/components/TextArea/types.d.ts +2 -0
- package/dist/true-react-common-ui-kit.js +543 -486
- package/dist/true-react-common-ui-kit.js.map +1 -1
- package/dist/true-react-common-ui-kit.umd.cjs +543 -486
- package/dist/true-react-common-ui-kit.umd.cjs.map +1 -1
- package/package.json +1 -1
- package/src/components/MultiSelectList/MultiSelectList.tsx +6 -9
- package/src/components/TextArea/TextArea.stories.tsx +1 -0
- package/src/components/TextArea/TextArea.styles.ts +24 -4
- package/src/components/TextArea/TextArea.tsx +137 -138
- package/src/components/TextArea/index.ts +1 -0
- package/src/components/TextArea/types.ts +6 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useEffect, useState, useMemo, useRef, useCallback, ReactNode } from 'react';
|
|
2
2
|
import clsx from 'clsx';
|
|
3
3
|
import { debounce } from 'ts-debounce';
|
|
4
|
-
import { isNotEmpty } from '@true-engineering/true-react-platform-helpers';
|
|
4
|
+
import { isArrayNotEmpty, isNotEmpty } from '@true-engineering/true-react-platform-helpers';
|
|
5
5
|
import { addDataAttributes } from '../../helpers';
|
|
6
6
|
import { useIsMounted, useTweakStyles } from '../../hooks';
|
|
7
7
|
import { ICommonProps } from '../../types';
|
|
@@ -317,20 +317,17 @@ export function MultiSelectList<Value extends IMultiSelectListValues<Option>, Op
|
|
|
317
317
|
|
|
318
318
|
const mainOptionsList = isGroupingEnabled ? unchosenOptions : allOptions;
|
|
319
319
|
|
|
320
|
-
const hasSelectedOptionsGroup =
|
|
321
|
-
isGroupingEnabled && chosenValues !== undefined && chosenValues.length > 0;
|
|
320
|
+
const hasSelectedOptionsGroup = isGroupingEnabled && isArrayNotEmpty(chosenValues);
|
|
322
321
|
|
|
323
|
-
const shouldShowNothingFoundMessage = !isLoading && allOptions
|
|
322
|
+
const shouldShowNothingFoundMessage = !isLoading && !isArrayNotEmpty(allOptions);
|
|
324
323
|
|
|
325
324
|
const shouldShowAllOptionsLabel =
|
|
326
|
-
|
|
327
|
-
unchosenOptions.length > 0 &&
|
|
328
|
-
chosenValues !== undefined &&
|
|
329
|
-
chosenValues.length > 0;
|
|
325
|
+
hasSelectedOptionsGroup && (isArrayNotEmpty(unchosenOptions) || !isArrayNotEmpty(allOptions));
|
|
330
326
|
|
|
331
327
|
const shouldShowPreloader = isLoading || isLoadingOptionsOnScroll;
|
|
332
328
|
|
|
333
|
-
const shouldShowOptionsList =
|
|
329
|
+
const shouldShowOptionsList =
|
|
330
|
+
!isLoading && (isArrayNotEmpty(allOptions) || isArrayNotEmpty(chosenValues));
|
|
334
331
|
|
|
335
332
|
return (
|
|
336
333
|
<div className={classes.root} {...addDataAttributes(data)}>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ITweakStyles, animations, createThemedStyles } from '../../theme';
|
|
1
|
+
import { ITweakStyles, animations, createThemedStyles, helpers } from '../../theme';
|
|
2
2
|
|
|
3
3
|
const PADDING_X = 12;
|
|
4
4
|
|
|
@@ -23,10 +23,11 @@ export const useStyles = createThemedStyles('TextArea', {
|
|
|
23
23
|
},
|
|
24
24
|
|
|
25
25
|
textarea: {
|
|
26
|
+
...helpers.withScrollBar,
|
|
27
|
+
|
|
26
28
|
width: '100%',
|
|
27
29
|
height: '100%',
|
|
28
30
|
outline: 'none',
|
|
29
|
-
boxSizing: 'border-box',
|
|
30
31
|
outlineStyle: 'none',
|
|
31
32
|
fontFamily: 'inherit',
|
|
32
33
|
fontSize: 16,
|
|
@@ -35,7 +36,6 @@ export const useStyles = createThemedStyles('TextArea', {
|
|
|
35
36
|
transitionProperty: 'background-color',
|
|
36
37
|
border: 'none',
|
|
37
38
|
resize: 'none',
|
|
38
|
-
overflow: 'auto',
|
|
39
39
|
|
|
40
40
|
'&:disabled': {
|
|
41
41
|
extend: 'disabled',
|
|
@@ -52,13 +52,33 @@ export const useStyles = createThemedStyles('TextArea', {
|
|
|
52
52
|
},
|
|
53
53
|
},
|
|
54
54
|
|
|
55
|
+
autosize: {
|
|
56
|
+
display: 'inline-grid',
|
|
57
|
+
gridTemplateRows: 'minmax(0, 100%)',
|
|
58
|
+
|
|
59
|
+
'& > $textarea, &::after': {
|
|
60
|
+
gridArea: '1 / 1 / 2 / 2',
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
'&::after': {
|
|
64
|
+
extend: 'textarea',
|
|
65
|
+
content: 'attr(data-value) " "',
|
|
66
|
+
overflowWrap: 'break-word',
|
|
67
|
+
whiteSpace: 'pre-wrap',
|
|
68
|
+
visibility: 'hidden',
|
|
69
|
+
scrollbarGutter: 'stable',
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
|
|
55
73
|
focused: {
|
|
56
74
|
position: 'relative',
|
|
57
75
|
zIndex: 1,
|
|
58
76
|
},
|
|
59
77
|
|
|
60
78
|
withFloatingLabel: {
|
|
61
|
-
|
|
79
|
+
'& $textArea, &::after': {
|
|
80
|
+
paddingTop: 24,
|
|
81
|
+
},
|
|
62
82
|
},
|
|
63
83
|
|
|
64
84
|
floating: {},
|
|
@@ -1,21 +1,19 @@
|
|
|
1
|
-
import {
|
|
2
|
-
useRef,
|
|
3
|
-
useState,
|
|
4
|
-
FC,
|
|
5
|
-
useEffect,
|
|
6
|
-
FormEvent,
|
|
7
|
-
FocusEvent,
|
|
8
|
-
ChangeEvent,
|
|
9
|
-
ClipboardEvent,
|
|
10
|
-
} from 'react';
|
|
1
|
+
import { forwardRef, useState, FormEvent, FocusEvent, ChangeEvent, ReactNode } from 'react';
|
|
11
2
|
import clsx from 'clsx';
|
|
3
|
+
import {
|
|
4
|
+
addDataTestId,
|
|
5
|
+
isNotEmpty,
|
|
6
|
+
isReactNodeNotEmpty,
|
|
7
|
+
isStringNotEmpty,
|
|
8
|
+
} from '@true-engineering/true-react-platform-helpers';
|
|
12
9
|
import { addDataAttributes, trimStringToMaxLength } from '../../helpers';
|
|
13
10
|
import { ICommonProps } from '../../types';
|
|
11
|
+
import { ITextAreaHTMLBaseProps } from './types';
|
|
14
12
|
import { useStyles, ITextAreaStyles } from './TextArea.styles';
|
|
15
13
|
|
|
16
|
-
export interface ITextAreaProps extends ICommonProps<ITextAreaStyles
|
|
14
|
+
export interface ITextAreaProps extends ICommonProps<ITextAreaStyles>, ITextAreaHTMLBaseProps {
|
|
17
15
|
value?: string;
|
|
18
|
-
label?:
|
|
16
|
+
label?: ReactNode;
|
|
19
17
|
placeholder?: string;
|
|
20
18
|
/** @default false */
|
|
21
19
|
isDisabled?: boolean;
|
|
@@ -25,11 +23,15 @@ export interface ITextAreaProps extends ICommonProps<ITextAreaStyles> {
|
|
|
25
23
|
isInvalid?: boolean;
|
|
26
24
|
/** @default false */
|
|
27
25
|
isActive?: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Должна ли высота и ширина textarea подстраиваться под содержимое
|
|
28
|
+
* @default true
|
|
29
|
+
*/
|
|
30
|
+
isAutoSizeable?: boolean;
|
|
28
31
|
infoMessage?: string;
|
|
29
32
|
errorMessage?: string;
|
|
30
33
|
/** @default false */
|
|
31
34
|
isRequired?: boolean;
|
|
32
|
-
name?: string;
|
|
33
35
|
/** @default false */
|
|
34
36
|
hasRequiredLabel?: boolean;
|
|
35
37
|
/** @default false */
|
|
@@ -38,143 +40,140 @@ export interface ITextAreaProps extends ICommonProps<ITextAreaStyles> {
|
|
|
38
40
|
hasCounter?: boolean;
|
|
39
41
|
/** @default false */
|
|
40
42
|
shouldTrimAfterMaxLength?: boolean;
|
|
41
|
-
maxLength?: number;
|
|
42
|
-
rows?: number;
|
|
43
43
|
onChange: (value: string, event?: FormEvent<HTMLTextAreaElement>) => void;
|
|
44
|
-
onFocus?: (event: FocusEvent<HTMLTextAreaElement>) => void;
|
|
45
|
-
onBlur?: (event: FocusEvent<HTMLTextAreaElement>) => void;
|
|
46
|
-
onPaste?: (event: ClipboardEvent<HTMLTextAreaElement>) => void;
|
|
47
44
|
}
|
|
48
45
|
|
|
49
46
|
const DEFAULT_VALUE = '';
|
|
50
47
|
|
|
51
|
-
export const TextArea
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
48
|
+
export const TextArea = forwardRef<HTMLTextAreaElement, ITextAreaProps>(
|
|
49
|
+
(
|
|
50
|
+
{
|
|
51
|
+
value = DEFAULT_VALUE,
|
|
52
|
+
label,
|
|
53
|
+
placeholder,
|
|
54
|
+
isDisabled,
|
|
55
|
+
hasFloatingLabel = true,
|
|
56
|
+
isInvalid = false,
|
|
57
|
+
isActive = false,
|
|
58
|
+
infoMessage,
|
|
59
|
+
errorMessage,
|
|
60
|
+
isRequired = false,
|
|
61
|
+
name,
|
|
62
|
+
hasRequiredLabel = false,
|
|
63
|
+
shouldFocusOnMount = false,
|
|
64
|
+
hasCounter = true,
|
|
65
|
+
shouldTrimAfterMaxLength = false,
|
|
66
|
+
isAutoSizeable = true,
|
|
67
|
+
maxLength,
|
|
68
|
+
rows,
|
|
69
|
+
testId,
|
|
70
|
+
data,
|
|
71
|
+
tweakStyles,
|
|
72
|
+
onChange,
|
|
73
|
+
onPaste,
|
|
74
|
+
onFocus,
|
|
75
|
+
onBlur,
|
|
76
|
+
},
|
|
77
|
+
ref,
|
|
78
|
+
) => {
|
|
79
|
+
const classes = useStyles({ theme: tweakStyles });
|
|
80
80
|
|
|
81
|
-
|
|
82
|
-
const newValue = event.currentTarget.value;
|
|
83
|
-
onChange(
|
|
84
|
-
shouldTrimAfterMaxLength && maxLength !== undefined
|
|
85
|
-
? trimStringToMaxLength(newValue, maxLength)
|
|
86
|
-
: newValue,
|
|
87
|
-
event,
|
|
88
|
-
);
|
|
89
|
-
};
|
|
81
|
+
const [isFocused, setFocused] = useState(false);
|
|
90
82
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
83
|
+
const handleOnChange = (event: ChangeEvent<HTMLTextAreaElement>) => {
|
|
84
|
+
const rawValue = event.currentTarget.value;
|
|
85
|
+
const newValue =
|
|
86
|
+
shouldTrimAfterMaxLength && maxLength !== undefined
|
|
87
|
+
? trimStringToMaxLength(rawValue, maxLength)
|
|
88
|
+
: rawValue;
|
|
97
89
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
if (onBlur !== undefined) {
|
|
101
|
-
onBlur(event);
|
|
102
|
-
}
|
|
103
|
-
};
|
|
90
|
+
onChange(newValue, event);
|
|
91
|
+
};
|
|
104
92
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
93
|
+
const handleOnFocus = (event: FocusEvent<HTMLTextAreaElement>) => {
|
|
94
|
+
setFocused(true);
|
|
95
|
+
if (onFocus !== undefined) {
|
|
96
|
+
onFocus(event);
|
|
97
|
+
}
|
|
98
|
+
};
|
|
109
99
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
onPaste,
|
|
117
|
-
disabled: isDisabled,
|
|
118
|
-
placeholder: hasPlaceholder ? placeholder : undefined,
|
|
119
|
-
name,
|
|
120
|
-
autoFocus: shouldFocusOnMount,
|
|
121
|
-
'data-testid': testId,
|
|
122
|
-
rows,
|
|
123
|
-
};
|
|
100
|
+
const handleOnBlur = (event: FocusEvent<HTMLTextAreaElement>) => {
|
|
101
|
+
setFocused(false);
|
|
102
|
+
if (onBlur !== undefined) {
|
|
103
|
+
onBlur(event);
|
|
104
|
+
}
|
|
105
|
+
};
|
|
124
106
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
// Нужно для того, чтобы TextArea уменьшалась при стирании значения.
|
|
131
|
-
textarea.style.height = 'auto';
|
|
132
|
-
textarea.style.height = `${textarea.scrollHeight}px`;
|
|
133
|
-
}, [value]);
|
|
107
|
+
const hasFocus = isFocused || isActive;
|
|
108
|
+
// в hasValue нельзя использовать isStringNotEmpty из-за того что isStringNotEmpty делает trim
|
|
109
|
+
const hasValue = value !== undefined && value !== '';
|
|
110
|
+
const hasLabel = isReactNodeNotEmpty(label);
|
|
111
|
+
const hasPlaceholder = (!hasLabel || hasFocus) && isStringNotEmpty(placeholder);
|
|
134
112
|
|
|
135
|
-
|
|
136
|
-
|
|
113
|
+
const props = {
|
|
114
|
+
className: classes.textarea,
|
|
115
|
+
onFocus: handleOnFocus,
|
|
116
|
+
onBlur: handleOnBlur,
|
|
117
|
+
onChange: handleOnChange,
|
|
118
|
+
value,
|
|
119
|
+
onPaste,
|
|
120
|
+
disabled: isDisabled,
|
|
121
|
+
placeholder: hasPlaceholder ? placeholder : undefined,
|
|
122
|
+
name,
|
|
123
|
+
autoFocus: shouldFocusOnMount,
|
|
124
|
+
rows,
|
|
125
|
+
...addDataTestId(testId),
|
|
126
|
+
};
|
|
137
127
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
<div
|
|
141
|
-
className={clsx(classes.wrapper, {
|
|
142
|
-
[classes.required]: isRequired && !hasRequiredLabel,
|
|
143
|
-
[classes.invalid]: isInvalid,
|
|
144
|
-
[classes.focused]: hasFocus,
|
|
145
|
-
[classes.disabled]: isDisabled,
|
|
146
|
-
})}
|
|
147
|
-
>
|
|
148
|
-
{label && (
|
|
149
|
-
<span
|
|
150
|
-
className={clsx(classes.label, {
|
|
151
|
-
[classes.invalidLabel]: isInvalid,
|
|
152
|
-
[classes.requiredLabel]: hasRequiredLabel && !isRequired,
|
|
153
|
-
[classes.activeLabel]: hasFocus || hasValue,
|
|
154
|
-
[classes.floating]: hasFloatingLabel,
|
|
155
|
-
})}
|
|
156
|
-
>
|
|
157
|
-
{label}
|
|
158
|
-
</span>
|
|
159
|
-
)}
|
|
160
|
-
<textarea ref={ref} {...props} />
|
|
161
|
-
</div>
|
|
128
|
+
const hasInfoMessage = isStringNotEmpty(infoMessage);
|
|
129
|
+
const hasErrorMessage = isStringNotEmpty(errorMessage);
|
|
162
130
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
131
|
+
return (
|
|
132
|
+
<div className={classes.root} {...addDataAttributes(data)}>
|
|
133
|
+
<div
|
|
134
|
+
className={clsx(classes.wrapper, {
|
|
135
|
+
[classes.required]: isRequired && !hasRequiredLabel,
|
|
136
|
+
[classes.invalid]: isInvalid,
|
|
137
|
+
[classes.focused]: hasFocus,
|
|
138
|
+
[classes.disabled]: isDisabled,
|
|
139
|
+
[classes.autosize]: isAutoSizeable,
|
|
140
|
+
[classes.withFloatingLabel]: hasFloatingLabel && hasLabel,
|
|
141
|
+
})}
|
|
142
|
+
data-value={isAutoSizeable ? value : undefined}
|
|
143
|
+
>
|
|
144
|
+
{hasLabel && (
|
|
145
|
+
<span
|
|
146
|
+
className={clsx(classes.label, {
|
|
147
|
+
[classes.invalidLabel]: isInvalid,
|
|
148
|
+
[classes.requiredLabel]: hasRequiredLabel && !isRequired,
|
|
149
|
+
[classes.activeLabel]: hasFocus || hasValue,
|
|
150
|
+
[classes.floating]: hasFloatingLabel,
|
|
151
|
+
})}
|
|
152
|
+
>
|
|
153
|
+
{label}
|
|
154
|
+
</span>
|
|
155
|
+
)}
|
|
156
|
+
|
|
157
|
+
<textarea ref={ref} {...props} />
|
|
158
|
+
</div>
|
|
159
|
+
|
|
160
|
+
<div className={classes.footer}>
|
|
161
|
+
{hasInfoMessage && <div className={classes.info}>{infoMessage}</div>}
|
|
162
|
+
{!hasInfoMessage && hasErrorMessage && (
|
|
163
|
+
<div className={classes.error}>{errorMessage}</div>
|
|
164
|
+
)}
|
|
165
|
+
{hasCounter && isNotEmpty(maxLength) && (
|
|
166
|
+
<span
|
|
167
|
+
className={clsx(classes.symbolsCount, {
|
|
168
|
+
[classes.symbolsCountError]: value.length > maxLength,
|
|
169
|
+
})}
|
|
170
|
+
>
|
|
171
|
+
{value.length} / {maxLength}
|
|
172
|
+
</span>
|
|
173
|
+
)}
|
|
174
|
+
</div>
|
|
175
|
+
{hasInfoMessage && hasErrorMessage && <div className={classes.error}>{errorMessage}</div>}
|
|
176
176
|
</div>
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
};
|
|
177
|
+
);
|
|
178
|
+
},
|
|
179
|
+
);
|