@true-engineering/true-react-common-ui-kit 4.0.0-alpha20 → 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-alpha20",
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": [
@@ -4,7 +4,7 @@ import { iconsList } from '../Icon';
4
4
  import { ControlWrapper } from './ControlWrapper';
5
5
 
6
6
  export default {
7
- title: 'ControlWrapper',
7
+ title: 'Controls/ControlWrapper',
8
8
  component: ControlWrapper,
9
9
  };
10
10
 
@@ -26,4 +26,6 @@ Default.args = {
26
26
  isDisabled: false,
27
27
  isRequired: false,
28
28
  isReadonly: false,
29
+ icon: 'question',
30
+ isClearable: true,
29
31
  };
@@ -1,41 +1,44 @@
1
1
  import { animations, colors, createThemedStyles, dimensions, ITweakStyles } from '../../theme';
2
2
  import { IInputStyles } from '../Input';
3
3
 
4
- export const BUTTONS_WIDTH = 36;
5
- export const BUTTONS_GAP = 2;
6
-
7
- export const useStyles = createThemedStyles('IncrementInput', {
8
- root: {
9
- display: 'flex',
10
- alignItems: 'center',
11
- position: 'relative',
4
+ export const inputStyles: IInputStyles = {
5
+ tweakControlWrapper: {
6
+ controlWrapper: {
7
+ '--increment-buttons-width': '36px',
8
+ '--increment-buttons-gap': '1px',
9
+ '--increment-buttons-margin': '1px',
10
+ '--increment-button-icon-size': '16px',
11
+ },
12
12
  },
13
+ };
13
14
 
15
+ export const useStyles = createThemedStyles('IncrementInput', {
14
16
  buttons: {
15
17
  display: 'flex',
16
18
  flexDirection: 'column',
17
- width: BUTTONS_WIDTH,
18
- position: 'absolute',
19
- right: BUTTONS_GAP,
20
- top: BUTTONS_GAP,
21
- bottom: BUTTONS_GAP,
19
+ height: 'calc(100% - var(--increment-buttons-margin) * 2)',
20
+ margin: 'var(--increment-buttons-margin)',
21
+ gap: 'var(--increment-buttons-gap)',
22
+ flexShrink: 0,
23
+ order: 1000, // сдвигаем кнопки на самый конец
24
+
22
25
  zIndex: dimensions.Z_INDEX.CONTROL_FOCUS + 1,
23
26
  },
24
27
 
25
28
  button: {
26
- width: '100%',
27
- height: (dimensions.CONTROL.HEIGHT - 5) / 2,
29
+ display: 'flex',
30
+ alignItems: 'center',
31
+ justifyContent: 'center',
32
+ width: 'var(--increment-buttons-width)',
33
+ flexGrow: 1,
34
+
35
+ cursor: 'pointer',
28
36
  border: 'none',
29
37
  outline: 'none',
38
+ color: colors.FONT_MEDIUM,
30
39
  backgroundColor: colors.GREY_BACKGROUND,
31
40
  transition: animations.defaultTransition,
32
- transitionProperty: 'background-color',
33
- color: colors.FONT_MEDIUM,
34
- cursor: 'pointer',
35
- display: 'flex',
36
- alignItems: 'center',
37
- justifyContent: 'center',
38
- padding: [0, 10],
41
+ transitionProperty: 'background-color, color',
39
42
 
40
43
  '&:hover, &:focus': {
41
44
  backgroundColor: colors.GREY_HOVER,
@@ -44,16 +47,12 @@ export const useStyles = createThemedStyles('IncrementInput', {
44
47
  '&:active': {
45
48
  backgroundColor: colors.GREY_ACTIVE,
46
49
  },
47
-
48
- '&:first-child': {
49
- marginBottom: 1,
50
- },
51
50
  },
52
51
 
53
52
  disabledButton: {
54
- backgroundColor: colors.CLASSIC_WHITE,
55
- color: colors.FONT_DISABLED,
56
53
  cursor: 'default',
54
+ color: colors.FONT_DISABLED,
55
+ backgroundColor: colors.CLASSIC_WHITE,
57
56
 
58
57
  '&:hover': {
59
58
  backgroundColor: colors.CLASSIC_WHITE,
@@ -61,18 +60,11 @@ export const useStyles = createThemedStyles('IncrementInput', {
61
60
  },
62
61
 
63
62
  errorButton: {},
64
- });
65
-
66
- export const inputStyles: IInputStyles = {
67
- input: {
68
- paddingRight: BUTTONS_WIDTH + BUTTONS_GAP,
69
- },
70
63
 
71
- tweakControlWrapper: {
72
- controls: {
73
- paddingRight: BUTTONS_WIDTH + BUTTONS_GAP,
74
- },
64
+ icon: {
65
+ width: 'var(--increment-button-icon-size)',
66
+ height: 'var(--increment-button-icon-size)',
75
67
  },
76
- };
68
+ });
77
69
 
78
70
  export type IIncrementInputStyles = ITweakStyles<typeof useStyles, { tweakInput: IInputStyles }>;
@@ -1,7 +1,7 @@
1
1
  import { FC, useEffect } from 'react';
2
2
  import clsx from 'clsx';
3
- import { addDataTestId, isNumberInteger } from '@true-engineering/true-react-platform-helpers';
4
- import { addDataAttributes, getNumberInRange, getNumberLength } from '../../helpers';
3
+ import { isNotEmpty, isNumberInteger } from '@true-engineering/true-react-platform-helpers';
4
+ import { getNumberInRange, getNumberLength } from '../../helpers';
5
5
  import { useTweakStyles } from '../../hooks';
6
6
  import { ICommonProps } from '../../types';
7
7
  import { Icon } from '../Icon';
@@ -22,8 +22,6 @@ export const IncrementInput: FC<IIncrementInputProps> = ({
22
22
  max,
23
23
  step = 1,
24
24
  intPartPrecision,
25
- data,
26
- testId,
27
25
  tweakStyles,
28
26
  isDisabled,
29
27
  isReadonly,
@@ -39,14 +37,15 @@ export const IncrementInput: FC<IIncrementInputProps> = ({
39
37
 
40
38
  const classes = useStyles({ theme: tweakStyles });
41
39
 
40
+ const isEditDisabled = isDisabled || isReadonly;
41
+
42
42
  const isIncreaseDisabled =
43
- isDisabled ||
44
- isReadonly ||
45
- (intPartPrecision !== undefined && getNumberLength((value ?? 0) + step) > intPartPrecision) ||
46
- (max !== undefined && value !== undefined && value >= max);
43
+ isEditDisabled ||
44
+ (isNotEmpty(intPartPrecision) && getNumberLength((value ?? 0) + step) > intPartPrecision) ||
45
+ (isNotEmpty(max) && isNotEmpty(value) && value >= max);
47
46
 
48
47
  const isDecreaseDisabled =
49
- isDisabled || isReadonly || (min !== undefined && value !== undefined ? value <= min : false);
48
+ isEditDisabled || (isNotEmpty(min) && isNotEmpty(value) && value <= min);
50
49
 
51
50
  const increment = (): void => {
52
51
  onChange(getNumberInRange((value ?? 0) + step, min, max));
@@ -63,19 +62,18 @@ export const IncrementInput: FC<IIncrementInputProps> = ({
63
62
  }, [step]);
64
63
 
65
64
  return (
66
- <div className={classes.root} {...addDataTestId(testId)} {...addDataAttributes(data)}>
67
- <NumberInput
68
- {...props}
69
- isInvalid={isInvalid}
70
- isDisabled={isDisabled}
71
- isReadonly={isReadonly}
72
- min={min}
73
- max={max}
74
- intPartPrecision={intPartPrecision}
75
- onChange={onChange}
76
- value={value}
77
- tweakStyles={tweakInputStyles}
78
- />
65
+ <NumberInput
66
+ {...props}
67
+ isInvalid={isInvalid}
68
+ isDisabled={isDisabled}
69
+ isReadonly={isReadonly}
70
+ min={min}
71
+ max={max}
72
+ intPartPrecision={intPartPrecision}
73
+ onChange={onChange}
74
+ value={value}
75
+ tweakStyles={tweakInputStyles}
76
+ >
79
77
  <div className={classes.buttons}>
80
78
  <button
81
79
  className={clsx(classes.button, {
@@ -86,8 +84,11 @@ export const IncrementInput: FC<IIncrementInputProps> = ({
86
84
  disabled={isIncreaseDisabled}
87
85
  onClick={increment}
88
86
  >
89
- <Icon type="plus" />
87
+ <div className={classes.icon}>
88
+ <Icon type="plus" />
89
+ </div>
90
90
  </button>
91
+
91
92
  <button
92
93
  className={clsx(classes.button, {
93
94
  [classes.disabledButton]: isDecreaseDisabled,
@@ -97,9 +98,11 @@ export const IncrementInput: FC<IIncrementInputProps> = ({
97
98
  disabled={isDecreaseDisabled}
98
99
  onClick={decrement}
99
100
  >
100
- <Icon type="minus" />
101
+ <div className={classes.icon}>
102
+ <Icon type="minus" />
103
+ </div>
101
104
  </button>
102
105
  </div>
103
- </div>
106
+ </NumberInput>
104
107
  );
105
108
  };
@@ -33,13 +33,13 @@ export interface IInputBaseProps
33
33
  IControlWrapperProps,
34
34
  | 'label'
35
35
  | 'icon'
36
+ | 'size'
36
37
  | 'groupPlacement'
37
38
  | 'isInvalid'
38
39
  | 'isRequired'
39
40
  | 'isLoading'
40
41
  | 'isDisabled'
41
42
  | 'onIconClick'
42
- | 'size'
43
43
  >,
44
44
  Pick<
45
45
  Partial<ReactInputMaskBaseProps>,
@@ -103,6 +103,7 @@ export const InputBase = forwardRef<HTMLInputElement, IInputBaseProps>(
103
103
  isRequired,
104
104
  size,
105
105
  onIconClick,
106
+ children,
106
107
  ...inputProps
107
108
  },
108
109
  ref,
@@ -248,6 +249,7 @@ export const InputBase = forwardRef<HTMLInputElement, IInputBaseProps>(
248
249
  ) : (
249
250
  inputContent
250
251
  )}
252
+ {children}
251
253
  </ControlWrapper>
252
254
  );
253
255
  },
@@ -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;
@@ -4,7 +4,7 @@ import { IInputProps, Input } from '../Input';
4
4
  import { WithMessages } from './WithMessages';
5
5
 
6
6
  export default {
7
- title: 'WithMessages',
7
+ title: 'Data Display/WithMessages',
8
8
  component: WithMessages,
9
9
  };
10
10