@true-engineering/true-react-common-ui-kit 4.0.0-alpha21 → 4.0.0-alpha22

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@true-engineering/true-react-common-ui-kit",
3
- "version": "4.0.0-alpha21",
3
+ "version": "4.0.0-alpha22",
4
4
  "description": "True Engineering React UI Kit with theming support",
5
5
  "author": "True Engineering (https://trueengineering.ru)",
6
6
  "keywords": [
@@ -1,7 +1,10 @@
1
1
  import { useState } from 'react';
2
2
  import { ComponentStory } from '@storybook/react';
3
+ import { IExtendableProps } from '../../types';
3
4
  import { TextArea } from './TextArea';
4
5
 
6
+ const COUNTER_POSITIONS = ['default', 'top'] as const;
7
+
5
8
  export default {
6
9
  title: 'Inputs/TextArea',
7
10
  component: TextArea,
@@ -12,9 +15,14 @@ export default {
12
15
  },
13
16
  };
14
17
 
18
+ declare module './types' {
19
+ // eslint-disable-next-line @typescript-eslint/no-empty-interface
20
+ export interface ITextAreaCounterPositions extends IExtendableProps<typeof COUNTER_POSITIONS> {}
21
+ }
22
+
15
23
  const Template: ComponentStory<typeof TextArea> = (args) => {
16
24
  const [value, setValue] = useState('');
17
- return <TextArea {...args} value={value} onChange={(v) => setValue(v)} />;
25
+ return <TextArea {...args} value={value} onChange={setValue} />;
18
26
  };
19
27
 
20
28
  export const Default = Template.bind({});
@@ -26,6 +34,7 @@ Default.args = {
26
34
  rows: 5,
27
35
  maxLength: 500,
28
36
  isInvalid: false,
37
+ counterPosition: 'default',
29
38
  errorMessage: 'Error Text',
30
39
  isActive: false,
31
40
  isDisabled: false,
@@ -33,3 +42,7 @@ Default.args = {
33
42
  isAutoSized: true,
34
43
  shouldAlwaysShowPlaceholder: false,
35
44
  };
45
+
46
+ Default.argTypes = {
47
+ counterPosition: { options: [undefined, ...COUNTER_POSITIONS], control: 'select' },
48
+ };
@@ -1,8 +1,14 @@
1
1
  import { animations, createThemedStyles, helpers, ITweakStyles } from '../../theme';
2
+ import { IWithPrefix } from '../../types';
2
3
  import { IControlWrapperStyles } from '../ControlWrapper';
3
4
  import { IWithMessagesStyles } from '../WithMessages';
5
+ import { ITextAreaCounterPositions } from './types';
4
6
 
5
7
  export const useStyles = createThemedStyles('TextArea', {
8
+ root: {
9
+ width: '100%',
10
+ },
11
+
6
12
  textarea: {
7
13
  ...helpers.withScrollBar,
8
14
  width: '100%',
@@ -66,6 +72,10 @@ export const useStyles = createThemedStyles('TextArea', {
66
72
  color: 'red',
67
73
  },
68
74
 
75
+ symbolsCountActive: {},
76
+
77
+ 'counter-default': {},
78
+
69
79
  withLabel: {},
70
80
  });
71
81
 
@@ -74,5 +84,5 @@ export type ITextAreaStyles = ITweakStyles<
74
84
  {
75
85
  tweakWithMessages: IWithMessagesStyles;
76
86
  tweakControlWrapper: IControlWrapperStyles;
77
- }
87
+ } & IWithPrefix<ITextAreaCounterPositions, 'counter-'>
78
88
  >;
@@ -6,6 +6,7 @@ import {
6
6
  TextareaHTMLAttributes,
7
7
  useState,
8
8
  } from 'react';
9
+ import { Styles } from 'react-jss';
9
10
  import clsx from 'clsx';
10
11
  import {
11
12
  addDataAttributes,
@@ -19,6 +20,7 @@ import { useTweakStyles } from '../../hooks';
19
20
  import { ICommonProps } from '../../types';
20
21
  import { ControlWrapper, IControlWrapperProps } from '../ControlWrapper';
21
22
  import { IWithMessagesProps, WithMessages } from '../WithMessages';
23
+ import { ITextAreaCounterPosition } from './types';
22
24
  import { ITextAreaStyles, useStyles } from './TextArea.styles';
23
25
 
24
26
  export interface ITextAreaProps
@@ -31,6 +33,7 @@ export interface ITextAreaProps
31
33
  Pick<IWithMessagesProps, 'infoMessage' | 'errorMessage'> {
32
34
  value?: string;
33
35
  placeholder?: string;
36
+ counterPosition?: ITextAreaCounterPosition;
34
37
  /** @default false */
35
38
  isActive?: boolean;
36
39
  /**
@@ -49,14 +52,13 @@ export interface ITextAreaProps
49
52
  onChange: (value: string, event?: FormEvent<HTMLTextAreaElement>) => void;
50
53
  }
51
54
 
52
- const DEFAULT_VALUE = '';
53
-
54
55
  export const TextArea = forwardRef<HTMLTextAreaElement, ITextAreaProps>(
55
56
  (
56
57
  {
57
- value = DEFAULT_VALUE,
58
+ value = '',
58
59
  placeholder,
59
60
  name,
61
+ counterPosition = 'default',
60
62
  shouldFocusOnMount,
61
63
  hasCounter = true,
62
64
  shouldTrimAfterMaxLength,
@@ -88,6 +90,9 @@ export const TextArea = forwardRef<HTMLTextAreaElement, ITextAreaProps>(
88
90
  ) => {
89
91
  const classes = useStyles({ theme: tweakStyles });
90
92
 
93
+ const shouldShowCounter = hasCounter && isNotEmpty(maxLength);
94
+ const counterString = `${value.length} / ${maxLength}`;
95
+
91
96
  const tweakWithMessagesStyles = useTweakStyles({
92
97
  tweakStyles,
93
98
  className: 'tweakWithMessages',
@@ -123,60 +128,65 @@ export const TextArea = forwardRef<HTMLTextAreaElement, ITextAreaProps>(
123
128
  };
124
129
 
125
130
  return (
126
- <WithMessages
127
- errorMessage={errorMessage}
128
- infoMessage={infoMessage}
129
- tweakStyles={tweakWithMessagesStyles}
130
- testId={getTestId(testId, 'wrapper')}
131
- >
132
- <ControlWrapper
133
- label={label}
134
- tweakStyles={tweakControlWrapperStyles}
135
- isFocused={hasFocus}
136
- isDisabled={isDisabled}
137
- hasValue={hasValue}
138
- isInvalid={isInvalid}
139
- isRequired={isRequired}
140
- groupPlacement={groupPlacement}
141
- size={size}
142
- isFullWidth
131
+ <div className={classes.root} style={{ '--counter-length': counterString.length } as Styles}>
132
+ <WithMessages
133
+ errorMessage={errorMessage}
134
+ infoMessage={infoMessage}
135
+ tweakStyles={tweakWithMessagesStyles}
136
+ testId={getTestId(testId, 'wrapper')}
143
137
  >
144
- <div
145
- className={clsx(classes.wrapper, {
146
- [classes.autoSized]: isAutoSized,
147
- [classes.withLabel]: hasLabel,
148
- })}
149
- {...(isAutoSized && { 'data-value': value })}
150
- >
151
- <textarea
152
- ref={ref}
153
- className={classes.textarea}
154
- value={value}
155
- disabled={isDisabled}
156
- placeholder={hasPlaceholder ? placeholder : undefined}
157
- maxLength={shouldTrimAfterMaxLength ? maxLength : undefined}
158
- name={name}
159
- rows={rows}
160
- onPaste={onPaste}
161
- onFocus={handleFocus}
162
- onBlur={handleBlur}
163
- onChange={handleChange}
164
- autoFocus={shouldFocusOnMount}
165
- {...addDataAttributes(data, testId)}
166
- {...textAreaProps}
167
- />
168
- </div>
169
- </ControlWrapper>
170
- {hasCounter && isNotEmpty(maxLength) && (
171
- <span
172
- className={clsx(classes.symbolsCount, {
173
- [classes.symbolsCountError]: value.length > maxLength,
174
- })}
138
+ <ControlWrapper
139
+ label={label}
140
+ tweakStyles={tweakControlWrapperStyles}
141
+ isFocused={hasFocus}
142
+ isDisabled={isDisabled}
143
+ hasValue={hasValue}
144
+ isInvalid={isInvalid}
145
+ isRequired={isRequired}
146
+ groupPlacement={groupPlacement}
147
+ size={size}
148
+ isFullWidth
175
149
  >
176
- {value.length} / {maxLength}
177
- </span>
178
- )}
179
- </WithMessages>
150
+ <div
151
+ className={clsx(classes.wrapper, isNotEmpty(size) && classes[size], {
152
+ [classes.autoSized]: isAutoSized,
153
+ [classes.withLabel]: hasLabel,
154
+ })}
155
+ // Не менять на addDataAttributes
156
+ {...(isAutoSized && { 'data-value': value })}
157
+ >
158
+ <textarea
159
+ ref={ref}
160
+ className={classes.textarea}
161
+ value={value}
162
+ disabled={isDisabled}
163
+ placeholder={hasPlaceholder ? placeholder : undefined}
164
+ maxLength={shouldTrimAfterMaxLength ? maxLength : undefined}
165
+ name={name}
166
+ rows={rows}
167
+ onPaste={onPaste}
168
+ onFocus={handleFocus}
169
+ onBlur={handleBlur}
170
+ onChange={handleChange}
171
+ autoFocus={shouldFocusOnMount}
172
+ {...addDataAttributes(data, testId)}
173
+ {...textAreaProps}
174
+ />
175
+ {shouldShowCounter && (
176
+ <span
177
+ className={clsx(classes.symbolsCount, classes[`counter-${counterPosition}`], {
178
+ [classes.symbolsCountError]: value.length > maxLength,
179
+ [classes.symbolsCountActive]: hasFocus || hasValue,
180
+ })}
181
+ {...addDataAttributes({ counterPosition })}
182
+ >
183
+ {counterString}
184
+ </span>
185
+ )}
186
+ </div>
187
+ </ControlWrapper>
188
+ </WithMessages>
189
+ </div>
180
190
  );
181
191
  },
182
192
  );
@@ -0,0 +1,6 @@
1
+ // eslint-disable-next-line @typescript-eslint/no-empty-interface
2
+ import { IExtendableProps } from '../../types';
3
+
4
+ export interface ITextAreaCounterPositions extends IExtendableProps<'default'> {}
5
+
6
+ export type ITextAreaCounterPosition = keyof ITextAreaCounterPositions;