@true-engineering/true-react-common-ui-kit 4.0.0-alpha51 → 4.0.0-alpha53

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-alpha51",
3
+ "version": "4.0.0-alpha53",
4
4
  "description": "True Engineering React UI Kit with theming support",
5
5
  "author": "True Engineering (https://trueengineering.ru)",
6
6
  "keywords": [
@@ -33,7 +33,8 @@ const meta: Meta<typeof Story> = {
33
33
  hasValue: false,
34
34
  isActive: false,
35
35
  isFullWidth: false,
36
- icon: [
36
+ startIcon: [{ iconComponent: 'search' }, { iconComponent: <Icon type="plus" /> }],
37
+ endIcon: [
37
38
  { iconComponent: <div>Бу</div>, onClick: () => console.log('close'), shouldResetSize: true },
38
39
  { iconComponent: <Icon type="plus" /> },
39
40
  { iconComponent: 'search', onClick: () => console.log('search') },
@@ -41,7 +42,7 @@ const meta: Meta<typeof Story> = {
41
42
  },
42
43
  argTypes: {
43
44
  size: { options: [undefined, ...SIZES], control: 'inline-radio' },
44
- icon: { options: [undefined, ...Object.keys(iconsMap)], control: 'select' },
45
+ endIcon: { options: [undefined, ...Object.keys(iconsMap)], control: 'select' },
45
46
  groupPlacement: { options: [undefined, ...GROUP_PLACEMENTS], control: 'select' },
46
47
  },
47
48
  parameters: {
@@ -84,6 +84,10 @@ export const useStyles = createThemedStyles('ControlWrapper', {
84
84
  flexShrink: 0,
85
85
  },
86
86
 
87
+ startControls: {
88
+ paddingLeft: 'var(--control-padding)',
89
+ },
90
+
87
91
  icon: {
88
92
  display: 'flex',
89
93
  alignItems: 'center',
@@ -111,6 +115,8 @@ export const useStyles = createThemedStyles('ControlWrapper', {
111
115
  },
112
116
  },
113
117
 
118
+ startIcon: {},
119
+
114
120
  endIcon: {},
115
121
 
116
122
  activeIcon: {
@@ -130,6 +136,8 @@ export const useStyles = createThemedStyles('ControlWrapper', {
130
136
  },
131
137
  },
132
138
 
139
+ withStartControls: {},
140
+
133
141
  /* groupPositions */
134
142
  'placement-top': {
135
143
  borderBottomLeftRadius: 0,
@@ -1,4 +1,4 @@
1
- import { FC, ReactNode } from 'react';
1
+ import { CSSProperties, FC, ReactNode, useCallback, useState } from 'react';
2
2
  import clsx from 'clsx';
3
3
  import {
4
4
  addClickHandler,
@@ -12,7 +12,7 @@ import {
12
12
  import { useTweakStyles } from '../../hooks';
13
13
  import { IMaybeArray } from '../../theme';
14
14
  import { IClickHandlerEvent, ICommonProps } from '../../types';
15
- import { Icon, IIcon, renderIcon } from '../Icon';
15
+ import { IIcon, renderIcon } from '../Icon';
16
16
  import { ThemedPreloader } from '../ThemedPreloader';
17
17
  import { convertToControlWrapperIcon } from './helpers';
18
18
  import { IControlWrapperIcon, IControlWrapperSize, IGroupPlacement } from './types';
@@ -21,7 +21,10 @@ import { IControlWrapperStyles, useStyles } from './ControlWrapper.styles';
21
21
  export interface IControlWrapperProps extends ICommonProps<IControlWrapperStyles> {
22
22
  children: ReactNode;
23
23
  label?: ReactNode;
24
+ /** @deprecated */
24
25
  icon?: IMaybeArray<IIcon | IControlWrapperIcon>;
26
+ startIcon?: IMaybeArray<IIcon | IControlWrapperIcon>;
27
+ endIcon?: IMaybeArray<IIcon | IControlWrapperIcon>;
25
28
  size?: IControlWrapperSize;
26
29
  groupPlacement?: IGroupPlacement;
27
30
  /** @default false */
@@ -46,6 +49,8 @@ export interface IControlWrapperProps extends ICommonProps<IControlWrapperStyles
46
49
  export const ControlWrapper: FC<IControlWrapperProps> = ({
47
50
  label,
48
51
  icon,
52
+ startIcon,
53
+ endIcon = icon,
49
54
  groupPlacement,
50
55
  isInvalid,
51
56
  isFocused,
@@ -64,11 +69,15 @@ export const ControlWrapper: FC<IControlWrapperProps> = ({
64
69
  }) => {
65
70
  const classes = useStyles({ theme: tweakStyles });
66
71
 
67
- const icons = getArray(icon).map(convertToControlWrapperIcon);
72
+ const [startControlsWidth, setStartControlsWidth] = useState<number>();
68
73
 
69
- const hasIcons = !isLoading && isArrayNotEmpty(icons);
74
+ const startIcons = getArray(startIcon).map(convertToControlWrapperIcon);
75
+ const endIcons = getArray(endIcon).map(convertToControlWrapperIcon);
76
+
77
+ const hasStartIcons = isArrayNotEmpty(startIcons);
78
+ const hasEndIcons = !isLoading && isArrayNotEmpty(endIcons);
70
79
  const hasClearButton = !isDisabled && !isLoading && hasValue && isNotEmpty(onClear);
71
- const hasControls = hasIcons || hasClearButton || isLoading;
80
+ const hasEndControls = hasEndIcons || hasClearButton || isLoading;
72
81
 
73
82
  const tweakPreloaderStyles = useTweakStyles({
74
83
  tweakStyles,
@@ -76,6 +85,32 @@ export const ControlWrapper: FC<IControlWrapperProps> = ({
76
85
  currentComponentName: 'ControlWrapper',
77
86
  });
78
87
 
88
+ const startControlsRef = useCallback(
89
+ (node: HTMLDivElement | null) => {
90
+ setStartControlsWidth(node?.clientWidth);
91
+ },
92
+ // eslint-disable-next-line
93
+ [startIcon, size],
94
+ );
95
+
96
+ const renderIconControl = (
97
+ { key, iconComponent, onClick, shouldResetSize = false }: IControlWrapperIcon,
98
+ iconType: 'start' | 'end' | 'clear',
99
+ index?: number,
100
+ ) => (
101
+ <div
102
+ key={key ?? index}
103
+ className={clsx(classes.icon, classes[`${iconType}Icon`], {
104
+ [classes.activeIcon]: !isDisabled && isNotEmpty(onClick),
105
+ [classes.customIcon]: shouldResetSize,
106
+ })}
107
+ {...addClickHandler(onClick, !isDisabled)}
108
+ {...addDataTestId(testId, `${iconType}-icon`)}
109
+ >
110
+ <div className={classes.iconInner}>{renderIcon(iconComponent)}</div>
111
+ </div>
112
+ );
113
+
79
114
  return (
80
115
  <div
81
116
  className={clsx(
@@ -89,8 +124,14 @@ export const ControlWrapper: FC<IControlWrapperProps> = ({
89
124
  [classes.disabled]: isDisabled,
90
125
  [classes.loading]: isLoading,
91
126
  [classes.minContent]: !isFullWidth,
127
+ [classes.withStartControls]: hasStartIcons,
92
128
  },
93
129
  )}
130
+ style={
131
+ hasStartIcons
132
+ ? ({ '--start-controls-width': `${startControlsWidth}px` } as CSSProperties)
133
+ : undefined
134
+ }
94
135
  {...addDataAttributes(data, testId)}
95
136
  >
96
137
  {isReactNodeNotEmpty(label) && (
@@ -105,36 +146,21 @@ export const ControlWrapper: FC<IControlWrapperProps> = ({
105
146
  </div>
106
147
  )}
107
148
  <div className={classes.wrapper}>
149
+ {hasStartIcons && (
150
+ <div className={clsx(classes.controls, classes.startControls)} ref={startControlsRef}>
151
+ {startIcons.map((iconProps, index) => renderIconControl(iconProps, 'start', index))}
152
+ </div>
153
+ )}
154
+
108
155
  {children}
109
156
 
110
- {hasControls && (
157
+ {hasEndControls && (
111
158
  <div className={classes.controls}>
112
- {hasClearButton && (
113
- <div
114
- className={clsx(classes.icon, classes.clearIcon, classes.activeIcon)}
115
- {...addClickHandler(onClear)}
116
- {...addDataTestId(testId, 'clear')}
117
- >
118
- <div className={classes.iconInner}>
119
- <Icon type="close" />
120
- </div>
121
- </div>
122
- )}
159
+ {hasClearButton &&
160
+ renderIconControl({ iconComponent: 'close', onClick: onClear }, 'clear')}
123
161
 
124
- {hasIcons &&
125
- icons.map(({ key, iconComponent, onClick, shouldResetSize = false }, index) => (
126
- <div
127
- key={key ?? index}
128
- className={clsx(classes.icon, classes.endIcon, {
129
- [classes.activeIcon]: !isDisabled && isNotEmpty(onClick),
130
- [classes.customIcon]: shouldResetSize,
131
- })}
132
- {...addClickHandler(onClick, !isDisabled)}
133
- {...addDataTestId(testId, 'icon')}
134
- >
135
- <div className={classes.iconInner}>{renderIcon(iconComponent)}</div>
136
- </div>
137
- ))}
162
+ {hasEndIcons &&
163
+ endIcons.map((iconProps, index) => renderIconControl(iconProps, 'end', index))}
138
164
 
139
165
  {isLoading && (
140
166
  <div
@@ -22,7 +22,7 @@ const meta: Meta<typeof Story> = {
22
22
  isDisabled: false,
23
23
  isRequired: false,
24
24
  isReadonly: false,
25
- icon: 'information',
25
+ endIcon: 'information',
26
26
  isClearable: true,
27
27
  },
28
28
  };
@@ -42,7 +42,8 @@ const meta: Meta<typeof Story> = {
42
42
  },
43
43
  argTypes: {
44
44
  type: { control: 'radio', options: types },
45
- icon: { control: 'select', options: icons },
45
+ endIcon: { control: 'select', options: icons },
46
+ startIcon: { control: 'select', options: icons },
46
47
  mask: { control: 'select', options: masks },
47
48
  groupPlacement: { options: [undefined, ...GROUP_PLACEMENTS], control: 'select' },
48
49
  },
@@ -44,15 +44,14 @@ export const useStyles = createThemedStyles('Input', {
44
44
  },
45
45
 
46
46
  defaultWrapper: {
47
- display: 'contents',
47
+ display: 'flex',
48
+ flexGrow: 1,
48
49
  },
49
50
 
50
51
  autoSizeWrapper: {
51
52
  position: 'relative',
52
- display: 'flex',
53
53
  minWidth: 0,
54
54
  zIndex: 0,
55
- flexGrow: 1,
56
55
  },
57
56
 
58
57
  autoSized: {
@@ -33,6 +33,8 @@ export interface IInputBaseProps
33
33
  IControlWrapperProps,
34
34
  | 'label'
35
35
  | 'icon'
36
+ | 'endIcon'
37
+ | 'startIcon'
36
38
  | 'size'
37
39
  | 'groupPlacement'
38
40
  | 'isInvalid'
@@ -97,7 +99,9 @@ export const InputBase = forwardRef<HTMLInputElement, IInputBaseProps>(
97
99
  // Пропсы ControlWrapper
98
100
  data,
99
101
  groupPlacement,
102
+ startIcon,
100
103
  icon,
104
+ endIcon = icon,
101
105
  isLoading,
102
106
  isRequired,
103
107
  size,
@@ -198,7 +202,7 @@ export const InputBase = forwardRef<HTMLInputElement, IInputBaseProps>(
198
202
  if (shouldFocusOnMount) {
199
203
  inputRef.current?.focus();
200
204
  }
201
- }, []);
205
+ }, [shouldFocusOnMount]);
202
206
 
203
207
  return (
204
208
  <ControlWrapper
@@ -213,7 +217,8 @@ export const InputBase = forwardRef<HTMLInputElement, IInputBaseProps>(
213
217
  onClear={isClearable && hasValue ? handleInputClear : undefined}
214
218
  data={data}
215
219
  groupPlacement={groupPlacement}
216
- icon={icon}
220
+ startIcon={startIcon}
221
+ endIcon={endIcon}
217
222
  isLoading={isLoading}
218
223
  isRequired={isRequired}
219
224
  size={size}
@@ -20,7 +20,7 @@ const meta: Meta<typeof Story> = {
20
20
  args: {
21
21
  label: 'Label',
22
22
  infoMessage: 'Message Info',
23
- icon: undefined,
23
+ endIcon: undefined,
24
24
  isInvalid: false,
25
25
  errorMessage: 'Error Text',
26
26
  isActive: false,
@@ -32,7 +32,7 @@ const meta: Meta<typeof Story> = {
32
32
  groupPlacement: undefined,
33
33
  },
34
34
  argTypes: {
35
- icon: { control: 'select', options: [undefined, ...Object.keys(iconsMap)] },
35
+ endIcon: { control: 'select', options: [undefined, ...Object.keys(iconsMap)] },
36
36
  groupPlacement: { options: [undefined, ...GROUP_PLACEMENTS], control: 'select' },
37
37
  },
38
38
  };
@@ -16,7 +16,12 @@ const meta: Meta<typeof Story> = {
16
16
  component: Story,
17
17
  args: {
18
18
  placeholder: 'Поиск',
19
+ size: undefined,
19
20
  isAutoSized: false,
21
+ isLoading: false,
22
+ },
23
+ argTypes: {
24
+ size: { options: [undefined, 'micro'], control: 'inline-radio' },
20
25
  },
21
26
  };
22
27
 
@@ -2,27 +2,21 @@ import { IInputStyles } from '../Input';
2
2
 
3
3
  export const inputStyles: IInputStyles = {
4
4
  tweakControlWrapper: {
5
- endIcon: {
5
+ startControls: {
6
6
  position: 'absolute',
7
- width: 40,
8
7
  left: 0,
9
-
10
- '&:last-child': {
11
- paddingRight: 0,
12
- },
13
-
14
- '&:not($activeIcon)': {
15
- pointerEvents: 'none',
16
- },
8
+ top: 0,
9
+ height: '100%',
10
+ pointerEvents: 'none',
17
11
  },
18
12
 
19
13
  controlWrapper: {
20
14
  borderColor: 'transparent',
21
15
  },
22
16
  },
17
+
23
18
  inputContent: {
24
- fontSize: 14,
25
- paddingLeft: 44,
19
+ paddingLeft: 'calc(var(--start-controls-width) + var(--control-padding))',
26
20
  },
27
21
  };
28
22
 
@@ -6,7 +6,7 @@ import { inputStyles, ISearchInputStyles } from './SearchInput.styles';
6
6
 
7
7
  export type ISearchInputProps = Omit<
8
8
  IInputProps,
9
- 'type' | 'label' | 'isInvalid' | 'errorMessage' | 'isActive' | 'tweakStyles'
9
+ 'type' | 'label' | 'isInvalid' | 'errorMessage' | 'isActive' | 'tweakStyles' | 'startIcon'
10
10
  > &
11
11
  ICommonProps<ISearchInputStyles>;
12
12
 
@@ -22,7 +22,7 @@ export const SearchInput = forwardRef<HTMLInputElement, ISearchInputProps>(
22
22
  return (
23
23
  <Input
24
24
  ref={ref}
25
- icon="search"
25
+ startIcon="search"
26
26
  isClearable={isClearable}
27
27
  tweakStyles={tweakInputStyles}
28
28
  {...props}
@@ -189,7 +189,7 @@ const meta: Meta<typeof Story> = {
189
189
  noMatchesLabel: 'No matches',
190
190
  isInvalid: false,
191
191
  errorMessage: 'Error Text',
192
- icon: 'filter',
192
+ endIcon: 'filter',
193
193
  defaultOptionLabel: '',
194
194
  allOptionsLabel: 'Все опции',
195
195
  isDisabled: false,
@@ -224,7 +224,7 @@ const meta: Meta<typeof Story> = {
224
224
  control: 'inline-radio',
225
225
  options: ['strings', 'objects'],
226
226
  },
227
- icon: { control: 'select', options: icons },
227
+ endIcon: { control: 'select', options: icons },
228
228
  },
229
229
  };
230
230
 
@@ -193,7 +193,7 @@ const meta: Meta<typeof Story> = {
193
193
  isClearable: false,
194
194
  isLoading: false,
195
195
  debounceTime: 400,
196
- icon: undefined,
196
+ endIcon: undefined,
197
197
  // custom options
198
198
  shouldUseReactNodes: false,
199
199
  valuesType: 'strings',
@@ -221,7 +221,7 @@ const meta: Meta<typeof Story> = {
221
221
  control: 'inline-radio',
222
222
  options: ['strings', 'objects'],
223
223
  },
224
- icon: { control: 'select', options: icons },
224
+ endIcon: { control: 'select', options: icons },
225
225
  },
226
226
  };
227
227
 
@@ -126,6 +126,7 @@ export function Select<Value>(
126
126
  shouldScrollToList = true,
127
127
  searchInput,
128
128
  icon,
129
+ endIcon = icon,
129
130
  onChange,
130
131
  onFocus,
131
132
  onBlur,
@@ -139,6 +140,7 @@ export function Select<Value>(
139
140
  optionsFilter,
140
141
  infoMessage,
141
142
  errorMessage,
143
+ size,
142
144
  ...inputProps
143
145
  } = props;
144
146
  const classes = useStyles({ theme: tweakStyles });
@@ -519,6 +521,7 @@ export function Select<Value>(
519
521
 
520
522
  const searchInputEl = hasSearchInputInList && (
521
523
  <SearchInput
524
+ size={size}
522
525
  value={searchValue}
523
526
  onChange={handleInputChange}
524
527
  tweakStyles={tweakSearchInputStyles}
@@ -551,6 +554,7 @@ export function Select<Value>(
551
554
  {isOpen && (
552
555
  <SelectList
553
556
  options={filteredOptions}
557
+ size={size}
554
558
  defaultOptionLabel={hasDefaultOption && shouldShowDefaultOption && defaultOptionLabel}
555
559
  allOptionsLabel={shouldShowAllOption && allOptionsLabel}
556
560
  areAllOptionsSelected={areAllOptionsSelected}
@@ -594,6 +598,7 @@ export function Select<Value>(
594
598
  value={
595
599
  searchValue !== '' && !shouldRenderSearchInputInList ? searchValue : showedStringValue
596
600
  }
601
+ size={size}
597
602
  onChange={handleInputChange}
598
603
  isActive={isListOpen || isActive}
599
604
  isReadonly={hasReadonlyInput}
@@ -617,7 +622,7 @@ export function Select<Value>(
617
622
  }
618
623
  : undefined,
619
624
 
620
- ...getArray(icon),
625
+ ...getArray(endIcon),
621
626
 
622
627
  <div
623
628
  key="arrow"
@@ -3,10 +3,12 @@ import clsx from 'clsx';
3
3
  import {
4
4
  addDataTestId,
5
5
  getArray,
6
+ isNotEmpty,
6
7
  isReactNodeNotEmpty,
7
8
  } from '@true-engineering/true-react-platform-helpers';
8
9
  import { type ICommonProps } from '../../../../types';
9
10
  import { ScrollIntoViewIfNeeded } from '../../../ScrollIntoViewIfNeeded';
11
+ import type { ISelectProps } from '../../Select';
10
12
  import { ALL_OPTION_INDEX, DEFAULT_OPTION_INDEX } from '../../constants';
11
13
  import { type ISelectListItemProps, SelectListItem } from '../SelectListItem';
12
14
  import { type ISelectListStyles, useStyles } from './SelectList.styles';
@@ -15,6 +17,7 @@ export interface ISelectListProps<Value>
15
17
  extends ICommonProps<ISelectListStyles>,
16
18
  Pick<ISelectListItemProps, 'onToggleCheckbox' | 'onOptionSelect' | 'isMultiSelect'> {
17
19
  options: Value[] | readonly Value[];
20
+ size?: ISelectProps<Value>['size'];
18
21
  focusedIndex?: number;
19
22
  activeValue?: Value | Value[];
20
23
  noMatchesLabel?: string;
@@ -35,6 +38,7 @@ export interface ISelectListProps<Value>
35
38
  export function SelectList<Value>({
36
39
  options,
37
40
  focusedIndex,
41
+ size,
38
42
  activeValue,
39
43
  defaultOptionLabel,
40
44
  noMatchesLabel = 'Совпадений не найдено',
@@ -78,7 +82,7 @@ export function SelectList<Value>({
78
82
  return (
79
83
  <ScrollIntoViewIfNeeded
80
84
  active={shouldScrollToList && !isMultiSelect}
81
- className={clsx(classes.root, {
85
+ className={clsx(classes.root, isNotEmpty(size) && classes[size], {
82
86
  [classes.withListHeader]: isHeaderNotEmpty,
83
87
  [classes.withListFooter]: isFooterNotEmpty,
84
88
  })}