@true-engineering/true-react-common-ui-kit 3.5.0 → 3.7.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.
Files changed (35) hide show
  1. package/README.md +28 -0
  2. package/dist/components/FiltersPane/FiltersPane.stories.d.ts +1 -2
  3. package/dist/components/FlexibleTable/FlexibleTable.d.ts +13 -21
  4. package/dist/components/FlexibleTable/FlexibleTable.stories.d.ts +3 -6
  5. package/dist/components/FlexibleTable/FlexibleTable.styles.d.ts +1 -1
  6. package/dist/components/FlexibleTable/components/FlexibleTableCell/FlexibleTableCell.d.ts +10 -14
  7. package/dist/components/FlexibleTable/components/FlexibleTableCell/FlexibleTableCell.styles.d.ts +1 -1
  8. package/dist/components/FlexibleTable/components/FlexibleTableRow/FlexibleTableRow.d.ts +19 -12
  9. package/dist/components/FlexibleTable/types.d.ts +8 -8
  10. package/dist/components/Icon/Icon.stories.d.ts +2 -2
  11. package/dist/components/ScrollIntoViewIfNeeded/ScrollIntoViewIfNeeded.d.ts +60 -56
  12. package/dist/components/Select/CustomSelect.stories.d.ts +13 -0
  13. package/dist/components/Select/MultiSelect.stories.d.ts +1 -2
  14. package/dist/components/Select/Select.d.ts +1 -1
  15. package/dist/components/Select/Select.stories.d.ts +1 -2
  16. package/dist/components/Select/components/SelectList/SelectList.d.ts +1 -1
  17. package/dist/true-react-common-ui-kit.js +485 -358
  18. package/dist/true-react-common-ui-kit.js.map +1 -1
  19. package/dist/true-react-common-ui-kit.umd.cjs +484 -357
  20. package/dist/true-react-common-ui-kit.umd.cjs.map +1 -1
  21. package/package.json +2 -1
  22. package/src/components/FiltersPane/components/Filter/Filter.tsx +1 -1
  23. package/src/components/FiltersPane/components/FilterValueView/FilterValueView.tsx +10 -9
  24. package/src/components/FlexibleTable/FlexibleTable.stories.tsx +28 -114
  25. package/src/components/FlexibleTable/FlexibleTable.styles.ts +1 -8
  26. package/src/components/FlexibleTable/FlexibleTable.tsx +89 -98
  27. package/src/components/FlexibleTable/components/FlexibleTableCell/FlexibleTableCell.styles.ts +6 -0
  28. package/src/components/FlexibleTable/components/FlexibleTableCell/FlexibleTableCell.tsx +48 -39
  29. package/src/components/FlexibleTable/components/FlexibleTableRow/FlexibleTableRow.tsx +89 -57
  30. package/src/components/FlexibleTable/helpers.ts +1 -3
  31. package/src/components/FlexibleTable/types.ts +9 -9
  32. package/src/components/Select/CustomSelect.stories.tsx +217 -0
  33. package/src/components/Select/Select.tsx +3 -2
  34. package/src/components/Select/components/SelectList/SelectList.tsx +2 -2
  35. package/src/hooks/use-dropdown.ts +2 -0
@@ -10,65 +10,64 @@ import {
10
10
  import { addDataAttributes } from '../../helpers';
11
11
  import { useMergedRefs, useTweakStyles } from '../../hooks';
12
12
  import { ICommonProps } from '../../types';
13
- import { Skeleton } from '../Skeleton';
14
13
  import { ThemedPreloader } from '../ThemedPreloader';
15
- import { FlexibleTableRow } from './components';
14
+ import { FlexibleTableRow, IFlexibleTableRowProps } from './components';
16
15
  import { hasHorizontalScrollBar } from './helpers';
17
- import { IFlexibleTableConfigType, IInfinityScrollConfig, ITitleComponent } from './types';
16
+ import {
17
+ ITableRow,
18
+ IFlexibleTableConfigType,
19
+ IInfinityScrollConfig,
20
+ IFlexibleTableRenderMode,
21
+ } from './types';
18
22
  import { useStyles, IFlexibleTableStyles } from './FlexibleTable.styles';
19
23
 
20
- // TODO: Заменить Record<string, any> на Record<string, unknown>
21
- export interface IFlexibleTableProps<Values extends Record<string, any>>
22
- extends ICommonProps<IFlexibleTableStyles> {
23
- content: Values[];
24
- headerContent?: Partial<Record<keyof Values, any>>;
25
- enabledColumns?: Array<keyof Values>;
26
- activeRows?: number[];
27
- config: IFlexibleTableConfigType<Values>;
24
+ export interface IFlexibleTableProps<Row extends ITableRow>
25
+ extends ICommonProps<IFlexibleTableStyles>,
26
+ Pick<
27
+ IFlexibleTableRowProps<Row>,
28
+ | 'uniqueField'
29
+ | 'activeRows'
30
+ | 'rowAttributes'
31
+ | 'isFirstColumnSticky'
32
+ | 'isExpandableRowComponentInitiallyOpen'
33
+ | 'expandableRowComponent'
34
+ | 'onRowClick'
35
+ | 'onRowHover'
36
+ > {
37
+ content: Row[];
38
+ /** @default 'table' */
39
+ renderMode?: IFlexibleTableRenderMode;
40
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
41
+ headerContent?: Partial<Record<keyof Row, any>>;
42
+ config: IFlexibleTableConfigType<Row>;
43
+ enabledColumns?: Array<keyof Row & string>;
28
44
  /** @default false */
29
- isHorizontallyScrollable?: boolean;
45
+ isLoading?: boolean;
30
46
  /** @default false */
31
- isFirstColumnSticky?: boolean;
47
+ isHorizontallyScrollable?: boolean;
32
48
  infinityScrollConfig?: IInfinityScrollConfig;
33
- /** @default Индекс строки */
34
- uniqueField?: keyof Values;
35
- onHeadClick?: (column: keyof Values) => void;
36
- /** @default false */
37
- isLoading?: boolean;
38
- // TODO: Заменить string на Generic Values[uniqueField]
39
- onRowClick?: (id: string) => void;
40
- onRowHover?: (id?: string) => void;
41
- rowAttributes?: Array<keyof Values>;
49
+ onHeadClick?: (column: keyof Row) => void;
42
50
  refForScroll?: RefObject<HTMLDivElement>;
43
51
  nothingFoundContent?: ReactNode;
44
- /** @default 'table' */
45
- renderMode?: 'table' | 'divs';
46
- expandableRowComponent?: (item: Values, isOpen: boolean, close: () => void) => ReactNode;
47
52
  }
48
53
 
49
- export function FlexibleTable<Values extends Record<string, any>>({
50
- data,
51
- tweakStyles,
54
+ export function FlexibleTable<Row extends ITableRow>({
52
55
  content,
53
56
  headerContent,
54
57
  config,
55
- activeRows,
56
58
  enabledColumns,
59
+ isLoading = false,
57
60
  isHorizontallyScrollable = false,
58
- isFirstColumnSticky = false,
59
61
  infinityScrollConfig,
60
- uniqueField,
61
- isLoading = false,
62
62
  renderMode = 'table',
63
- onHeadClick,
64
- onRowHover,
65
- onRowClick,
66
63
  refForScroll,
67
- rowAttributes,
68
64
  nothingFoundContent,
65
+ data,
69
66
  testId,
70
- expandableRowComponent,
71
- }: IFlexibleTableProps<Values>): JSX.Element {
67
+ tweakStyles,
68
+ onHeadClick,
69
+ ...restProps
70
+ }: IFlexibleTableProps<Row>): JSX.Element {
72
71
  const classes = useStyles({ theme: tweakStyles });
73
72
 
74
73
  const tweakTableRowStyles = useTweakStyles({
@@ -80,10 +79,19 @@ export function FlexibleTable<Values extends Record<string, any>>({
80
79
  const observer = useRef<IntersectionObserver>();
81
80
  const scrollRef = useRef<HTMLDivElement>(null);
82
81
 
83
- const showedColumns = useMemo(
84
- () => enabledColumns ?? Object.keys(config),
85
- [enabledColumns, config],
86
- );
82
+ const columns = useMemo(() => enabledColumns ?? Object.keys(config), [enabledColumns, config]);
83
+
84
+ const hasInfiniteScroll = isNotEmpty(infinityScrollConfig);
85
+ const { uniqueField, isFirstColumnSticky = false } = restProps;
86
+
87
+ const tableRowProps: Omit<IFlexibleTableRowProps<Row>, 'item' | 'index'> = {
88
+ ...restProps,
89
+ renderMode,
90
+ config,
91
+ columns,
92
+ isLoading,
93
+ tweakStyles: tweakTableRowStyles,
94
+ };
87
95
 
88
96
  const getDataScrollAttributeSetter = useCallback(
89
97
  (key: string, setter: (el: HTMLDivElement) => boolean) => (el?: HTMLDivElement) => {
@@ -97,14 +105,14 @@ export function FlexibleTable<Values extends Record<string, any>>({
97
105
  );
98
106
 
99
107
  // Когда таблица имеет скроллбар - добавляем аттрибут scrollable
100
- const setHasScrollBarAttribute = useCallback(
101
- getDataScrollAttributeSetter('scrollable', hasHorizontalScrollBar),
108
+ const setHasScrollBarAttribute = useMemo(
109
+ () => getDataScrollAttributeSetter('scrollable', hasHorizontalScrollBar),
102
110
  [getDataScrollAttributeSetter],
103
111
  );
104
112
 
105
113
  // Когда таблица проскроллена - добавляем аттрибут scrolled
106
- const setIsScrolledAttribute = useCallback(
107
- getDataScrollAttributeSetter('scrolled', (el) => el.scrollLeft > 0),
114
+ const setIsScrolledAttribute = useMemo(
115
+ () => getDataScrollAttributeSetter('scrolled', (el) => el.scrollLeft > 0),
108
116
  [getDataScrollAttributeSetter],
109
117
  );
110
118
 
@@ -122,31 +130,31 @@ export function FlexibleTable<Values extends Record<string, any>>({
122
130
  ]);
123
131
 
124
132
  const initIntersectionObserver = useCallback(
125
- (node: HTMLDivElement) => {
126
- if (infinityScrollConfig) {
127
- if (
128
- infinityScrollConfig.isLoading ||
129
- infinityScrollConfig.activePage >= infinityScrollConfig.totalPages
130
- ) {
131
- return;
132
- }
133
-
134
- if (observer.current) {
135
- observer.current.disconnect();
136
- }
133
+ (node: HTMLDivElement | null) => {
134
+ if (
135
+ !hasInfiniteScroll ||
136
+ infinityScrollConfig.isLoading ||
137
+ infinityScrollConfig.activePage >= infinityScrollConfig.totalPages
138
+ ) {
139
+ return;
140
+ }
137
141
 
138
- observer.current = new IntersectionObserver((entries) => {
139
- if (entries[0].isIntersecting) {
140
- infinityScrollConfig.onInfinityScroll(infinityScrollConfig.activePage + 1);
141
- }
142
- });
142
+ if (observer.current) {
143
+ observer.current.disconnect();
144
+ }
143
145
 
144
- if (node) {
145
- observer.current.observe(node);
146
+ observer.current = new IntersectionObserver((entries) => {
147
+ if (entries[0].isIntersecting) {
148
+ infinityScrollConfig.onInfinityScroll(infinityScrollConfig.activePage + 1);
146
149
  }
150
+ });
151
+
152
+ if (node) {
153
+ observer.current.observe(node);
147
154
  }
148
155
  },
149
156
  [
157
+ hasInfiniteScroll,
150
158
  infinityScrollConfig?.activePage,
151
159
  infinityScrollConfig?.totalPages,
152
160
  infinityScrollConfig?.onInfinityScroll,
@@ -174,7 +182,7 @@ export function FlexibleTable<Values extends Record<string, any>>({
174
182
  scrollContainer.removeEventListener('scroll', scrollHandler);
175
183
  window.removeEventListener('resize', resizeHandler);
176
184
  };
177
- }, [scrollRef, setIsScrolledAttribute, setHasScrollBarAttribute]);
185
+ }, [scrollRef, isHorizontallyScrollable, setIsScrolledAttribute, setHasScrollBarAttribute]);
178
186
 
179
187
  const Table = renderMode === 'divs' ? 'div' : 'table';
180
188
  const TableHead = renderMode === 'divs' ? 'div' : 'thead';
@@ -192,18 +200,13 @@ export function FlexibleTable<Values extends Record<string, any>>({
192
200
  >
193
201
  <TableHead className={classes.head}>
194
202
  <TableRow className={classes.headerRow}>
195
- {showedColumns.map((key, i) => {
203
+ {columns.map((key, i) => {
196
204
  const itemConfig = config?.[key];
197
-
198
- let titleContent = itemConfig?.title ?? '';
199
-
200
- if (itemConfig?.titleComponent !== undefined) {
201
- const TitleComponent = itemConfig?.titleComponent as ITitleComponent<any>;
202
- titleContent = <TitleComponent value={headerContent?.[key]} />;
203
- }
205
+ const TitleComponent = itemConfig?.titleComponent;
204
206
 
205
207
  return (
206
208
  <TableHeader
209
+ key={key}
207
210
  className={clsx(classes.header, {
208
211
  [classes.headerSticky]: isFirstColumnSticky && i === 0,
209
212
  [classes.headerSecond]: isFirstColumnSticky && i === 1,
@@ -214,10 +217,13 @@ export function FlexibleTable<Values extends Record<string, any>>({
214
217
  maxWidth: itemConfig?.maxWidth,
215
218
  textAlign: itemConfig?.titleAlign ?? 'left',
216
219
  }}
217
- key={key as string}
218
220
  onClick={() => onHeadClick?.(key)}
219
221
  >
220
- {titleContent}
222
+ {isNotEmpty(TitleComponent) ? (
223
+ <TitleComponent value={headerContent?.[key]} />
224
+ ) : (
225
+ itemConfig?.title ?? ''
226
+ )}
221
227
  </TableHeader>
222
228
  );
223
229
  })}
@@ -226,19 +232,13 @@ export function FlexibleTable<Values extends Record<string, any>>({
226
232
  <TableBody className={classes.body}>
227
233
  {isLoading ? (
228
234
  indexMap(6, (i) => (
229
- <TableRow className={classes.skeletonRow} key={i}>
230
- {showedColumns.map((_, j) => (
231
- <TableCell className={classes.skeleton} key={j}>
232
- <Skeleton />
233
- </TableCell>
234
- ))}
235
- </TableRow>
235
+ <FlexibleTableRow {...tableRowProps} key={i} item={{} as Row} index={i} />
236
236
  ))
237
237
  ) : (
238
238
  <>
239
239
  {shouldShowNothingFound && (
240
240
  <TableRow className={classes.nothingFoundRow}>
241
- <TableCell className={classes.nothingFound} colSpan={showedColumns.length}>
241
+ <TableCell className={classes.nothingFound} colSpan={columns.length}>
242
242
  {nothingFoundContent}
243
243
  </TableCell>
244
244
  </TableRow>
@@ -246,25 +246,16 @@ export function FlexibleTable<Values extends Record<string, any>>({
246
246
 
247
247
  {content.map((item, i) => (
248
248
  <FlexibleTableRow
249
- item={item}
250
- uniqueField={uniqueField}
251
- isActive={activeRows?.includes(i) ?? false}
252
- isFirstColumnSticky={isFirstColumnSticky}
253
- onRowClick={onRowClick}
254
- onRowHover={onRowHover}
255
- enabledColumns={enabledColumns}
256
- config={config}
249
+ {...tableRowProps}
257
250
  key={isNotEmpty(uniqueField) ? item[uniqueField] : i}
258
- rowAttributes={rowAttributes}
259
- renderMode={renderMode}
260
- tweakStyles={tweakTableRowStyles}
261
- expandableRowComponent={expandableRowComponent}
251
+ item={item}
252
+ index={i}
262
253
  />
263
254
  ))}
264
255
 
265
- {infinityScrollConfig !== undefined && !infinityScrollConfig.isLastPage && (
256
+ {hasInfiniteScroll && !infinityScrollConfig.isLastPage && (
266
257
  <TableRow className={classes.loaderRow}>
267
- <TableCell className={classes.loaderCell} colSpan={showedColumns.length}>
258
+ <TableCell className={classes.loaderCell} colSpan={columns.length}>
268
259
  <div ref={initIntersectionObserver} className={classes.loader}>
269
260
  <ThemedPreloader type="dots" />
270
261
  </div>
@@ -27,6 +27,12 @@ export const useStyles = createThemedStyles('FlexibleTableCell', {
27
27
  second: {
28
28
  paddingLeft: STICKY_SHADOW_PADDING,
29
29
  },
30
+
31
+ loading: {},
32
+
33
+ skeleton: {
34
+ height: 21,
35
+ },
30
36
  });
31
37
 
32
38
  export type IFlexibleTableCellStyles = ITweakStyles<typeof useStyles>;
@@ -1,52 +1,61 @@
1
- import { ReactNode } from 'react';
2
1
  import clsx from 'clsx';
3
2
  import { isNotEmpty } from '@true-engineering/true-react-platform-helpers';
4
- import type { ICommonProps } from '../../../../types';
3
+ import { ICommonProps } from '../../../../types';
4
+ import { Skeleton } from '../../../Skeleton';
5
5
  import { formatCellContent } from '../../helpers';
6
- import type { IFlexibleTableConfigType } from '../../types';
6
+ import {
7
+ ITableRow,
8
+ IValueComponent,
9
+ IFlexibleTableConfigType,
10
+ IFlexibleTableRenderMode,
11
+ } from '../../types';
7
12
  import { useStyles, IFlexibleTableCellStyles } from './FlexibleTableCell.styles';
8
13
 
9
- export interface IFlexibleTableCellProps<Values extends Record<string, any>>
10
- extends Pick<ICommonProps<IFlexibleTableCellStyles>, 'tweakStyles'> {
11
- item: Values;
12
- columnName: keyof Values;
13
- config: IFlexibleTableConfigType<Values>;
14
- /** @default 'table' */
15
- renderMode?: 'table' | 'divs';
16
- isFocusedRow?: boolean;
14
+ export interface IFlexibleTableCellProps<Row extends ITableRow>
15
+ extends Pick<ICommonProps<IFlexibleTableCellStyles>, 'tweakStyles'>,
16
+ Pick<
17
+ Parameters<IValueComponent<Row, unknown>>[0],
18
+ | 'isFocusedRow'
19
+ | 'isNestedComponentExpanded'
20
+ | 'isRowNestedComponentExpanded'
21
+ | 'onSetNestedComponent'
22
+ > {
23
+ row: Row;
24
+ columnName: keyof Row;
25
+ config: IFlexibleTableConfigType<Row>;
26
+ renderMode: IFlexibleTableRenderMode;
17
27
  isSecond?: boolean;
18
28
  isSticky?: boolean;
19
- isNestedComponentExpanded: boolean;
20
- isRowNestedComponentExpanded: boolean;
21
- onSetNestedComponent: (component?: ReactNode) => void;
29
+ isLoading?: boolean;
22
30
  }
23
31
 
24
- export function FlexibleTableCell<Values extends Record<string, any>>({
25
- item,
32
+ export function FlexibleTableCell<Row extends ITableRow>({
33
+ row,
26
34
  columnName,
27
35
  config,
28
- renderMode = 'table',
29
- isFocusedRow,
36
+ renderMode,
30
37
  isSecond,
31
38
  isSticky,
32
- isNestedComponentExpanded,
33
- isRowNestedComponentExpanded,
39
+ isLoading,
34
40
  tweakStyles,
35
- onSetNestedComponent,
36
- }: IFlexibleTableCellProps<Values>): JSX.Element {
41
+ ...valueComponentProps
42
+ }: IFlexibleTableCellProps<Row>): JSX.Element {
37
43
  const classes = useStyles({ theme: tweakStyles });
38
44
 
39
- const { component, cellAlign, position, right, left, cellVerticalAlign } =
45
+ const { component, left, right, position, cellAlign, cellVerticalAlign } =
40
46
  config[columnName] ?? {};
41
47
 
42
- const value = item[columnName];
48
+ const value = row[columnName];
43
49
 
44
50
  const TableCell = renderMode === 'divs' ? 'div' : 'td';
45
51
 
46
52
  return (
47
53
  <TableCell
48
- key={columnName as string}
49
- className={clsx(classes.root, { [classes.sticky]: isSticky, [classes.second]: isSecond })}
54
+ className={clsx(classes.root, {
55
+ [classes.sticky]: isSticky,
56
+ [classes.second]: isSecond,
57
+ [classes.loading]: isLoading,
58
+ })}
50
59
  style={{
51
60
  textAlign: cellAlign,
52
61
  position: isSticky ? 'sticky' : position,
@@ -55,19 +64,19 @@ export function FlexibleTableCell<Values extends Record<string, any>>({
55
64
  verticalAlign: cellVerticalAlign,
56
65
  }}
57
66
  >
58
- {isNotEmpty(value) && (
59
- <>
60
- {isNotEmpty(component)
61
- ? component({
62
- value,
63
- row: item,
64
- isFocusedRow,
65
- isNestedComponentExpanded,
66
- isRowNestedComponentExpanded,
67
- onSetNestedComponent,
68
- })
69
- : formatCellContent(value, config[columnName])}
70
- </>
67
+ {isLoading ? (
68
+ <div className={classes.skeleton}>
69
+ <Skeleton />
70
+ </div>
71
+ ) : (
72
+ isNotEmpty(value) && (
73
+ <>
74
+ {/* TODO: Рендерить как настоящий компонент */}
75
+ {isNotEmpty(component)
76
+ ? component({ ...valueComponentProps, value, row })
77
+ : formatCellContent(value, config[columnName])}
78
+ </>
79
+ )
71
80
  )}
72
81
  </TableCell>
73
82
  );
@@ -1,45 +1,64 @@
1
- import { ReactNode, useState, memo } from 'react';
1
+ import { ReactNode, useState, memo, MouseEvent } from 'react';
2
2
  import clsx from 'clsx';
3
- import { isNotEmpty } from '@true-engineering/true-react-platform-helpers';
3
+ import {
4
+ isEmpty,
5
+ isFunction,
6
+ isNotEmpty,
7
+ isReactNodeNotEmpty,
8
+ } from '@true-engineering/true-react-platform-helpers';
4
9
  import { addDataAttributes } from '../../../../helpers';
5
10
  import { useTweakStyles } from '../../../../hooks';
6
11
  import { ICommonProps, IDataAttributes } from '../../../../types';
7
- import { IFlexibleTableConfigType, INestedComponent } from '../../types';
12
+ import {
13
+ ITableRow,
14
+ IFlexibleTableConfigType,
15
+ IFlexibleTableRenderMode,
16
+ INestedComponent,
17
+ } from '../../types';
8
18
  import { FlexibleTableCell } from '../FlexibleTableCell';
9
19
  import { useStyles, IFlexibleTableRowStyles } from './FlexibleTableRow.styles';
10
20
 
11
- // TODO: Заменить Record<string, any> на Record<string, unknown>
12
- export interface IFlexibleTableRowProps<Values extends Record<string, any>>
21
+ export interface IFlexibleTableRowProps<Row extends ITableRow>
13
22
  extends Pick<ICommonProps<IFlexibleTableRowStyles>, 'tweakStyles'> {
14
- item: Values;
15
- uniqueField?: keyof Values;
23
+ item: Row;
24
+ index: number;
25
+ uniqueField?: keyof Row;
26
+ renderMode: IFlexibleTableRenderMode;
27
+ /** Индексы строк, на которые навешивается класс `active` */
28
+ activeRows?: number[];
29
+ /** @default false */
16
30
  isFirstColumnSticky?: boolean;
17
- isActive: boolean;
18
- config: IFlexibleTableConfigType<Values>;
19
- enabledColumns?: Array<keyof Values>;
20
- rowAttributes?: Array<keyof Values>;
21
- /** @default 'table' */
22
- renderMode?: 'table' | 'divs';
23
- expandableRowComponent?: (item: Values, isOpen: boolean, close: () => void) => ReactNode;
31
+ /** @default false */
32
+ isLoading?: boolean;
33
+ config: IFlexibleTableConfigType<Row>;
34
+ columns: Array<keyof Row & string>;
35
+ rowAttributes?: Array<keyof Row>;
36
+ /** @default false */
37
+ isExpandableRowComponentInitiallyOpen?: boolean | ((row: Row, index: number) => boolean);
38
+ /** Возвращает React-элемент, который отрисуется под строкой при нажатии на неё */
39
+ expandableRowComponent?: (item: Row, isOpen: boolean, close: () => void) => ReactNode;
24
40
  // TODO: Заменить string на Generic Values[uniqueField]
25
41
  onRowHover?: (id?: string) => void;
26
42
  onRowClick?: (id: string) => void;
27
43
  }
28
44
 
29
- function FlexibleTableRowInner<Values extends Record<string, any>>({
45
+ function FlexibleTableRowInner<Row extends ITableRow>({
30
46
  item,
47
+ index,
48
+ config,
49
+ columns,
31
50
  uniqueField,
51
+ renderMode,
52
+ activeRows,
32
53
  isFirstColumnSticky,
33
- isActive,
34
- config,
35
- enabledColumns,
54
+ isLoading = false,
36
55
  rowAttributes,
37
- renderMode = 'table',
56
+ isExpandableRowComponentInitiallyOpen = false,
38
57
  tweakStyles,
39
58
  expandableRowComponent,
40
59
  onRowHover,
41
60
  onRowClick,
42
- }: IFlexibleTableRowProps<Values>): JSX.Element {
61
+ }: IFlexibleTableRowProps<Row>): JSX.Element {
43
62
  const classes = useStyles({ theme: tweakStyles });
44
63
 
45
64
  const tweakTableCellStyles = useTweakStyles({
@@ -49,24 +68,43 @@ function FlexibleTableRowInner<Values extends Record<string, any>>({
49
68
  });
50
69
 
51
70
  const [isFocused, setFocused] = useState(false);
52
- const [nestedComponent, setNestedComponent] = useState<INestedComponent>({
53
- isOpen: false,
71
+ const [nestedComponent, setNestedComponent] = useState<INestedComponent>(() => {
72
+ const isOpen = isFunction(isExpandableRowComponentInitiallyOpen)
73
+ ? isExpandableRowComponentInitiallyOpen(item, index)
74
+ : isExpandableRowComponentInitiallyOpen;
75
+
76
+ const component = isOpen
77
+ ? expandableRowComponent?.(item, true, () => {
78
+ setNestedComponent({ isOpen: false });
79
+ })
80
+ : undefined;
81
+
82
+ return isReactNodeNotEmpty(component) ? { isOpen: true, component } : { isOpen: false };
54
83
  });
55
84
 
85
+ const isActive = activeRows?.includes(index) ?? false;
86
+ const isEditable = !isLoading && (isNotEmpty(onRowClick) || isNotEmpty(onRowHover));
87
+ const isClickable = !isLoading && (isNotEmpty(onRowClick) || isNotEmpty(expandableRowComponent));
88
+
89
+ const { isOpen: isNestedComponentExpanded, cellKey: nestedComponentCellKey } = nestedComponent;
90
+
56
91
  // уникальная разработка, позволяющая прокидывать data-атрибуты в <tr>
57
92
  // например: rowAttributes={['id']} => <tr data-id="x" />
58
- const rowData = rowAttributes?.reduce((acc: IDataAttributes, cur) => {
59
- const val = item[cur];
60
- if (val !== undefined) {
61
- acc[cur as string] = String(item[cur]);
93
+ const rowData = rowAttributes?.reduce<IDataAttributes>(
94
+ (acc, cur) => ({ ...acc, [cur]: item[cur] }),
95
+ {},
96
+ );
97
+
98
+ const handleMouseEnter = (event: MouseEvent) => {
99
+ if (isNotEmpty(uniqueField) && isNotEmpty(onRowHover)) {
100
+ event.stopPropagation();
101
+ onRowHover(item[uniqueField]);
102
+ setFocused(true);
62
103
  }
63
- return acc;
64
- }, {});
104
+ };
65
105
 
66
106
  const handleMouseLeave = () => {
67
- if (onRowHover) {
68
- onRowHover(undefined);
69
- }
107
+ onRowHover?.(undefined);
70
108
  setFocused(false);
71
109
  };
72
110
 
@@ -81,28 +119,25 @@ function FlexibleTableRowInner<Values extends Record<string, any>>({
81
119
  };
82
120
 
83
121
  const handleRowClick = () => {
84
- if (uniqueField !== undefined) {
122
+ if (isNotEmpty(uniqueField)) {
85
123
  onRowClick?.(item[uniqueField]);
86
124
  }
87
125
 
88
126
  if (isNotEmpty(expandableRowComponent)) {
89
127
  const newNestedComponent = expandableRowComponent(item, true, closeNestedComponent);
90
128
 
91
- if (!nestedComponent.isOpen && newNestedComponent !== null) {
129
+ if (!isNestedComponentExpanded && newNestedComponent !== null) {
92
130
  updateNestedComponent(newNestedComponent);
93
131
  return;
94
132
  }
95
133
 
96
- if (nestedComponent.isOpen && nestedComponent.cellKey === undefined) {
134
+ if (isNestedComponentExpanded && isEmpty(nestedComponentCellKey)) {
97
135
  closeNestedComponent();
98
136
  return;
99
137
  }
100
138
  }
101
139
  };
102
140
 
103
- const items = enabledColumns ?? Object.keys(config);
104
- const isEditable = isNotEmpty(onRowClick) || isNotEmpty(onRowHover);
105
-
106
141
  const TableRow = renderMode === 'divs' ? 'div' : 'tr';
107
142
  const TableCell = renderMode === 'divs' ? 'div' : 'td';
108
143
 
@@ -112,47 +147,44 @@ function FlexibleTableRowInner<Values extends Record<string, any>>({
112
147
  className={clsx(classes.root, {
113
148
  [classes.active]: isActive,
114
149
  [classes.editable]: isEditable,
115
- [classes.clickable]: isNotEmpty(onRowClick) || isNotEmpty(expandableRowComponent),
150
+ [classes.clickable]: isClickable,
151
+ })}
152
+ {...(!isLoading && {
153
+ onClick: handleRowClick,
154
+ onMouseEnter: handleMouseEnter,
155
+ onMouseLeave: handleMouseLeave,
116
156
  })}
117
- onMouseEnter={(e) => {
118
- if (uniqueField !== undefined && onRowHover !== undefined) {
119
- e.stopPropagation();
120
- onRowHover(item[uniqueField]);
121
- setFocused(true);
122
- }
123
- }}
124
- onMouseLeave={handleMouseLeave}
125
- onClick={handleRowClick}
126
157
  {...addDataAttributes({
127
158
  ...rowData,
128
159
  active: isActive ? true : undefined,
129
160
  editable: isEditable ? true : undefined,
130
- isExpandableComponentActive: nestedComponent.isOpen ? true : undefined,
161
+ isExpandableComponentActive: isNestedComponentExpanded ? true : undefined,
131
162
  })}
132
163
  >
133
- {items.map((key, i) => (
164
+ {columns.map((key, i) => (
134
165
  <FlexibleTableCell
135
- columnName={key}
166
+ key={key}
136
167
  isSticky={isFirstColumnSticky && i === 0}
137
168
  isSecond={isFirstColumnSticky && i === 1}
138
- key={key as string}
139
- item={item}
169
+ isLoading={isLoading}
170
+ row={item}
140
171
  config={config}
172
+ columnName={key}
141
173
  tweakStyles={tweakTableCellStyles}
142
174
  renderMode={renderMode}
143
175
  isFocusedRow={isFocused}
144
- isNestedComponentExpanded={nestedComponent.isOpen && nestedComponent.cellKey === key}
176
+ isNestedComponentExpanded={isNestedComponentExpanded && nestedComponentCellKey === key}
145
177
  isRowNestedComponentExpanded={
146
- nestedComponent.isOpen && nestedComponent.cellKey === undefined
178
+ isNestedComponentExpanded && isEmpty(nestedComponentCellKey)
147
179
  }
148
- onSetNestedComponent={(component) => updateNestedComponent(component, key as string)}
180
+ onSetNestedComponent={(component) => updateNestedComponent(component, key)}
149
181
  />
150
182
  ))}
151
183
  </TableRow>
152
184
 
153
- {nestedComponent.isOpen && (
185
+ {isNestedComponentExpanded && (
154
186
  <TableRow className={classes.root}>
155
- <TableCell className={classes.nestedComponent} colSpan={items.length}>
187
+ <TableCell className={classes.nestedComponent} colSpan={columns.length}>
156
188
  {nestedComponent.component}
157
189
  </TableCell>
158
190
  </TableRow>
@@ -10,6 +10,4 @@ export const formatCellContent = <Values>(
10
10
  value: unknown,
11
11
  config?: IFlexibleTableConfigType<Values>[keyof Values],
12
12
  ): string =>
13
- value instanceof Date
14
- ? format(value as Date, config?.dateFormat ?? DEFAULT_DATE_FORMAT)
15
- : String(value);
13
+ value instanceof Date ? format(value, config?.dateFormat ?? DEFAULT_DATE_FORMAT) : String(value);