@true-engineering/true-react-common-ui-kit 3.26.0 → 3.27.0

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": "3.26.0",
3
+ "version": "3.27.0",
4
4
  "description": "True Engineering React UI Kit with theming support",
5
5
  "author": "True Engineering (https://trueengineering.ru)",
6
6
  "keywords": [
@@ -60,6 +60,10 @@ Default.args = {
60
60
  hasRequiredLabel: true,
61
61
  isInvalid: false,
62
62
  errorMessage: 'Error Text',
63
+ errorPosition: 'bottom',
64
+ isAutoSizeable: false,
65
+ shouldFocusOnMount: false,
66
+ shouldAlwaysShowPlaceholder: false,
63
67
  isActive: false,
64
68
  isDisabled: false,
65
69
  isRequired: false,
@@ -81,6 +85,7 @@ Default.parameters = {
81
85
  'onPaste',
82
86
  'onKeyDown',
83
87
  'onIconClick',
88
+ 'tweakStyles',
84
89
  ],
85
90
  },
86
91
  };
@@ -166,9 +166,8 @@ export const Input = forwardRef<HTMLInputElement, IInputProps>(
166
166
  const hasValue = value !== undefined && value !== '';
167
167
  const hasUnits = units !== undefined && units !== '';
168
168
  const hasLabel = isReactNodeNotEmpty(label);
169
- const hasPlaceholder =
170
- (!hasLabel || (hasFocus && !isReadonly) || shouldAlwaysShowPlaceholder) &&
171
- isStringNotEmpty(placeholder);
169
+ const isLabelActive = (hasFocus && !isReadonly) || hasValue || shouldAlwaysShowPlaceholder;
170
+ const hasPlaceholder = (!hasLabel || isLabelActive) && isStringNotEmpty(placeholder);
172
171
  const shouldShowUnits = (hasValue || (isFocused && !hasPlaceholder)) && hasUnits;
173
172
 
174
173
  const props: InputHTMLAttributes<HTMLInputElement> = {
@@ -231,7 +230,7 @@ export const Input = forwardRef<HTMLInputElement, IInputProps>(
231
230
  className={clsx(classes.label, {
232
231
  [classes.invalidLabel]: isInvalid,
233
232
  [classes.requiredLabel]: hasRequiredLabel && !isRequired,
234
- [classes.activeLabel]: (hasFocus && !isReadonly) || hasValue,
233
+ [classes.activeLabel]: isLabelActive,
235
234
  [classes.floating]: hasFloatingLabel,
236
235
  // Обсуждаемо, сделал так, потому что не хочется создавать новую пропсу, на каждый чих в стилях
237
236
  [classes.floatingWithoutPadding]: hasFloatingLabel && border === 'bottom',
@@ -13,14 +13,34 @@ const listItems: IListItem[] = [
13
13
  {
14
14
  item: 'Аннулировать',
15
15
  onClick: console.log,
16
+ disabled: true,
16
17
  icon: <>2</>,
17
18
  },
18
19
  {
19
20
  item: 'Вернуть',
20
21
  onClick: console.log,
21
- disabled: true,
22
22
  withIconGap: true,
23
23
  shouldDrawSpacerBelow: true,
24
+ nestedItems: [
25
+ {
26
+ item: 'Вернуть деньги',
27
+ nestedItems: [
28
+ {
29
+ item: 'Вернуть деньги',
30
+ onClick: console.log,
31
+ },
32
+ {
33
+ item: 'Вернуть стену',
34
+ onClick: console.log,
35
+ },
36
+ ],
37
+ onClick: console.log,
38
+ },
39
+ {
40
+ item: 'Вернуть стену',
41
+ onClick: console.log,
42
+ },
43
+ ],
24
44
  },
25
45
  {
26
46
  item: (() => <div style={{ color: colors.GREEN_FOCUS }}>Test</div>)(),
@@ -1,8 +1,5 @@
1
- import { colors, ITweakStyles, createThemedStyles, animations } from '../../theme';
2
-
3
- const ITEM_HORIZONTAL_PADDING = 16;
4
- const ICON_SIZE = 20;
5
- const ICON_GAP = 12;
1
+ import { colors, ITweakStyles, createThemedStyles } from '../../theme';
2
+ import { IWithPopupStyles } from '../WithPopup';
6
3
 
7
4
  export const useStyles = createThemedStyles('List', {
8
5
  root: {
@@ -13,40 +10,15 @@ export const useStyles = createThemedStyles('List', {
13
10
  margin: 0,
14
11
  },
15
12
 
16
- item: {
17
- display: 'flex',
18
- alignItems: 'center',
19
- boxSizing: 'border-box',
20
- fontSize: 16,
21
- whiteSpace: 'nowrap',
22
- minHeight: 40,
23
- padding: [0, ITEM_HORIZONTAL_PADDING],
24
- transition: animations.defaultTransition,
25
- transitionProperty: 'background-color',
26
- cursor: 'pointer',
27
- },
28
-
29
- disabledItem: {
30
- cursor: 'default',
31
- },
32
-
33
- spacer: {
34
- height: 1,
35
- backgroundColor: colors.BORDER_LIGHT,
36
- },
37
-
38
- withIconGap: {
39
- paddingLeft: ITEM_HORIZONTAL_PADDING + ICON_SIZE + ICON_GAP,
13
+ nestedItems: {
14
+ paddingLeft: 4,
40
15
  },
16
+ });
41
17
 
42
- icon: {
43
- width: ICON_SIZE,
44
- height: ICON_SIZE,
45
- marginRight: ICON_GAP,
46
- flexShrink: 0,
18
+ export const withPopupStyles: IWithPopupStyles = {
19
+ trigger: {
20
+ width: '100%',
47
21
  },
48
-
49
- content: {},
50
- });
22
+ };
51
23
 
52
24
  export type IListStyles = ITweakStyles<typeof useStyles>;
@@ -1,9 +1,14 @@
1
- import { FC, KeyboardEvent, MouseEvent } from 'react';
2
- import { addDataTestId, getTestId } from '@true-engineering/true-react-platform-helpers';
1
+ import { FC, Fragment, KeyboardEvent, MouseEvent } from 'react';
2
+ import {
3
+ addDataTestId,
4
+ getTestId,
5
+ isArrayNotEmpty,
6
+ } from '@true-engineering/true-react-platform-helpers';
3
7
  import { addDataAttributes } from '../../helpers';
4
8
  import { ICommonProps } from '../../types';
5
- import { IListItemProps as IListItem, ListItem } from './components';
6
- import { useStyles, IListStyles } from './List.styles';
9
+ import { WithPopup } from '../WithPopup';
10
+ import { IListItemProps, IListItemProps as IListItem, ListItem } from './components';
11
+ import { useStyles, IListStyles, withPopupStyles } from './List.styles';
7
12
 
8
13
  export interface IListProps extends ICommonProps<IListStyles> {
9
14
  items: IListItem[];
@@ -20,14 +25,34 @@ export const List: FC<IListProps> = ({ items, testId, data, tweakStyles, onClick
20
25
 
21
26
  return (
22
27
  <div className={classes.root} {...addDataTestId(testId)} {...addDataAttributes(data)}>
23
- {items.map((item, i) => (
24
- <ListItem
25
- key={i}
26
- testId={getTestId(testId, `item-${i}`)}
27
- {...item}
28
- onClick={(event) => handleItemClick(event, item)}
29
- />
30
- ))}
28
+ {items.map((item, i) => {
29
+ const itemProps: IListItemProps = {
30
+ testId: getTestId(item.testId, `item-${i}`),
31
+ ...item,
32
+ onClick: (event) => handleItemClick(event, item),
33
+ };
34
+
35
+ return (
36
+ <Fragment key={i}>
37
+ {isArrayNotEmpty(item.nestedItems) ? (
38
+ <WithPopup
39
+ eventType="hover"
40
+ tweakStyles={withPopupStyles}
41
+ placement="right-start"
42
+ popupOffset={0}
43
+ shouldRenderInBody={false}
44
+ trigger={({ isActive }) => <ListItem {...itemProps} isFocused={isActive} />}
45
+ >
46
+ <div className={classes.nestedItems}>
47
+ <List items={item.nestedItems} />
48
+ </div>
49
+ </WithPopup>
50
+ ) : (
51
+ <ListItem {...itemProps} />
52
+ )}
53
+ </Fragment>
54
+ );
55
+ })}
31
56
  </div>
32
57
  );
33
58
  };
@@ -8,6 +8,7 @@ export const useStyles = createThemedStyles('ListItem', {
8
8
  root: {
9
9
  display: 'flex',
10
10
  alignItems: 'center',
11
+ gap: ICON_GAP,
11
12
  boxSizing: 'border-box',
12
13
  fontSize: 16,
13
14
  whiteSpace: 'nowrap',
@@ -22,6 +23,8 @@ export const useStyles = createThemedStyles('ListItem', {
22
23
 
23
24
  destructive: {},
24
25
 
26
+ focused: {},
27
+
25
28
  disabledItem: {
26
29
  cursor: 'default',
27
30
  },
@@ -38,11 +41,12 @@ export const useStyles = createThemedStyles('ListItem', {
38
41
  icon: {
39
42
  width: ICON_SIZE,
40
43
  height: ICON_SIZE,
41
- marginRight: ICON_GAP,
42
44
  flexShrink: 0,
43
45
  },
44
46
 
45
- content: {},
47
+ content: {
48
+ flexGrow: 1,
49
+ },
46
50
  });
47
51
 
48
52
  export type IListItemStyles = ITweakStyles<typeof useStyles>;
@@ -1,19 +1,22 @@
1
- import { FC, Fragment, MouseEvent, KeyboardEvent, ReactNode } from 'react';
1
+ import { FC, MouseEvent, KeyboardEvent, ReactNode } from 'react';
2
2
  import clsx from 'clsx';
3
3
  import {
4
4
  addClickHandler,
5
5
  addDataTestId,
6
+ isArrayNotEmpty,
6
7
  isReactNodeNotEmpty,
7
8
  } from '@true-engineering/true-react-platform-helpers';
8
9
  import { addDataAttributes } from '../../../../helpers';
9
10
  import { ICommonProps } from '../../../../types';
10
- import { renderIcon, IIcon } from '../../../Icon';
11
+ import { renderIcon, IIcon, Icon } from '../../../Icon';
11
12
  import { useStyles, IListItemStyles } from './ListItem.styles';
12
13
 
13
14
  export interface IListItemProps extends ICommonProps<IListItemStyles> {
14
15
  item: ReactNode;
15
16
  view?: 'default' | 'destructive';
16
17
  icon?: IIcon;
18
+ nestedItems?: IListItemProps[];
19
+ isFocused?: boolean;
17
20
  disabled?: boolean;
18
21
  shouldDrawSpacerAbove?: boolean;
19
22
  shouldDrawSpacerBelow?: boolean;
@@ -22,9 +25,11 @@ export interface IListItemProps extends ICommonProps<IListItemStyles> {
22
25
  }
23
26
 
24
27
  export const ListItem: FC<IListItemProps> = ({
25
- disabled: isDisabled,
26
28
  icon,
27
29
  item,
30
+ nestedItems,
31
+ disabled: isDisabled,
32
+ isFocused,
28
33
  shouldDrawSpacerAbove,
29
34
  shouldDrawSpacerBelow,
30
35
  testId,
@@ -37,12 +42,13 @@ export const ListItem: FC<IListItemProps> = ({
37
42
  const classes = useStyles({ theme: tweakStyles });
38
43
 
39
44
  return (
40
- <Fragment>
45
+ <>
41
46
  {shouldDrawSpacerAbove && <div className={classes.spacer} />}
42
47
  <div
43
48
  className={clsx(classes.root, classes[view], {
44
49
  [classes.disabledItem]: isDisabled,
45
50
  [classes.withIconGap]: withIconGap,
51
+ [classes.focused]: isFocused,
46
52
  })}
47
53
  {...addClickHandler(onClick, !isDisabled)}
48
54
  {...addDataTestId(testId)}
@@ -50,8 +56,13 @@ export const ListItem: FC<IListItemProps> = ({
50
56
  >
51
57
  {isReactNodeNotEmpty(icon) && <span className={classes.icon}>{renderIcon(icon)}</span>}
52
58
  <span className={classes.content}>{item}</span>
59
+ {isArrayNotEmpty(nestedItems) && (
60
+ <span className={classes.icon}>
61
+ <Icon type="chevron-right" />
62
+ </span>
63
+ )}
53
64
  </div>
54
65
  {shouldDrawSpacerBelow && <div className={classes.spacer} />}
55
- </Fragment>
66
+ </>
56
67
  );
57
68
  };
@@ -4,14 +4,14 @@ import {
4
4
  useMemo,
5
5
  useRef,
6
6
  useState,
7
- type ChangeEvent,
8
- type CSSProperties,
9
- type FocusEvent,
10
- type FormEvent,
11
- type KeyboardEvent,
12
- type MouseEvent,
13
- type ReactNode,
14
- type SyntheticEvent,
7
+ ChangeEvent,
8
+ CSSProperties,
9
+ FocusEvent,
10
+ FormEvent,
11
+ KeyboardEvent,
12
+ MouseEvent,
13
+ ReactNode,
14
+ SyntheticEvent,
15
15
  } from 'react';
16
16
  import { Portal } from 'react-overlays';
17
17
  import clsx from 'clsx';
@@ -26,10 +26,10 @@ import {
26
26
  } from '@true-engineering/true-react-platform-helpers';
27
27
  import { hasExactParent } from '../../helpers';
28
28
  import { useDropdown, useIsMounted, useOnClickOutsideWithRef, useTweakStyles } from '../../hooks';
29
- import { IDropdownWithPopperOptions, type ICommonProps } from '../../types';
30
- import { renderIcon, type IIcon } from '../Icon';
31
- import { Input, type IInputProps } from '../Input';
32
- import { SearchInput, type ISearchInputProps } from '../SearchInput';
29
+ import { IDropdownWithPopperOptions, ICommonProps } from '../../types';
30
+ import { renderIcon, IIcon } from '../Icon';
31
+ import { Input, IInputProps } from '../Input';
32
+ import { SearchInput, ISearchInputProps } from '../SearchInput';
33
33
  import { SelectList } from './components';
34
34
  import { ALL_OPTION_INDEX, DEFAULT_OPTION_INDEX } from './constants';
35
35
  import {
@@ -39,7 +39,7 @@ import {
39
39
  getDefaultConvertToIdFunction,
40
40
  } from './helpers';
41
41
  import { IMultipleSelectValue } from './types';
42
- import { getInputStyles, searchInputStyles, useStyles, type ISelectStyles } from './Select.styles';
42
+ import { getInputStyles, searchInputStyles, useStyles, ISelectStyles } from './Select.styles';
43
43
 
44
44
  export interface ISelectProps<Value>
45
45
  extends Omit<IInputProps, 'value' | 'onChange' | 'onBlur' | 'type' | 'isActive' | 'tweakStyles'>,