@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.
- package/README.md +28 -0
- package/dist/components/FiltersPane/FiltersPane.stories.d.ts +1 -2
- package/dist/components/FlexibleTable/FlexibleTable.d.ts +13 -21
- package/dist/components/FlexibleTable/FlexibleTable.stories.d.ts +3 -6
- package/dist/components/FlexibleTable/FlexibleTable.styles.d.ts +1 -1
- package/dist/components/FlexibleTable/components/FlexibleTableCell/FlexibleTableCell.d.ts +10 -14
- package/dist/components/FlexibleTable/components/FlexibleTableCell/FlexibleTableCell.styles.d.ts +1 -1
- package/dist/components/FlexibleTable/components/FlexibleTableRow/FlexibleTableRow.d.ts +19 -12
- package/dist/components/FlexibleTable/types.d.ts +8 -8
- package/dist/components/Icon/Icon.stories.d.ts +2 -2
- package/dist/components/ScrollIntoViewIfNeeded/ScrollIntoViewIfNeeded.d.ts +60 -56
- package/dist/components/Select/CustomSelect.stories.d.ts +13 -0
- package/dist/components/Select/MultiSelect.stories.d.ts +1 -2
- package/dist/components/Select/Select.d.ts +1 -1
- package/dist/components/Select/Select.stories.d.ts +1 -2
- package/dist/components/Select/components/SelectList/SelectList.d.ts +1 -1
- package/dist/true-react-common-ui-kit.js +485 -358
- package/dist/true-react-common-ui-kit.js.map +1 -1
- package/dist/true-react-common-ui-kit.umd.cjs +484 -357
- package/dist/true-react-common-ui-kit.umd.cjs.map +1 -1
- package/package.json +2 -1
- package/src/components/FiltersPane/components/Filter/Filter.tsx +1 -1
- package/src/components/FiltersPane/components/FilterValueView/FilterValueView.tsx +10 -9
- package/src/components/FlexibleTable/FlexibleTable.stories.tsx +28 -114
- package/src/components/FlexibleTable/FlexibleTable.styles.ts +1 -8
- package/src/components/FlexibleTable/FlexibleTable.tsx +89 -98
- package/src/components/FlexibleTable/components/FlexibleTableCell/FlexibleTableCell.styles.ts +6 -0
- package/src/components/FlexibleTable/components/FlexibleTableCell/FlexibleTableCell.tsx +48 -39
- package/src/components/FlexibleTable/components/FlexibleTableRow/FlexibleTableRow.tsx +89 -57
- package/src/components/FlexibleTable/helpers.ts +1 -3
- package/src/components/FlexibleTable/types.ts +9 -9
- package/src/components/Select/CustomSelect.stories.tsx +217 -0
- package/src/components/Select/Select.tsx +3 -2
- package/src/components/Select/components/SelectList/SelectList.tsx +2 -2
- 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 {
|
|
16
|
+
import {
|
|
17
|
+
ITableRow,
|
|
18
|
+
IFlexibleTableConfigType,
|
|
19
|
+
IInfinityScrollConfig,
|
|
20
|
+
IFlexibleTableRenderMode,
|
|
21
|
+
} from './types';
|
|
18
22
|
import { useStyles, IFlexibleTableStyles } from './FlexibleTable.styles';
|
|
19
23
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
45
|
+
isLoading?: boolean;
|
|
30
46
|
/** @default false */
|
|
31
|
-
|
|
47
|
+
isHorizontallyScrollable?: boolean;
|
|
32
48
|
infinityScrollConfig?: IInfinityScrollConfig;
|
|
33
|
-
|
|
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<
|
|
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
|
-
|
|
71
|
-
|
|
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
|
|
84
|
-
|
|
85
|
-
|
|
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 =
|
|
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 =
|
|
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 (
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
}
|
|
142
|
-
});
|
|
142
|
+
if (observer.current) {
|
|
143
|
+
observer.current.disconnect();
|
|
144
|
+
}
|
|
143
145
|
|
|
144
|
-
|
|
145
|
-
|
|
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
|
-
{
|
|
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
|
-
{
|
|
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
|
-
<
|
|
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={
|
|
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
|
-
|
|
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
|
-
|
|
259
|
-
|
|
260
|
-
tweakStyles={tweakTableRowStyles}
|
|
261
|
-
expandableRowComponent={expandableRowComponent}
|
|
251
|
+
item={item}
|
|
252
|
+
index={i}
|
|
262
253
|
/>
|
|
263
254
|
))}
|
|
264
255
|
|
|
265
|
-
{
|
|
256
|
+
{hasInfiniteScroll && !infinityScrollConfig.isLastPage && (
|
|
266
257
|
<TableRow className={classes.loaderRow}>
|
|
267
|
-
<TableCell className={classes.loaderCell} colSpan={
|
|
258
|
+
<TableCell className={classes.loaderCell} colSpan={columns.length}>
|
|
268
259
|
<div ref={initIntersectionObserver} className={classes.loader}>
|
|
269
260
|
<ThemedPreloader type="dots" />
|
|
270
261
|
</div>
|
package/src/components/FlexibleTable/components/FlexibleTableCell/FlexibleTableCell.styles.ts
CHANGED
|
@@ -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
|
|
3
|
+
import { ICommonProps } from '../../../../types';
|
|
4
|
+
import { Skeleton } from '../../../Skeleton';
|
|
5
5
|
import { formatCellContent } from '../../helpers';
|
|
6
|
-
import
|
|
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<
|
|
10
|
-
extends Pick<ICommonProps<IFlexibleTableCellStyles>, 'tweakStyles'
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
20
|
-
isRowNestedComponentExpanded: boolean;
|
|
21
|
-
onSetNestedComponent: (component?: ReactNode) => void;
|
|
29
|
+
isLoading?: boolean;
|
|
22
30
|
}
|
|
23
31
|
|
|
24
|
-
export function FlexibleTableCell<
|
|
25
|
-
|
|
32
|
+
export function FlexibleTableCell<Row extends ITableRow>({
|
|
33
|
+
row,
|
|
26
34
|
columnName,
|
|
27
35
|
config,
|
|
28
|
-
renderMode
|
|
29
|
-
isFocusedRow,
|
|
36
|
+
renderMode,
|
|
30
37
|
isSecond,
|
|
31
38
|
isSticky,
|
|
32
|
-
|
|
33
|
-
isRowNestedComponentExpanded,
|
|
39
|
+
isLoading,
|
|
34
40
|
tweakStyles,
|
|
35
|
-
|
|
36
|
-
}: IFlexibleTableCellProps<
|
|
41
|
+
...valueComponentProps
|
|
42
|
+
}: IFlexibleTableCellProps<Row>): JSX.Element {
|
|
37
43
|
const classes = useStyles({ theme: tweakStyles });
|
|
38
44
|
|
|
39
|
-
const { component,
|
|
45
|
+
const { component, left, right, position, cellAlign, cellVerticalAlign } =
|
|
40
46
|
config[columnName] ?? {};
|
|
41
47
|
|
|
42
|
-
const value =
|
|
48
|
+
const value = row[columnName];
|
|
43
49
|
|
|
44
50
|
const TableCell = renderMode === 'divs' ? 'div' : 'td';
|
|
45
51
|
|
|
46
52
|
return (
|
|
47
53
|
<TableCell
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
{
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
}
|
|
69
|
-
|
|
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 {
|
|
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 {
|
|
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
|
-
|
|
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:
|
|
15
|
-
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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<
|
|
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
|
-
|
|
34
|
-
config,
|
|
35
|
-
enabledColumns,
|
|
54
|
+
isLoading = false,
|
|
36
55
|
rowAttributes,
|
|
37
|
-
|
|
56
|
+
isExpandableRowComponentInitiallyOpen = false,
|
|
38
57
|
tweakStyles,
|
|
39
58
|
expandableRowComponent,
|
|
40
59
|
onRowHover,
|
|
41
60
|
onRowClick,
|
|
42
|
-
}: IFlexibleTableRowProps<
|
|
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
|
|
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(
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
|
|
64
|
-
}, {});
|
|
104
|
+
};
|
|
65
105
|
|
|
66
106
|
const handleMouseLeave = () => {
|
|
67
|
-
|
|
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
|
|
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 (!
|
|
129
|
+
if (!isNestedComponentExpanded && newNestedComponent !== null) {
|
|
92
130
|
updateNestedComponent(newNestedComponent);
|
|
93
131
|
return;
|
|
94
132
|
}
|
|
95
133
|
|
|
96
|
-
if (
|
|
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]:
|
|
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:
|
|
161
|
+
isExpandableComponentActive: isNestedComponentExpanded ? true : undefined,
|
|
131
162
|
})}
|
|
132
163
|
>
|
|
133
|
-
{
|
|
164
|
+
{columns.map((key, i) => (
|
|
134
165
|
<FlexibleTableCell
|
|
135
|
-
|
|
166
|
+
key={key}
|
|
136
167
|
isSticky={isFirstColumnSticky && i === 0}
|
|
137
168
|
isSecond={isFirstColumnSticky && i === 1}
|
|
138
|
-
|
|
139
|
-
|
|
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={
|
|
176
|
+
isNestedComponentExpanded={isNestedComponentExpanded && nestedComponentCellKey === key}
|
|
145
177
|
isRowNestedComponentExpanded={
|
|
146
|
-
|
|
178
|
+
isNestedComponentExpanded && isEmpty(nestedComponentCellKey)
|
|
147
179
|
}
|
|
148
|
-
onSetNestedComponent={(component) => updateNestedComponent(component, key
|
|
180
|
+
onSetNestedComponent={(component) => updateNestedComponent(component, key)}
|
|
149
181
|
/>
|
|
150
182
|
))}
|
|
151
183
|
</TableRow>
|
|
152
184
|
|
|
153
|
-
{
|
|
185
|
+
{isNestedComponentExpanded && (
|
|
154
186
|
<TableRow className={classes.root}>
|
|
155
|
-
<TableCell className={classes.nestedComponent} colSpan={
|
|
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);
|