@true-engineering/true-react-common-ui-kit 4.0.0-alpha29 → 4.0.0-alpha30

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-alpha29",
3
+ "version": "4.0.0-alpha30",
4
4
  "description": "True Engineering React UI Kit with theming support",
5
5
  "author": "True Engineering (https://trueengineering.ru)",
6
6
  "keywords": [
@@ -1,6 +1,6 @@
1
1
  import { doNothing } from '@true-engineering/true-react-platform-helpers';
2
2
  import { ComponentStory } from '@storybook/react';
3
- import { iconsList } from '../Icon';
3
+ import { Icon, iconsList } from '../Icon';
4
4
  import { ControlWrapper } from './ControlWrapper';
5
5
 
6
6
  export default {
@@ -9,7 +9,7 @@ export default {
9
9
  };
10
10
 
11
11
  const Template: ComponentStory<typeof ControlWrapper> = (args) => (
12
- <ControlWrapper {...args} onClear={doNothing} onIconClick={doNothing}>
12
+ <ControlWrapper {...args} onClear={doNothing}>
13
13
  <div style={{ minWidth: 200, height: 48 }} />
14
14
  </ControlWrapper>
15
15
  );
@@ -24,6 +24,11 @@ Default.args = {
24
24
  isLoading: false,
25
25
  isDisabled: false,
26
26
  hasValue: false,
27
+ icon: [
28
+ { iconComponent: <div>Бу</div>, onClick: () => console.log('close'), shouldResetSize: true },
29
+ { iconComponent: <Icon type="question" /> },
30
+ { iconComponent: 'search', onClick: () => console.log('search') },
31
+ ],
27
32
  };
28
33
 
29
34
  Default.argTypes = {
@@ -4,21 +4,24 @@ import {
4
4
  addClickHandler,
5
5
  addDataAttributes,
6
6
  addDataTestId,
7
+ getArray,
8
+ isArrayNotEmpty,
7
9
  isNotEmpty,
8
10
  isReactNodeNotEmpty,
9
- isString,
10
11
  } from '@true-engineering/true-react-platform-helpers';
11
12
  import { useTweakStyles } from '../../hooks';
13
+ import { IMaybeArray } from '../../theme';
12
14
  import { IClickHandlerEvent, ICommonProps } from '../../types';
13
15
  import { Icon, IIcon, renderIcon } from '../Icon';
14
16
  import { ThemedPreloader } from '../ThemedPreloader';
15
- import { IControlWrapperSize } from './types';
17
+ import { convertToControlWrapperIcon } from './helpers';
18
+ import { IControlWrapperIcon, IControlWrapperSize } from './types';
16
19
  import { IControlWrapperStyles, useStyles } from './ControlWrapper.styles';
17
20
 
18
21
  export interface IControlWrapperProps extends ICommonProps<IControlWrapperStyles> {
19
22
  children: ReactNode;
20
23
  label?: ReactNode;
21
- icon?: IIcon;
24
+ icon?: IMaybeArray<IIcon | IControlWrapperIcon>;
22
25
  size?: IControlWrapperSize;
23
26
  groupPlacement?:
24
27
  | 'left'
@@ -44,7 +47,6 @@ export interface IControlWrapperProps extends ICommonProps<IControlWrapperStyles
44
47
  isLoading?: boolean;
45
48
  /** @default false */
46
49
  isDisabled?: boolean;
47
- onIconClick?: (event: IClickHandlerEvent) => void;
48
50
  onClear?: (event: IClickHandlerEvent) => void;
49
51
  }
50
52
 
@@ -64,14 +66,15 @@ export const ControlWrapper: FC<IControlWrapperProps> = ({
64
66
  children,
65
67
  tweakStyles,
66
68
  data,
67
- onIconClick,
68
69
  onClear,
69
70
  }) => {
70
71
  const classes = useStyles({ theme: tweakStyles });
71
72
 
72
- const hasEndIcon = !isLoading && isReactNodeNotEmpty(icon);
73
+ const icons = getArray(icon).map(convertToControlWrapperIcon);
74
+
75
+ const hasIcons = !isLoading && isArrayNotEmpty(icons);
73
76
  const hasClearButton = !isDisabled && !isLoading && hasValue && isNotEmpty(onClear);
74
- const hasControls = hasEndIcon || hasClearButton || isLoading;
77
+ const hasControls = hasIcons || hasClearButton || isLoading;
75
78
 
76
79
  const isActive = isFocused || hasValue;
77
80
 
@@ -126,18 +129,20 @@ export const ControlWrapper: FC<IControlWrapperProps> = ({
126
129
  </div>
127
130
  )}
128
131
 
129
- {hasEndIcon && (
130
- <div
131
- className={clsx(classes.icon, classes.endIcon, {
132
- [classes.activeIcon]: !isDisabled && isNotEmpty(onIconClick),
133
- [classes.customIcon]: !isString(icon),
134
- })}
135
- {...addClickHandler(onIconClick, !isDisabled)}
136
- {...addDataTestId(testId, 'icon')}
137
- >
138
- <div className={classes.iconInner}>{renderIcon(icon)}</div>
139
- </div>
140
- )}
132
+ {hasIcons &&
133
+ icons.map(({ key, iconComponent, onClick, shouldResetSize = false }, index) => (
134
+ <div
135
+ key={key ?? index}
136
+ className={clsx(classes.icon, classes.endIcon, {
137
+ [classes.activeIcon]: !isDisabled && isNotEmpty(onClick),
138
+ [classes.customIcon]: shouldResetSize,
139
+ })}
140
+ {...addClickHandler(onClick, !isDisabled)}
141
+ {...addDataTestId(testId, 'icon')}
142
+ >
143
+ <div className={classes.iconInner}>{renderIcon(iconComponent)}</div>
144
+ </div>
145
+ ))}
141
146
 
142
147
  {isLoading && (
143
148
  <div
@@ -0,0 +1,11 @@
1
+ import { isObject } from 'lodash-es';
2
+ import { IIcon } from '../Icon';
3
+ import { IControlWrapperIcon } from './types';
4
+
5
+ export const isControlWrapperIcon = (
6
+ iconItem: IIcon | IControlWrapperIcon,
7
+ ): iconItem is IControlWrapperIcon => isObject(iconItem) && 'iconComponent' in iconItem;
8
+
9
+ export const convertToControlWrapperIcon = (
10
+ iconItem: IIcon | IControlWrapperIcon,
11
+ ): IControlWrapperIcon => (isControlWrapperIcon(iconItem) ? iconItem : { iconComponent: iconItem });
@@ -1,3 +1,4 @@
1
1
  export * from './ControlWrapper';
2
+ export * from './helpers';
2
3
  export * from './types';
3
4
  export type { IControlWrapperStyles } from './ControlWrapper.styles';
@@ -1,4 +1,16 @@
1
+ import { type Key } from 'react';
2
+ import { IClickHandlerEvent } from '../../types';
3
+ import { IIcon } from '../Icon';
4
+
1
5
  // eslint-disable-next-line @typescript-eslint/no-empty-interface
2
6
  export interface IControlWrapperSizes {}
3
7
 
4
8
  export type IControlWrapperSize = keyof IControlWrapperSizes;
9
+
10
+ // подумать над extend HTMLAttributes<HTMLDivElement>
11
+ export interface IControlWrapperIcon {
12
+ key?: Key;
13
+ iconComponent: IIcon;
14
+ onClick?: (event: IClickHandlerEvent) => void;
15
+ shouldResetSize?: boolean;
16
+ }
@@ -54,7 +54,6 @@ Default.parameters = {
54
54
  'onFocus',
55
55
  'onBlur',
56
56
  'onPaste',
57
- 'onIconClick',
58
57
  'onKeyDown',
59
58
  ],
60
59
  },
@@ -96,15 +96,6 @@ Default.argTypes = {
96
96
 
97
97
  Default.parameters = {
98
98
  controls: {
99
- exclude: [
100
- 'data',
101
- 'testId',
102
- 'tabIndex',
103
- 'onFocus',
104
- 'onBlur',
105
- 'onPaste',
106
- 'onIconClick',
107
- 'onKeyDown',
108
- ],
99
+ exclude: ['data', 'testId', 'tabIndex', 'onFocus', 'onBlur', 'onPaste', 'onKeyDown'],
109
100
  },
110
101
  };
@@ -35,12 +35,7 @@ const Template: ComponentStory<typeof Input> = (args) => {
35
35
 
36
36
  return (
37
37
  <div style={{ width: 300 }}>
38
- <Input
39
- {...args}
40
- value={value}
41
- onChange={setValue}
42
- onIconClick={() => console.log('icon click')}
43
- />
38
+ <Input {...args} value={value} onChange={setValue} />
44
39
  </div>
45
40
  );
46
41
  };
@@ -39,7 +39,6 @@ export interface IInputBaseProps
39
39
  | 'isRequired'
40
40
  | 'isLoading'
41
41
  | 'isDisabled'
42
- | 'onIconClick'
43
42
  >,
44
43
  Pick<
45
44
  Partial<ReactInputMaskBaseProps>,
@@ -102,7 +101,6 @@ export const InputBase = forwardRef<HTMLInputElement, IInputBaseProps>(
102
101
  isLoading,
103
102
  isRequired,
104
103
  size,
105
- onIconClick,
106
104
  children,
107
105
  ...inputProps
108
106
  },
@@ -241,7 +239,6 @@ export const InputBase = forwardRef<HTMLInputElement, IInputBaseProps>(
241
239
  icon={icon}
242
240
  isLoading={isLoading}
243
241
  isRequired={isRequired}
244
- onIconClick={onIconClick}
245
242
  size={size}
246
243
  >
247
244
  {hasUnits || isAutoSized ? (
@@ -247,4 +247,5 @@ CustomSelect.args = {
247
247
  shouldScrollToList: true,
248
248
  canBeFlipped: false,
249
249
  scrollParent: 'document',
250
+ size: undefined,
250
251
  };
@@ -1,6 +1,7 @@
1
1
  import { ReactNode, useEffect, useState } from 'react';
2
2
  import { isStringNotEmpty } from '@true-engineering/true-react-platform-helpers';
3
3
  import { ComponentMeta, ComponentStory } from '@storybook/react';
4
+ import { iconsList } from '../Icon';
4
5
  import { IMultipleSelectProps, Select } from './Select';
5
6
 
6
7
  interface ObjectValue {
@@ -54,6 +55,8 @@ const objectOptions: ObjectValue[] = [
54
55
  { name: 'Artem', age: 23 },
55
56
  ];
56
57
 
58
+ const icons = [undefined, ...Object.keys(iconsList)];
59
+
57
60
  // Максимум не включается, минимум включается
58
61
  const getRandomInt = (min: number, max: number) =>
59
62
  Math.floor(Math.random() * (Math.floor(max) - Math.ceil(min))) + Math.ceil(min);
@@ -195,6 +198,7 @@ export default {
195
198
  control: 'inline-radio',
196
199
  options: ['strings', 'objects'],
197
200
  },
201
+ icon: { control: 'select', options: icons },
198
202
  },
199
203
  } as ComponentMeta<typeof SelectWithCustomProps>;
200
204
 
@@ -229,4 +233,5 @@ MultiSelect.args = {
229
233
  shouldScrollToList: true,
230
234
  canBeFlipped: false,
231
235
  scrollParent: 'document',
236
+ size: undefined,
232
237
  };
@@ -1,6 +1,7 @@
1
1
  import { ReactNode, useEffect, useState } from 'react';
2
2
  import { isStringNotEmpty } from '@true-engineering/true-react-platform-helpers';
3
3
  import { ComponentMeta, ComponentStory } from '@storybook/react';
4
+ import { iconsList } from '../Icon';
4
5
  import { ISelectProps, Select } from './Select';
5
6
 
6
7
  interface ObjectValue {
@@ -54,6 +55,8 @@ const objectOptions: ObjectValue[] = [
54
55
  { name: 'Artem', age: 23 },
55
56
  ];
56
57
 
58
+ const icons = [undefined, ...Object.keys(iconsList)];
59
+
57
60
  // Максимум не включается, минимум включается
58
61
  const getRandomInt = (min: number, max: number) =>
59
62
  Math.floor(Math.random() * (Math.floor(max) - Math.ceil(min))) + Math.ceil(min);
@@ -192,6 +195,7 @@ export default {
192
195
  control: 'inline-radio',
193
196
  options: ['strings', 'objects'],
194
197
  },
198
+ icon: { control: 'select', options: icons },
195
199
  },
196
200
  } as ComponentMeta<typeof SelectWithCustomProps>;
197
201
 
@@ -213,6 +217,7 @@ Default.args = {
213
217
  isClearable: false,
214
218
  isLoading: false,
215
219
  debounceTime: 400,
220
+ icon: undefined,
216
221
  // custom options
217
222
  shouldUseReactNodes: false,
218
223
  valuesType: 'strings',
@@ -225,4 +230,5 @@ Default.args = {
225
230
  shouldScrollToList: true,
226
231
  canBeFlipped: false,
227
232
  scrollParent: 'document',
233
+ size: undefined,
228
234
  };
@@ -5,7 +5,7 @@ import { type ISearchInputStyles } from '../SearchInput';
5
5
  import { IWithMessagesStyles } from '../WithMessages';
6
6
  import { type ISelectListStyles } from './components';
7
7
 
8
- const { CONTROL, Z_INDEX } = dimensions;
8
+ const { Z_INDEX } = dimensions;
9
9
 
10
10
  export const useStyles = createThemedStyles('Select', {
11
11
  root: {
@@ -17,7 +17,6 @@ export const useStyles = createThemedStyles('Select', {
17
17
  inputWrapper: {
18
18
  width: '100%',
19
19
  cursor: 'text',
20
- position: 'relative',
21
20
  },
22
21
 
23
22
  listWrapper: {
@@ -40,20 +39,16 @@ export const useStyles = createThemedStyles('Select', {
40
39
  },
41
40
 
42
41
  arrow: {
43
- position: 'absolute',
44
- right: 12,
45
- top: '50%',
46
- width: 20,
47
- height: 20,
42
+ width: 'var(--icon-inner-size, 20px)',
43
+ height: 'var(--icon-inner-size, 20px)',
48
44
  cursor: 'pointer',
49
- transform: 'translateY(-50%)',
50
45
  transition: animations.defaultTransition,
51
46
  transitionProperty: 'transform',
52
47
  zIndex: Z_INDEX.CONTROL_FOCUS + 1,
53
48
  },
54
49
 
55
50
  activeArrow: {
56
- transform: 'translateY(-50%) rotate(180deg)',
51
+ transform: 'rotate(180deg)',
57
52
  },
58
53
 
59
54
  disabled: {
@@ -62,45 +57,15 @@ export const useStyles = createThemedStyles('Select', {
62
57
  },
63
58
  },
64
59
 
65
- counter: {
66
- '&:not(:last-child)': {
67
- paddingRight: 8,
68
- },
69
- },
70
-
71
- icon: {
72
- width: CONTROL.ICON_INNER_SIZE,
73
- height: CONTROL.ICON_INNER_SIZE,
74
- },
75
-
76
- iconWrapper: {
77
- display: 'flex',
78
- alignItems: 'center',
79
- },
60
+ counter: {},
80
61
  });
81
62
 
82
63
  const baseInputStyles: IInputStyles = {
83
- inputContent: {
84
- paddingRight: 32,
85
- },
86
-
87
64
  input: {
88
65
  '&[readonly]': {
89
66
  cursor: 'pointer',
90
67
  },
91
68
  },
92
-
93
- tweakControlWrapper: {
94
- controls: {
95
- paddingRight: 40,
96
- },
97
-
98
- icon: {
99
- '&:last-child': {
100
- paddingRight: 0,
101
- },
102
- },
103
- },
104
69
  };
105
70
 
106
71
  const readonlyInputBaseStyles: IInputStyles = {
@@ -18,6 +18,7 @@ import clsx from 'clsx';
18
18
  import { debounce } from 'ts-debounce';
19
19
  import {
20
20
  createFilter,
21
+ getArray,
21
22
  getTestId,
22
23
  isNotEmpty,
23
24
  isReactNodeNotEmpty,
@@ -575,16 +576,6 @@ export function Select<Value>(
575
576
  </div>
576
577
  );
577
578
 
578
- const multiSelectCounterWithIcon =
579
- shouldShowMultiSelectCounter || isNotEmpty(icon) ? (
580
- <div className={classes.iconWrapper}>
581
- {shouldShowMultiSelectCounter && (
582
- <div className={classes.counter}>(+{value.length - 1})</div>
583
- )}
584
- {isNotEmpty(icon) && <div className={classes.icon}>{renderIcon(icon)}</div>}
585
- </div>
586
- ) : undefined;
587
-
588
579
  return (
589
580
  <WithMessages
590
581
  errorMessage={errorMessage}
@@ -612,18 +603,31 @@ export function Select<Value>(
612
603
  isLoading={areOptionsLoading}
613
604
  tweakStyles={tweakInputStyles}
614
605
  testId={testId}
615
- icon={isMultiSelect ? multiSelectCounterWithIcon : icon}
606
+ icon={[
607
+ isMultiSelect && shouldShowMultiSelectCounter
608
+ ? {
609
+ key: 'counter',
610
+ iconComponent: (
611
+ <div key="counter" className={classes.counter}>
612
+ (+{value.length - 1})
613
+ </div>
614
+ ),
615
+ shouldResetSize: true,
616
+ }
617
+ : undefined,
618
+
619
+ ...getArray(icon),
620
+
621
+ <div
622
+ key="arrow"
623
+ className={clsx(classes.arrow, isOpen && classes.activeArrow)}
624
+ onClick={onArrowClick} // клик тут, потому что onClick в ControlWrapper добавляет tabIndex={0}
625
+ >
626
+ {renderIcon(dropdownIcon)}
627
+ </div>,
628
+ ].filter(isNotEmpty)}
616
629
  {...inputProps}
617
630
  />
618
- <div
619
- onMouseDown={(event: MouseEvent) => {
620
- event.preventDefault();
621
- }}
622
- onClick={onArrowClick}
623
- className={clsx(classes.arrow, isOpen && classes.activeArrow)}
624
- >
625
- {renderIcon(dropdownIcon)}
626
- </div>
627
631
  </div>
628
632
  {shouldUsePopper ? (
629
633
  <Portal container={shouldRenderInBody ? document.body : inputWrapper.current}>
@@ -41,6 +41,7 @@ Default.args = {
41
41
  isRequired: false,
42
42
  isAutoSized: true,
43
43
  shouldAlwaysShowPlaceholder: false,
44
+ size: undefined,
44
45
  };
45
46
 
46
47
  Default.argTypes = {